1 /*
2 * Copyright (C) 2004-2015 Andrew Beekhof <andrew@beekhof.net>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <crm_internal.h>
20
21 #include <sys/param.h>
22
23 #include <crm/crm.h>
24
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <libgen.h>
34 #include <sys/utsname.h>
35
36 #include <crm/msg_xml.h>
37 #include <crm/services.h>
38 #include <crm/lrmd.h>
39 #include <crm/common/internal.h> /* crm_ends_with_ext */
40 #include <crm/common/ipc.h>
41 #include <crm/common/mainloop.h>
42 #include <crm/common/util.h>
43 #include <crm/common/xml.h>
44
45 #include <crm/cib/internal.h>
46 #include <crm/pengine/status.h>
47 #include <crm/pengine/internal.h>
48 #include <../lib/pengine/unpack.h>
49 #include <../pengine/pengine.h>
50 #include <crm/stonith-ng.h>
51
52 void clean_up_connections(void);
53 static int clean_up(int rc);
54 void crm_diff_update(const char *event, xmlNode * msg);
55 gboolean mon_refresh_display(gpointer user_data);
56 int cib_connect(gboolean full);
57 void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
58 void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
59 void kick_refresh(gboolean data_updated);
60 static char *get_node_display_name(node_t *node);
61
62 /*
63 * Definitions indicating which items to print
64 */
65
66 #define mon_show_times (0x0001U)
67 #define mon_show_stack (0x0002U)
68 #define mon_show_dc (0x0004U)
69 #define mon_show_count (0x0008U)
70 #define mon_show_nodes (0x0010U)
71 #define mon_show_resources (0x0020U)
72 #define mon_show_attributes (0x0040U)
73 #define mon_show_failcounts (0x0080U)
74 #define mon_show_operations (0x0100U)
75 #define mon_show_tickets (0x0200U)
76 #define mon_show_bans (0x0400U)
77 #define mon_show_fence_history (0x0800U)
78
79 #define mon_show_headers (mon_show_times | mon_show_stack | mon_show_dc \
80 | mon_show_count)
81 #define mon_show_default (mon_show_headers | mon_show_nodes \
82 | mon_show_resources)
83 #define mon_show_all (mon_show_default | mon_show_attributes \
84 | mon_show_failcounts | mon_show_operations \
85 | mon_show_tickets | mon_show_bans \
86 | mon_show_fence_history)
87
88 unsigned int show = mon_show_default;
89
90 /*
91 * Definitions indicating how to output
92 */
93
94 enum mon_output_format_e {
95 mon_output_none,
96 mon_output_monitor,
97 mon_output_plain,
98 mon_output_console,
99 mon_output_xml,
100 mon_output_html,
101 mon_output_cgi
102 } output_format = mon_output_console;
103
104 char *output_filename = NULL; /* if sending output to a file, its name */
105
106 /* other globals */
107 static GIOChannel *io_channel = NULL;
108 char *pid_file = NULL;
109 char *snmp_target = NULL;
110 char *snmp_community = NULL;
111
112 gboolean group_by_node = FALSE;
113 gboolean inactive_resources = FALSE;
114 int reconnect_msec = 5000;
115 gboolean daemonize = FALSE;
116 GMainLoop *mainloop = NULL;
117 guint timer_id = 0;
118 mainloop_timer_t *refresh_timer = NULL;
119 static pe_working_set_t *mon_data_set = NULL;
120 GList *attr_list = NULL;
121
122 const char *crm_mail_host = NULL;
123 const char *crm_mail_prefix = NULL;
124 const char *crm_mail_from = NULL;
125 const char *crm_mail_to = NULL;
126 const char *external_agent = NULL;
127 const char *external_recipient = NULL;
128
129 cib_t *cib = NULL;
130 stonith_t *st = NULL;
131 xmlNode *current_cib = NULL;
132
133 gboolean one_shot = FALSE;
134 gboolean has_warnings = FALSE;
135 gboolean print_timing = FALSE;
136 gboolean watch_fencing = FALSE;
137 gboolean fence_history = FALSE;
138 gboolean fence_full_history = FALSE;
139 gboolean fence_connect = FALSE;
140 int fence_history_level = 1;
141 gboolean print_brief = FALSE;
142 gboolean print_pending = TRUE;
143 gboolean print_clone_detail = FALSE;
144 #if CURSES_ENABLED
145 gboolean curses_console_initialized = FALSE;
146 #endif
147
148 /* FIXME allow, detect, and correctly interpret glob pattern or regex? */
149 const char *print_neg_location_prefix = "";
150
151 /* Never display node attributes whose name starts with one of these prefixes */
152 #define FILTER_STR { CRM_FAIL_COUNT_PREFIX, CRM_LAST_FAILURE_PREFIX, \
153 "shutdown", "terminate", "standby", "probe_complete", \
154 "#", NULL }
155
156 long last_refresh = 0;
157 crm_trigger_t *refresh_trigger = NULL;
158
159 /*
160 * 1.3.6.1.4.1.32723 has been assigned to the project by IANA
161 * http://www.iana.org/assignments/enterprise-numbers
162 */
163 #define PACEMAKER_PREFIX "1.3.6.1.4.1.32723"
164 #define PACEMAKER_TRAP_PREFIX PACEMAKER_PREFIX ".1"
165
166 #define snmp_crm_trap_oid PACEMAKER_TRAP_PREFIX
167 #define snmp_crm_oid_node PACEMAKER_TRAP_PREFIX ".1"
168 #define snmp_crm_oid_rsc PACEMAKER_TRAP_PREFIX ".2"
169 #define snmp_crm_oid_task PACEMAKER_TRAP_PREFIX ".3"
170 #define snmp_crm_oid_desc PACEMAKER_TRAP_PREFIX ".4"
171 #define snmp_crm_oid_status PACEMAKER_TRAP_PREFIX ".5"
172 #define snmp_crm_oid_rc PACEMAKER_TRAP_PREFIX ".6"
173 #define snmp_crm_oid_trc PACEMAKER_TRAP_PREFIX ".7"
174
175 /* Define exit codes for monitoring-compatible output */
176 #define MON_STATUS_OK (0)
177 #define MON_STATUS_WARN (1)
178 #define MON_STATUS_CRIT (2)
179 #define MON_STATUS_UNKNOWN (3)
180
181 /* Convenience macro for prettifying output (e.g. "node" vs "nodes") */
182 #define s_if_plural(i) (((i) == 1)? "" : "s")
183
184 #if CURSES_ENABLED
185 # define print_dot() if (output_format == mon_output_console) { \
186 printw("."); \
187 clrtoeol(); \
188 refresh(); \
189 } else { \
190 fprintf(stdout, "."); \
191 }
192 #else
193 # define print_dot() fprintf(stdout, ".");
194 #endif
195
196 #if CURSES_ENABLED
197 # define print_as(fmt, args...) if (output_format == mon_output_console) { \
198 printw(fmt, ##args); \
199 clrtoeol(); \
200 refresh(); \
201 } else { \
202 fprintf(stdout, fmt, ##args); \
203 }
204 #else
205 # define print_as(fmt, args...) fprintf(stdout, fmt, ##args);
206 #endif
207
208 static void
blank_screen(void)209 blank_screen(void)
210 {
211 #if CURSES_ENABLED
212 int lpc = 0;
213
214 for (lpc = 0; lpc < LINES; lpc++) {
215 move(lpc, 0);
216 clrtoeol();
217 }
218 move(0, 0);
219 refresh();
220 #endif
221 }
222
223 static gboolean
mon_timer_popped(gpointer data)224 mon_timer_popped(gpointer data)
225 {
226 int rc = pcmk_ok;
227
228 #if CURSES_ENABLED
229 if (output_format == mon_output_console) {
230 clear();
231 refresh();
232 }
233 #endif
234
235 if (timer_id > 0) {
236 g_source_remove(timer_id);
237 timer_id = 0;
238 }
239
240 print_as("Reconnecting...\n");
241 rc = cib_connect(TRUE);
242
243 if (rc != pcmk_ok) {
244 timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
245 }
246 return FALSE;
247 }
248
249 static void
mon_cib_connection_destroy(gpointer user_data)250 mon_cib_connection_destroy(gpointer user_data)
251 {
252 print_as("Connection to the cluster-daemons terminated\n");
253 if (refresh_timer != NULL) {
254 /* we'll trigger a refresh after reconnect */
255 mainloop_timer_stop(refresh_timer);
256 }
257 if (timer_id) {
258 /* we'll trigger a new reconnect-timeout at the end */
259 g_source_remove(timer_id);
260 timer_id = 0;
261 }
262 if (st) {
263 /* the client API won't properly reconnect notifications
264 * if they are still in the table - so remove them
265 */
266 st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
267 st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
268 st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
269 if (st->state != stonith_disconnected) {
270 st->cmds->disconnect(st);
271 }
272 }
273 if (cib) {
274 cib->cmds->signoff(cib);
275 timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
276 }
277 return;
278 }
279
280 /*
281 * Mainloop signal handler.
282 */
283 static void
mon_shutdown(int nsig)284 mon_shutdown(int nsig)
285 {
286 clean_up(EX_OK);
287 }
288
289 #if ON_DARWIN
290 # define sighandler_t sig_t
291 #endif
292
293 #if CURSES_ENABLED
294 # ifndef HAVE_SIGHANDLER_T
295 typedef void (*sighandler_t) (int);
296 # endif
297 static sighandler_t ncurses_winch_handler;
298 static void
mon_winresize(int nsig)299 mon_winresize(int nsig)
300 {
301 static int not_done;
302 int lines = 0, cols = 0;
303
304 if (!not_done++) {
305 if (ncurses_winch_handler)
306 /* the original ncurses WINCH signal handler does the
307 * magic of retrieving the new window size;
308 * otherwise, we'd have to use ioctl or tgetent */
309 (*ncurses_winch_handler) (SIGWINCH);
310 getmaxyx(stdscr, lines, cols);
311 resizeterm(lines, cols);
312 mainloop_set_trigger(refresh_trigger);
313 }
314 not_done--;
315 }
316 #endif
317
318 int
cib_connect(gboolean full)319 cib_connect(gboolean full)
320 {
321 int rc = pcmk_ok;
322 static gboolean need_pass = TRUE;
323
324 CRM_CHECK(cib != NULL, return -EINVAL);
325
326 if (getenv("CIB_passwd") != NULL) {
327 need_pass = FALSE;
328 }
329
330 if ((fence_connect) && (st == NULL)) {
331 st = stonith_api_new();
332 }
333
334 if ((fence_connect) && (st->state == stonith_disconnected)) {
335 crm_trace("Connecting to stonith");
336 rc = st->cmds->connect(st, crm_system_name, NULL);
337 if (rc == pcmk_ok) {
338 crm_trace("Setting up stonith callbacks");
339 if (watch_fencing) {
340 st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
341 mon_st_callback_event);
342 st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
343 } else {
344 st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
345 mon_st_callback_display);
346 st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
347 }
348 }
349 }
350
351 if (cib->state != cib_connected_query && cib->state != cib_connected_command) {
352 crm_trace("Connecting to the CIB");
353 if ((output_format == mon_output_console) && need_pass && (cib->variant == cib_remote)) {
354 need_pass = FALSE;
355 print_as("Password:");
356 }
357
358 rc = cib->cmds->signon(cib, crm_system_name, cib_query);
359
360 if (rc != pcmk_ok) {
361 return rc;
362 }
363
364 rc = cib->cmds->query(cib, NULL, ¤t_cib, cib_scope_local | cib_sync_call);
365 if (rc == pcmk_ok) {
366 mon_refresh_display(NULL);
367 }
368
369 if (rc == pcmk_ok && full) {
370 if (rc == pcmk_ok) {
371 rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy);
372 if (rc == -EPROTONOSUPPORT) {
373 print_as
374 ("Notification setup not supported, won't be able to reconnect after failure");
375 if (output_format == mon_output_console) {
376 sleep(2);
377 }
378 rc = pcmk_ok;
379 }
380
381 }
382
383 if (rc == pcmk_ok) {
384 cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
385 rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
386 }
387
388 if (rc != pcmk_ok) {
389 print_as("Notification setup failed, could not monitor CIB actions");
390 if (output_format == mon_output_console) {
391 sleep(2);
392 }
393 clean_up_connections();
394 }
395 }
396 }
397 return rc;
398 }
399
400 /* *INDENT-OFF* */
401 static struct crm_option long_options[] = {
402 /* Top-level Options */
403 {"help", 0, 0, '?', "\tThis text"},
404 {"version", 0, 0, '$', "\tVersion information" },
405 {"verbose", 0, 0, 'V', "\tIncrease debug output"},
406 {"quiet", 0, 0, 'Q', "\tDisplay only essential output" },
407
408 {"-spacer-", 1, 0, '-', "\nModes (mutually exclusive):"},
409 {"as-html", 1, 0, 'h', "\tWrite cluster status to the named html file"},
410 {"as-xml", 0, 0, 'X', "\t\tWrite cluster status as xml to stdout. This will enable one-shot mode."},
411 {"web-cgi", 0, 0, 'w', "\t\tWeb mode with output suitable for CGI (preselected when run as *.cgi)"},
412 {"simple-status", 0, 0, 's', "\tDisplay the cluster status once as a simple one line output (suitable for nagios)"},
413 {"snmp-traps", 1, 0, 'S', "\tSend SNMP traps to this station", !ENABLE_SNMP},
414 {"snmp-community", 1, 0, 'C', "Specify community for SNMP traps(default is NULL)", !ENABLE_SNMP},
415 {"mail-to", 1, 0, 'T', "\tSend Mail alerts to this user. See also --mail-from, --mail-host, --mail-prefix", !ENABLE_ESMTP},
416
417 {"-spacer-", 1, 0, '-', "\nDisplay Options:"},
418 {"group-by-node", 0, 0, 'n', "\tGroup resources by node" },
419 {"inactive", 0, 0, 'r', "\t\tDisplay inactive resources" },
420 {"failcounts", 0, 0, 'f', "\tDisplay resource fail counts"},
421 {"operations", 0, 0, 'o', "\tDisplay resource operation history" },
422 {"timing-details", 0, 0, 't', "\tDisplay resource operation history with timing details" },
423 {"tickets", 0, 0, 'c', "\t\tDisplay cluster tickets"},
424 {"watch-fencing", 0, 0, 'W', "\tListen for fencing events. For use with --external-agent, --mail-to and/or --snmp-traps where supported"},
425 {"fence-history", 2, 0, 'm', "Show fence history\n"
426 "\t\t\t\t\t0=off, 1=failures and pending (default without option),\n"
427 "\t\t\t\t\t2=add successes (default without value for option),\n"
428 "\t\t\t\t\t3=show full history without reduction to most recent of each flavor"},
429 {"neg-locations", 2, 0, 'L', "Display negative location constraints [optionally filtered by id prefix]"},
430 {"show-node-attributes", 0, 0, 'A', "Display node attributes" },
431 {"hide-headers", 0, 0, 'D', "\tHide all headers" },
432 {"show-detail", 0, 0, 'R', "\tShow more details (node IDs, individual clone instances)" },
433 {"brief", 0, 0, 'b', "\t\tBrief output" },
434 {"pending", 0, 0, 'j', "\t\tDisplay pending state if 'record-pending' is enabled", pcmk_option_hidden},
435
436 {"-spacer-", 1, 0, '-', "\nAdditional Options:"},
437 {"interval", 1, 0, 'i', "\tUpdate frequency in seconds" },
438 {"one-shot", 0, 0, '1', "\t\tDisplay the cluster status once on the console and exit"},
439 {"disable-ncurses",0, 0, 'N', "\tDisable the use of ncurses", !CURSES_ENABLED},
440 {"daemonize", 0, 0, 'd', "\tRun in the background as a daemon"},
441 {"pid-file", 1, 0, 'p', "\t(Advanced) Daemon pid file location"},
442 {"mail-from", 1, 0, 'F', "\tMail alerts should come from the named user", !ENABLE_ESMTP},
443 {"mail-host", 1, 0, 'H', "\tMail alerts should be sent via the named host", !ENABLE_ESMTP},
444 {"mail-prefix", 1, 0, 'P', "Subjects for mail alerts should start with this string", !ENABLE_ESMTP},
445 {"external-agent", 1, 0, 'E', "A program to run when resource operations take place."},
446 {"external-recipient",1, 0, 'e', "A recipient for your program (assuming you want the program to send something to someone)."},
447
448
449 {"xml-file", 1, 0, 'x', NULL, pcmk_option_hidden},
450
451 {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph},
452 {"-spacer-", 1, 0, '-', "Display the cluster status on the console with updates as they occur:", pcmk_option_paragraph},
453 {"-spacer-", 1, 0, '-', " crm_mon", pcmk_option_example},
454 {"-spacer-", 1, 0, '-', "Display the cluster status on the console just once then exit:", pcmk_option_paragraph},
455 {"-spacer-", 1, 0, '-', " crm_mon -1", pcmk_option_example},
456 {"-spacer-", 1, 0, '-', "Display your cluster status, group resources by node, and include inactive resources in the list:", pcmk_option_paragraph},
457 {"-spacer-", 1, 0, '-', " crm_mon --group-by-node --inactive", pcmk_option_example},
458 {"-spacer-", 1, 0, '-', "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:", pcmk_option_paragraph},
459 {"-spacer-", 1, 0, '-', " crm_mon --daemonize --as-html /path/to/docroot/filename.html", pcmk_option_example},
460 {"-spacer-", 1, 0, '-', "Start crm_mon and export the current cluster status as xml to stdout, then exit.:", pcmk_option_paragraph},
461 {"-spacer-", 1, 0, '-', " crm_mon --as-xml", pcmk_option_example},
462 {"-spacer-", 1, 0, '-', "Start crm_mon as a background daemon and have it send email alerts:", pcmk_option_paragraph|!ENABLE_ESMTP},
463 {"-spacer-", 1, 0, '-', " crm_mon --daemonize --mail-to user@example.com --mail-host mail.example.com", pcmk_option_example|!ENABLE_ESMTP},
464 {"-spacer-", 1, 0, '-', "Start crm_mon as a background daemon and have it send SNMP alerts:", pcmk_option_paragraph|!ENABLE_SNMP},
465 {"-spacer-", 1, 0, '-', " crm_mon --daemonize --snmp-traps snmptrapd.example.com", pcmk_option_example|!ENABLE_SNMP},
466
467 {NULL, 0, 0, 0}
468 };
469 /* *INDENT-ON* */
470
471 #if CURSES_ENABLED
472 static const char *
get_option_desc(char c)473 get_option_desc(char c)
474 {
475 int lpc;
476
477 for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
478
479 if (long_options[lpc].name[0] == '-')
480 continue;
481
482 if (long_options[lpc].val == c) {
483 static char *buf = NULL;
484 const char *rv;
485 char *nl;
486
487 /* chop off tabs and cut at newline */
488 free(buf); /* free string from last usage */
489 buf = strdup(long_options[lpc].desc);
490 rv = buf; /* make a copy to keep buf pointer unaltered
491 for freeing when we come by next time.
492 Like this the result stays valid until
493 the next call.
494 */
495 while(isspace(rv[0])) {
496 rv++;
497 }
498 nl = strchr(rv, '\n');
499 if (nl) {
500 *nl = '\0';
501 }
502 return rv;
503 }
504 }
505
506 return NULL;
507 }
508
509 #define print_option_help(option, condition) \
510 print_as("%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
511
512 static gboolean
detect_user_input(GIOChannel * channel,GIOCondition condition,gpointer unused)513 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer unused)
514 {
515 int c;
516 gboolean config_mode = FALSE;
517
518 while (1) {
519
520 /* Get user input */
521 c = getchar();
522
523 switch (c) {
524 case 'm':
525 if (!fence_history_level) {
526 fence_history = TRUE;
527 fence_connect = TRUE;
528 if (st == NULL) {
529 mon_cib_connection_destroy(NULL);
530 }
531 }
532 show ^= mon_show_fence_history;
533 break;
534 case 'c':
535 show ^= mon_show_tickets;
536 break;
537 case 'f':
538 show ^= mon_show_failcounts;
539 break;
540 case 'n':
541 group_by_node = ! group_by_node;
542 break;
543 case 'o':
544 show ^= mon_show_operations;
545 if ((show & mon_show_operations) == 0) {
546 print_timing = 0;
547 }
548 break;
549 case 'r':
550 inactive_resources = ! inactive_resources;
551 break;
552 case 'R':
553 print_clone_detail = ! print_clone_detail;
554 break;
555 case 't':
556 print_timing = ! print_timing;
557 if (print_timing) {
558 show |= mon_show_operations;
559 }
560 break;
561 case 'A':
562 show ^= mon_show_attributes;
563 break;
564 case 'L':
565 show ^= mon_show_bans;
566 break;
567 case 'D':
568 /* If any header is shown, clear them all, otherwise set them all */
569 if (show & mon_show_headers) {
570 show &= ~mon_show_headers;
571 } else {
572 show |= mon_show_headers;
573 }
574 break;
575 case 'b':
576 print_brief = ! print_brief;
577 break;
578 case 'j':
579 print_pending = ! print_pending;
580 break;
581 case '?':
582 config_mode = TRUE;
583 break;
584 default:
585 goto refresh;
586 }
587
588 if (!config_mode)
589 goto refresh;
590
591 blank_screen();
592
593 print_as("Display option change mode\n");
594 print_as("\n");
595 print_option_help('c', show & mon_show_tickets);
596 print_option_help('f', show & mon_show_failcounts);
597 print_option_help('n', group_by_node);
598 print_option_help('o', show & mon_show_operations);
599 print_option_help('r', inactive_resources);
600 print_option_help('t', print_timing);
601 print_option_help('A', show & mon_show_attributes);
602 print_option_help('L', show & mon_show_bans);
603 print_option_help('D', (show & mon_show_headers) == 0);
604 print_option_help('R', print_clone_detail);
605 print_option_help('b', print_brief);
606 print_option_help('j', print_pending);
607 print_option_help('m', (show & mon_show_fence_history));
608 print_as("\n");
609 print_as("Toggle fields via field letter, type any other key to return");
610 }
611
612 refresh:
613 mon_refresh_display(NULL);
614 return TRUE;
615 }
616 #endif
617
618 int
main(int argc,char ** argv)619 main(int argc, char **argv)
620 {
621 int flag;
622 int argerr = 0;
623 int exit_code = 0;
624 int option_index = 0;
625
626 pid_file = strdup("/tmp/ClusterMon.pid");
627 crm_log_cli_init("crm_mon");
628 crm_set_options(NULL, "mode [options]", long_options,
629 "Provides a summary of cluster's current state."
630 "\n\nOutputs varying levels of detail in a number of different formats.\n");
631
632 #if !defined (ON_DARWIN) && !defined (ON_BSD)
633 /* prevent zombies */
634 signal(SIGCLD, SIG_IGN);
635 #endif
636
637 if (crm_ends_with_ext(argv[0], ".cgi") == TRUE) {
638 output_format = mon_output_cgi;
639 one_shot = TRUE;
640 }
641
642 /* to enable stonith-connection when called via some application like pcs
643 * set environment-variable FENCE_HISTORY to desired level
644 * so you don't have to modify this application
645 */
646 /* fence_history_level = crm_atoi(getenv("FENCE_HISTORY"), "0"); */
647
648 while (1) {
649 flag = crm_get_option(argc, argv, &option_index);
650 if (flag == -1)
651 break;
652
653 switch (flag) {
654 case 'V':
655 crm_bump_log_level(argc, argv);
656 break;
657 case 'Q':
658 show &= ~mon_show_times;
659 break;
660 case 'i':
661 reconnect_msec = crm_get_msec(optarg);
662 break;
663 case 'n':
664 group_by_node = TRUE;
665 break;
666 case 'r':
667 inactive_resources = TRUE;
668 break;
669 case 'W':
670 watch_fencing = TRUE;
671 fence_connect = TRUE;
672 break;
673 case 'm':
674 fence_history_level = crm_atoi(optarg, "2");
675 break;
676 case 'd':
677 daemonize = TRUE;
678 break;
679 case 't':
680 print_timing = TRUE;
681 show |= mon_show_operations;
682 break;
683 case 'o':
684 show |= mon_show_operations;
685 break;
686 case 'f':
687 show |= mon_show_failcounts;
688 break;
689 case 'A':
690 show |= mon_show_attributes;
691 break;
692 case 'L':
693 show |= mon_show_bans;
694 print_neg_location_prefix = optarg? optarg : "";
695 break;
696 case 'D':
697 show &= ~mon_show_headers;
698 break;
699 case 'b':
700 print_brief = TRUE;
701 break;
702 case 'j':
703 print_pending = TRUE;
704 break;
705 case 'R':
706 print_clone_detail = TRUE;
707 break;
708 case 'c':
709 show |= mon_show_tickets;
710 break;
711 case 'p':
712 free(pid_file);
713 if(optarg == NULL) {
714 return crm_help(flag, EX_USAGE);
715 }
716 pid_file = strdup(optarg);
717 break;
718 case 'x':
719 if(optarg == NULL) {
720 return crm_help(flag, EX_USAGE);
721 }
722 setenv("CIB_file", optarg, 1);
723 one_shot = TRUE;
724 break;
725 case 'h':
726 if(optarg == NULL) {
727 return crm_help(flag, EX_USAGE);
728 }
729 argerr += (output_format != mon_output_console);
730 output_format = mon_output_html;
731 output_filename = strdup(optarg);
732 umask(S_IWGRP | S_IWOTH);
733 break;
734 case 'X':
735 argerr += (output_format != mon_output_console);
736 output_format = mon_output_xml;
737 one_shot = TRUE;
738 break;
739 case 'w':
740 /* do not allow argv[0] and argv[1...] redundancy */
741 argerr += (output_format != mon_output_console);
742 output_format = mon_output_cgi;
743 one_shot = TRUE;
744 break;
745 case 's':
746 argerr += (output_format != mon_output_console);
747 output_format = mon_output_monitor;
748 one_shot = TRUE;
749 break;
750 case 'S':
751 snmp_target = optarg;
752 break;
753 case 'T':
754 crm_mail_to = optarg;
755 break;
756 case 'F':
757 crm_mail_from = optarg;
758 break;
759 case 'H':
760 crm_mail_host = optarg;
761 break;
762 case 'P':
763 crm_mail_prefix = optarg;
764 break;
765 case 'E':
766 external_agent = optarg;
767 break;
768 case 'e':
769 external_recipient = optarg;
770 break;
771 case '1':
772 one_shot = TRUE;
773 break;
774 case 'N':
775 if (output_format == mon_output_console) {
776 output_format = mon_output_plain;
777 }
778 break;
779 case 'C':
780 snmp_community = optarg;
781 break;
782 case '$':
783 case '?':
784 return crm_help(flag, EX_OK);
785 break;
786 default:
787 printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag);
788 ++argerr;
789 break;
790 }
791 }
792
793 if (watch_fencing) {
794 /* don't moan as fence_history_level == 1 is default */
795 fence_history_level = 0;
796 }
797
798 /* create the cib-object early to be able to do further
799 * decisions based on the cib-source
800 */
801 cib = cib_new();
802
803 if (cib == NULL) {
804 exit_code = -EINVAL;
805 } else {
806 switch (cib->variant) {
807
808 case cib_native:
809 /* cib & fencing - everything available */
810 break;
811
812 case cib_file:
813 /* Don't try to connect to fencing as we
814 * either don't have a running cluster or
815 * the fencing-information would possibly
816 * not match the cib data from a file.
817 * As we don't expect cib-updates coming
818 * in enforce one-shot. */
819 fence_history_level = 0;
820 one_shot = TRUE;
821 break;
822
823 case cib_remote:
824 /* updates coming in but no fencing */
825 fence_history_level = 0;
826 break;
827
828 case cib_undefined:
829 case cib_database:
830 default:
831 /* something is odd */
832 exit_code = -EINVAL;
833 crm_err("Invalid cib-source");
834 break;
835 }
836 }
837
838 switch (fence_history_level) {
839 case 3:
840 fence_full_history = TRUE;
841 /* fall through to next lower level */
842 case 2:
843 show |= mon_show_fence_history;
844 /* fall through to next lower level */
845 case 1:
846 fence_history = TRUE;
847 fence_connect = TRUE;
848 break;
849 default:
850 break;
851 }
852
853 /* Extra sanity checks when in CGI mode */
854 if (output_format == mon_output_cgi) {
855 argerr += (optind < argc);
856 argerr += (output_filename != NULL);
857 argerr += ((cib) && (cib->variant == cib_file));
858 argerr += (snmp_target != NULL);
859 argerr += (crm_mail_to != NULL);
860 argerr += (external_agent != NULL);
861 argerr += (daemonize == TRUE); /* paranoia */
862
863 } else if (optind < argc) {
864 printf("non-option ARGV-elements: ");
865 while (optind < argc)
866 printf("%s ", argv[optind++]);
867 printf("\n");
868 }
869
870 if (argerr) {
871 return clean_up(EX_USAGE);
872 }
873
874 /* XML output always prints everything */
875 if (output_format == mon_output_xml) {
876 show = mon_show_all;
877 print_timing = TRUE;
878 }
879
880 if (one_shot) {
881 if (output_format == mon_output_console) {
882 output_format = mon_output_plain;
883 }
884
885 } else if (daemonize) {
886 if ((output_format == mon_output_console) || (output_format == mon_output_plain)) {
887 output_format = mon_output_none;
888 }
889 crm_enable_stderr(FALSE);
890
891 if ((output_format != mon_output_html) && (output_format != mon_output_xml)
892 && !snmp_target && !crm_mail_to && !external_agent) {
893 printf ("Looks like you forgot to specify one or more of: "
894 "--as-html, --external-agent, --mail-to, --snmp-target\n");
895 return clean_up(EX_USAGE);
896 }
897
898 if (cib) {
899 /* to be on the safe side don't have cib-object around
900 * when we are forking
901 */
902 cib_delete(cib);
903 cib = NULL;
904 crm_make_daemon(crm_system_name, TRUE, pid_file);
905 cib = cib_new();
906 if (cib == NULL) {
907 exit_code = -EINVAL;
908 }
909 /* otherwise assume we've got the same cib-object we've just destroyed
910 * in our parent
911 */
912 }
913
914 } else if (output_format == mon_output_console) {
915 #if CURSES_ENABLED
916 initscr();
917 cbreak();
918 noecho();
919 crm_enable_stderr(FALSE);
920 curses_console_initialized = TRUE;
921 #else
922 one_shot = TRUE;
923 output_format = mon_output_plain;
924 printf("Defaulting to one-shot mode\n");
925 printf("You need to have curses available at compile time to enable console mode\n");
926 #endif
927 }
928
929 crm_info("Starting %s", crm_system_name);
930
931 if (cib) {
932
933 do {
934 if (!one_shot) {
935 print_as("Waiting until cluster is available on this node ...\n");
936 }
937 exit_code = cib_connect(!one_shot);
938
939 if (one_shot) {
940 break;
941 } else if (exit_code != pcmk_ok) {
942 sleep(reconnect_msec / 1000);
943 #if CURSES_ENABLED
944 if (output_format == mon_output_console) {
945 clear();
946 refresh();
947 }
948 #endif
949 } else {
950 if (output_format == mon_output_html) {
951 print_as("Writing html to %s ...\n", output_filename);
952 }
953 }
954 } while (exit_code == -ENOTCONN);
955 }
956
957 if (exit_code != pcmk_ok) {
958 if (output_format == mon_output_monitor) {
959 printf("CLUSTER CRIT: Connection to cluster failed: %s\n",
960 pcmk_strerror(exit_code));
961 return clean_up(MON_STATUS_CRIT);
962 } else {
963 if (exit_code == -ENOTCONN) {
964 print_as("\nError: cluster is not available on this node\n");
965 } else {
966 print_as("\nConnection to cluster failed: %s\n",
967 pcmk_strerror(exit_code));
968 }
969 }
970 if (output_format == mon_output_console) {
971 sleep(2);
972 }
973 return clean_up(-exit_code);
974 }
975
976 if (one_shot) {
977 return clean_up(0);
978 }
979
980 mainloop = g_main_new(FALSE);
981
982 mainloop_add_signal(SIGTERM, mon_shutdown);
983 mainloop_add_signal(SIGINT, mon_shutdown);
984 #if CURSES_ENABLED
985 if (output_format == mon_output_console) {
986 ncurses_winch_handler = signal(SIGWINCH, mon_winresize);
987 if (ncurses_winch_handler == SIG_DFL ||
988 ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
989 ncurses_winch_handler = NULL;
990
991 io_channel = g_io_channel_unix_new(STDIN_FILENO);
992 g_io_add_watch(io_channel, G_IO_IN, detect_user_input, NULL);
993 }
994 #endif
995 refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
996
997 g_main_run(mainloop);
998 g_main_destroy(mainloop);
999
1000 if (io_channel != NULL) {
1001 g_io_channel_shutdown(io_channel, TRUE, NULL);
1002 }
1003
1004 crm_info("Exiting %s", crm_system_name);
1005
1006 return clean_up(0);
1007 }
1008
1009 #define mon_warn(fmt...) do { \
1010 if (!has_warnings) { \
1011 print_as("CLUSTER WARN:"); \
1012 } else { \
1013 print_as(","); \
1014 } \
1015 print_as(fmt); \
1016 has_warnings = TRUE; \
1017 } while(0)
1018
1019
1020 /*!
1021 * \internal
1022 * \brief Print one-line status suitable for use with monitoring software
1023 *
1024 * \param[in] data_set Working set of CIB state
1025 * \param[in] history List of stonith actions
1026 *
1027 * \note This function's output (and the return code when the program exits)
1028 * should conform to https://www.monitoring-plugins.org/doc/guidelines.html
1029 */
1030 static void
print_simple_status(pe_working_set_t * data_set,stonith_history_t * history)1031 print_simple_status(pe_working_set_t * data_set,
1032 stonith_history_t *history)
1033 {
1034 GListPtr gIter = NULL;
1035 int nodes_online = 0;
1036 int nodes_standby = 0;
1037 int nodes_maintenance = 0;
1038
1039 if (data_set->dc_node == NULL) {
1040 mon_warn(" No DC");
1041 }
1042
1043 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
1044 node_t *node = (node_t *) gIter->data;
1045
1046 if (node->details->standby && node->details->online) {
1047 nodes_standby++;
1048 } else if (node->details->maintenance && node->details->online) {
1049 nodes_maintenance++;
1050 } else if (node->details->online) {
1051 nodes_online++;
1052 } else {
1053 mon_warn(" offline node: %s", node->details->uname);
1054 }
1055 }
1056
1057 if (!has_warnings) {
1058
1059 print_as("CLUSTER OK: %d node%s online", nodes_online, s_if_plural(nodes_online));
1060 if (nodes_standby > 0) {
1061 print_as(", %d standby node%s", nodes_standby, s_if_plural(nodes_standby));
1062 }
1063 if (nodes_maintenance > 0) {
1064 print_as(", %d maintenance node%s", nodes_maintenance, s_if_plural(nodes_maintenance));
1065 }
1066 print_as(", %d resource instance%s configured",
1067 data_set->ninstances, s_if_plural(data_set->ninstances));
1068 }
1069
1070 print_as("\n");
1071 }
1072
1073 /*!
1074 * \internal
1075 * \brief Print a [name]=[value][units] pair, optionally using time string
1076 *
1077 * \param[in] stream File stream to display output to
1078 * \param[in] name Name to display
1079 * \param[in] value Value to display (or NULL to convert time instead)
1080 * \param[in] units Units to display (or NULL for no units)
1081 * \param[in] epoch_time Epoch time to convert if value is NULL
1082 */
1083 static void
print_nvpair(FILE * stream,const char * name,const char * value,const char * units,time_t epoch_time)1084 print_nvpair(FILE *stream, const char *name, const char *value,
1085 const char *units, time_t epoch_time)
1086 {
1087 /* print name= */
1088 switch (output_format) {
1089 case mon_output_plain:
1090 case mon_output_console:
1091 print_as(" %s=", name);
1092 break;
1093
1094 case mon_output_html:
1095 case mon_output_cgi:
1096 case mon_output_xml:
1097 fprintf(stream, " %s=", name);
1098 break;
1099
1100 default:
1101 break;
1102 }
1103
1104 /* If we have a value (and optionally units), print it */
1105 if (value) {
1106 switch (output_format) {
1107 case mon_output_plain:
1108 case mon_output_console:
1109 print_as("%s%s", value, (units? units : ""));
1110 break;
1111
1112 case mon_output_html:
1113 case mon_output_cgi:
1114 fprintf(stream, "%s%s", value, (units? units : ""));
1115 break;
1116
1117 case mon_output_xml:
1118 fprintf(stream, "\"%s%s\"", value, (units? units : ""));
1119 break;
1120
1121 default:
1122 break;
1123 }
1124
1125 /* Otherwise print user-friendly time string */
1126 } else {
1127 static char empty_str[] = "";
1128 char *c, *date_str = asctime(localtime(&epoch_time));
1129
1130 for (c = (date_str != NULL) ? date_str : empty_str; *c != '\0'; ++c) {
1131 if (*c == '\n') {
1132 *c = '\0';
1133 break;
1134 }
1135 }
1136 switch (output_format) {
1137 case mon_output_plain:
1138 case mon_output_console:
1139 print_as("'%s'", date_str);
1140 break;
1141
1142 case mon_output_html:
1143 case mon_output_cgi:
1144 case mon_output_xml:
1145 fprintf(stream, "\"%s\"", date_str);
1146 break;
1147
1148 default:
1149 break;
1150 }
1151 }
1152 }
1153
1154 /*!
1155 * \internal
1156 * \brief Print whatever is needed to start a node section
1157 *
1158 * \param[in] stream File stream to display output to
1159 * \param[in] node Node to print
1160 */
1161 static void
print_node_start(FILE * stream,node_t * node)1162 print_node_start(FILE *stream, node_t *node)
1163 {
1164 char *node_name;
1165
1166 switch (output_format) {
1167 case mon_output_plain:
1168 case mon_output_console:
1169 node_name = get_node_display_name(node);
1170 print_as("* Node %s:\n", node_name);
1171 free(node_name);
1172 break;
1173
1174 case mon_output_html:
1175 case mon_output_cgi:
1176 node_name = get_node_display_name(node);
1177 fprintf(stream, " <h3>Node: %s</h3>\n <ul>\n", node_name);
1178 free(node_name);
1179 break;
1180
1181 case mon_output_xml:
1182 fprintf(stream, " <node name=\"%s\">\n", node->details->uname);
1183 break;
1184
1185 default:
1186 break;
1187 }
1188 }
1189
1190 /*!
1191 * \internal
1192 * \brief Print whatever is needed to end a node section
1193 *
1194 * \param[in] stream File stream to display output to
1195 */
1196 static void
print_node_end(FILE * stream)1197 print_node_end(FILE *stream)
1198 {
1199 switch (output_format) {
1200 case mon_output_html:
1201 case mon_output_cgi:
1202 fprintf(stream, " </ul>\n");
1203 break;
1204
1205 case mon_output_xml:
1206 fprintf(stream, " </node>\n");
1207 break;
1208
1209 default:
1210 break;
1211 }
1212 }
1213
1214 /*!
1215 * \internal
1216 * \brief Print resources section heading appropriate to options
1217 *
1218 * \param[in] stream File stream to display output to
1219 */
1220 static void
print_resources_heading(FILE * stream)1221 print_resources_heading(FILE *stream)
1222 {
1223 const char *heading;
1224
1225 if (group_by_node) {
1226
1227 /* Active resources have already been printed by node */
1228 heading = (inactive_resources? "Inactive resources" : NULL);
1229
1230 } else if (inactive_resources) {
1231 heading = "Full list of resources";
1232
1233 } else {
1234 heading = "Active resources";
1235 }
1236
1237 /* Print section heading */
1238 switch (output_format) {
1239 case mon_output_plain:
1240 case mon_output_console:
1241 print_as("\n%s:\n\n", heading);
1242 break;
1243
1244 case mon_output_html:
1245 case mon_output_cgi:
1246 fprintf(stream, " <hr />\n <h2>%s</h2>\n", heading);
1247 break;
1248
1249 case mon_output_xml:
1250 fprintf(stream, " <resources>\n");
1251 break;
1252
1253 default:
1254 break;
1255 }
1256
1257 }
1258
1259 /*!
1260 * \internal
1261 * \brief Print whatever resource section closing is appropriate
1262 *
1263 * \param[in] stream File stream to display output to
1264 */
1265 static void
print_resources_closing(FILE * stream,gboolean printed_heading)1266 print_resources_closing(FILE *stream, gboolean printed_heading)
1267 {
1268 const char *heading;
1269
1270 /* What type of resources we did or did not display */
1271 if (group_by_node) {
1272 heading = "inactive ";
1273 } else if (inactive_resources) {
1274 heading = "";
1275 } else {
1276 heading = "active ";
1277 }
1278
1279 switch (output_format) {
1280 case mon_output_plain:
1281 case mon_output_console:
1282 if (!printed_heading) {
1283 print_as("\nNo %sresources\n\n", heading);
1284 }
1285 break;
1286
1287 case mon_output_html:
1288 case mon_output_cgi:
1289 if (!printed_heading) {
1290 fprintf(stream, " <hr />\n <h2>No %sresources</h2>\n", heading);
1291 }
1292 break;
1293
1294 case mon_output_xml:
1295 fprintf(stream, " %s\n",
1296 (printed_heading? "</resources>" : "<resources/>"));
1297 break;
1298
1299 default:
1300 break;
1301 }
1302 }
1303
1304 /*!
1305 * \internal
1306 * \brief Print whatever resource section(s) are appropriate
1307 *
1308 * \param[in] stream File stream to display output to
1309 * \param[in] data_set Cluster state to display
1310 * \param[in] print_opts Bitmask of pe_print_options
1311 */
1312 static void
print_resources(FILE * stream,pe_working_set_t * data_set,int print_opts)1313 print_resources(FILE *stream, pe_working_set_t *data_set, int print_opts)
1314 {
1315 GListPtr rsc_iter;
1316 const char *prefix = NULL;
1317 gboolean printed_heading = FALSE;
1318 gboolean brief_output = print_brief;
1319
1320 /* If we already showed active resources by node, and
1321 * we're not showing inactive resources, we have nothing to do
1322 */
1323 if (group_by_node && !inactive_resources) {
1324 return;
1325 }
1326
1327 /* XML uses an indent, and ignores brief option for resources */
1328 if (output_format == mon_output_xml) {
1329 prefix = " ";
1330 brief_output = FALSE;
1331 }
1332
1333 /* If we haven't already printed resources grouped by node,
1334 * and brief output was requested, print resource summary */
1335 if (brief_output && !group_by_node) {
1336 print_resources_heading(stream);
1337 printed_heading = TRUE;
1338 print_rscs_brief(data_set->resources, NULL, print_opts, stream,
1339 inactive_resources);
1340 }
1341
1342 /* For each resource, display it if appropriate */
1343 for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
1344 resource_t *rsc = (resource_t *) rsc_iter->data;
1345
1346 /* Complex resources may have some sub-resources active and some inactive */
1347 gboolean is_active = rsc->fns->active(rsc, TRUE);
1348 gboolean partially_active = rsc->fns->active(rsc, FALSE);
1349
1350 /* Skip inactive orphans (deleted but still in CIB) */
1351 if (is_set(rsc->flags, pe_rsc_orphan) && !is_active) {
1352 continue;
1353
1354 /* Skip active resources if we already displayed them by node */
1355 } else if (group_by_node) {
1356 if (is_active) {
1357 continue;
1358 }
1359
1360 /* Skip primitives already counted in a brief summary */
1361 } else if (brief_output && (rsc->variant == pe_native)) {
1362 continue;
1363
1364 /* Skip resources that aren't at least partially active,
1365 * unless we're displaying inactive resources
1366 */
1367 } else if (!partially_active && !inactive_resources) {
1368 continue;
1369 }
1370
1371 /* Print this resource */
1372 if (printed_heading == FALSE) {
1373 print_resources_heading(stream);
1374 printed_heading = TRUE;
1375 }
1376 rsc->fns->print(rsc, prefix, print_opts, stream);
1377 }
1378
1379 print_resources_closing(stream, printed_heading);
1380 }
1381
1382 /*!
1383 * \internal
1384 * \brief Print heading for resource history
1385 *
1386 * \param[in] stream File stream to display output to
1387 * \param[in] data_set Current state of CIB
1388 * \param[in] node Node that ran this resource
1389 * \param[in] rsc Resource to print
1390 * \param[in] rsc_id ID of resource to print
1391 * \param[in] all Whether to print every resource or just failed ones
1392 */
1393 static void
print_rsc_history_start(FILE * stream,pe_working_set_t * data_set,node_t * node,resource_t * rsc,const char * rsc_id,gboolean all)1394 print_rsc_history_start(FILE *stream, pe_working_set_t *data_set, node_t *node,
1395 resource_t *rsc, const char *rsc_id, gboolean all)
1396 {
1397 time_t last_failure = 0;
1398 int failcount = rsc?
1399 pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
1400 NULL, data_set)
1401 : 0;
1402
1403 if (!all && !failcount && (last_failure <= 0)) {
1404 return;
1405 }
1406
1407 /* Print resource ID */
1408 switch (output_format) {
1409 case mon_output_plain:
1410 case mon_output_console:
1411 print_as(" %s:", rsc_id);
1412 break;
1413
1414 case mon_output_html:
1415 case mon_output_cgi:
1416 fprintf(stream, " <li>%s:", rsc_id);
1417 break;
1418
1419 case mon_output_xml:
1420 fprintf(stream, " <resource_history id=\"%s\"", rsc_id);
1421 break;
1422
1423 default:
1424 break;
1425 }
1426
1427 /* If resource is an orphan, that's all we can say about it */
1428 if (rsc == NULL) {
1429 switch (output_format) {
1430 case mon_output_plain:
1431 case mon_output_console:
1432 print_as(" orphan");
1433 break;
1434
1435 case mon_output_html:
1436 case mon_output_cgi:
1437 fprintf(stream, " orphan");
1438 break;
1439
1440 case mon_output_xml:
1441 fprintf(stream, " orphan=\"true\"");
1442 break;
1443
1444 default:
1445 break;
1446 }
1447
1448 /* If resource is not an orphan, print some details */
1449 } else if (all || failcount || (last_failure > 0)) {
1450
1451 /* Print migration threshold */
1452 switch (output_format) {
1453 case mon_output_plain:
1454 case mon_output_console:
1455 print_as(" migration-threshold=%d", rsc->migration_threshold);
1456 break;
1457
1458 case mon_output_html:
1459 case mon_output_cgi:
1460 fprintf(stream, " migration-threshold=%d", rsc->migration_threshold);
1461 break;
1462
1463 case mon_output_xml:
1464 fprintf(stream, " orphan=\"false\" migration-threshold=\"%d\"",
1465 rsc->migration_threshold);
1466 break;
1467
1468 default:
1469 break;
1470 }
1471
1472 /* Print fail count if any */
1473 if (failcount > 0) {
1474 switch (output_format) {
1475 case mon_output_plain:
1476 case mon_output_console:
1477 print_as(" " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
1478 break;
1479
1480 case mon_output_html:
1481 case mon_output_cgi:
1482 fprintf(stream, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
1483 break;
1484
1485 case mon_output_xml:
1486 fprintf(stream, " " CRM_FAIL_COUNT_PREFIX "=\"%d\"",
1487 failcount);
1488 break;
1489
1490 default:
1491 break;
1492 }
1493 }
1494
1495 /* Print last failure time if any */
1496 if (last_failure > 0) {
1497 print_nvpair(stream, CRM_LAST_FAILURE_PREFIX, NULL, NULL,
1498 last_failure);
1499 }
1500 }
1501
1502 /* End the heading */
1503 switch (output_format) {
1504 case mon_output_plain:
1505 case mon_output_console:
1506 print_as("\n");
1507 break;
1508
1509 case mon_output_html:
1510 case mon_output_cgi:
1511 fprintf(stream, "\n <ul>\n");
1512 break;
1513
1514 case mon_output_xml:
1515 fprintf(stream, ">\n");
1516 break;
1517
1518 default:
1519 break;
1520 }
1521 }
1522
1523 /*!
1524 * \internal
1525 * \brief Print closing for resource history
1526 *
1527 * \param[in] stream File stream to display output to
1528 */
1529 static void
print_rsc_history_end(FILE * stream)1530 print_rsc_history_end(FILE *stream)
1531 {
1532 switch (output_format) {
1533 case mon_output_html:
1534 case mon_output_cgi:
1535 fprintf(stream, " </ul>\n </li>\n");
1536 break;
1537
1538 case mon_output_xml:
1539 fprintf(stream, " </resource_history>\n");
1540 break;
1541
1542 default:
1543 break;
1544 }
1545 }
1546
1547 /*!
1548 * \internal
1549 * \brief Print operation history
1550 *
1551 * \param[in] stream File stream to display output to
1552 * \param[in] data_set Current state of CIB
1553 * \param[in] node Node this operation is for
1554 * \param[in] xml_op Root of XML tree describing this operation
1555 * \param[in] task Task parsed from this operation's XML
1556 * \param[in] interval Interval parsed from this operation's XML
1557 * \param[in] rc Return code parsed from this operation's XML
1558 */
1559 static void
print_op_history(FILE * stream,pe_working_set_t * data_set,node_t * node,xmlNode * xml_op,const char * task,const char * interval,int rc)1560 print_op_history(FILE *stream, pe_working_set_t *data_set, node_t *node,
1561 xmlNode *xml_op, const char *task, const char *interval, int rc)
1562 {
1563 const char *value = NULL;
1564 const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
1565
1566 /* Begin the operation description */
1567 switch (output_format) {
1568 case mon_output_plain:
1569 case mon_output_console:
1570 print_as(" + (%s) %s:", call, task);
1571 break;
1572
1573 case mon_output_html:
1574 case mon_output_cgi:
1575 fprintf(stream, " <li>(%s) %s:", call, task);
1576 break;
1577
1578 case mon_output_xml:
1579 fprintf(stream, " <operation_history call=\"%s\" task=\"%s\"",
1580 call, task);
1581 break;
1582
1583 default:
1584 break;
1585 }
1586
1587 /* Add name=value pairs as appropriate */
1588 if (safe_str_neq(interval, "0")) {
1589 print_nvpair(stream, "interval", interval, "ms", 0);
1590 }
1591 if (print_timing) {
1592 int int_value;
1593 const char *attr;
1594
1595 attr = XML_RSC_OP_LAST_CHANGE;
1596 value = crm_element_value(xml_op, attr);
1597 if (value) {
1598 int_value = crm_parse_int(value, NULL);
1599 if (int_value > 0) {
1600 print_nvpair(stream, attr, NULL, NULL, int_value);
1601 }
1602 }
1603
1604 attr = XML_RSC_OP_LAST_RUN;
1605 value = crm_element_value(xml_op, attr);
1606 if (value) {
1607 int_value = crm_parse_int(value, NULL);
1608 if (int_value > 0) {
1609 print_nvpair(stream, attr, NULL, NULL, int_value);
1610 }
1611 }
1612
1613 attr = XML_RSC_OP_T_EXEC;
1614 value = crm_element_value(xml_op, attr);
1615 if (value) {
1616 print_nvpair(stream, attr, value, "ms", 0);
1617 }
1618
1619 attr = XML_RSC_OP_T_QUEUE;
1620 value = crm_element_value(xml_op, attr);
1621 if (value) {
1622 print_nvpair(stream, attr, value, "ms", 0);
1623 }
1624 }
1625
1626 /* End the operation description */
1627 switch (output_format) {
1628 case mon_output_plain:
1629 case mon_output_console:
1630 print_as(" rc=%d (%s)\n", rc, services_ocf_exitcode_str(rc));
1631 break;
1632
1633 case mon_output_html:
1634 case mon_output_cgi:
1635 fprintf(stream, " rc=%d (%s)</li>\n", rc, services_ocf_exitcode_str(rc));
1636 break;
1637
1638 case mon_output_xml:
1639 fprintf(stream, " rc=\"%d\" rc_text=\"%s\" />\n", rc, services_ocf_exitcode_str(rc));
1640 break;
1641
1642 default:
1643 break;
1644 }
1645 }
1646
1647 /*!
1648 * \internal
1649 * \brief Print resource operation/failure history
1650 *
1651 * \param[in] stream File stream to display output to
1652 * \param[in] data_set Current state of CIB
1653 * \param[in] node Node that ran this resource
1654 * \param[in] rsc_entry Root of XML tree describing resource status
1655 * \param[in] operations Whether to print operations or just failcounts
1656 */
1657 static void
print_rsc_history(FILE * stream,pe_working_set_t * data_set,node_t * node,xmlNode * rsc_entry,gboolean operations)1658 print_rsc_history(FILE *stream, pe_working_set_t *data_set, node_t *node,
1659 xmlNode *rsc_entry, gboolean operations)
1660 {
1661 GListPtr gIter = NULL;
1662 GListPtr op_list = NULL;
1663 gboolean printed = FALSE;
1664 const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
1665 resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
1666 xmlNode *rsc_op = NULL;
1667
1668 /* If we're not showing operations, just print the resource failure summary */
1669 if (operations == FALSE) {
1670 print_rsc_history_start(stream, data_set, node, rsc, rsc_id, FALSE);
1671 print_rsc_history_end(stream);
1672 return;
1673 }
1674
1675 /* Create a list of this resource's operations */
1676 for (rsc_op = __xml_first_child_element(rsc_entry); rsc_op != NULL;
1677 rsc_op = __xml_next_element(rsc_op)) {
1678 if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) {
1679 op_list = g_list_append(op_list, rsc_op);
1680 }
1681 }
1682 op_list = g_list_sort(op_list, sort_op_by_callid);
1683
1684 /* Print each operation */
1685 for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
1686 xmlNode *xml_op = (xmlNode *) gIter->data;
1687 const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
1688 const char *interval = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
1689 const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
1690 int rc = crm_parse_int(op_rc, "0");
1691
1692 /* Display 0-interval monitors as "probe" */
1693 if (safe_str_eq(task, CRMD_ACTION_STATUS) && safe_str_eq(interval, "0")) {
1694 task = "probe";
1695 }
1696
1697 /* Ignore notifies and some probes */
1698 if (safe_str_eq(task, CRMD_ACTION_NOTIFY) || (safe_str_eq(task, "probe") && (rc == 7))) {
1699 continue;
1700 }
1701
1702 /* If this is the first printed operation, print heading for resource */
1703 if (printed == FALSE) {
1704 printed = TRUE;
1705 print_rsc_history_start(stream, data_set, node, rsc, rsc_id, TRUE);
1706 }
1707
1708 /* Print the operation */
1709 print_op_history(stream, data_set, node, xml_op, task, interval, rc);
1710 }
1711
1712 /* Free the list we created (no need to free the individual items) */
1713 g_list_free(op_list);
1714
1715 /* If we printed anything, close the resource */
1716 if (printed) {
1717 print_rsc_history_end(stream);
1718 }
1719 }
1720
1721 /*!
1722 * \internal
1723 * \brief Print node operation/failure history
1724 *
1725 * \param[in] stream File stream to display output to
1726 * \param[in] data_set Current state of CIB
1727 * \param[in] node_state Root of XML tree describing node status
1728 * \param[in] operations Whether to print operations or just failcounts
1729 */
1730 static void
print_node_history(FILE * stream,pe_working_set_t * data_set,xmlNode * node_state,gboolean operations)1731 print_node_history(FILE *stream, pe_working_set_t *data_set,
1732 xmlNode *node_state, gboolean operations)
1733 {
1734 node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
1735 xmlNode *lrm_rsc = NULL;
1736 xmlNode *rsc_entry = NULL;
1737
1738 if (node && node->details && node->details->online) {
1739 print_node_start(stream, node);
1740
1741 lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
1742 lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
1743
1744 /* Print history of each of the node's resources */
1745 for (rsc_entry = __xml_first_child_element(lrm_rsc); rsc_entry != NULL;
1746 rsc_entry = __xml_next_element(rsc_entry)) {
1747
1748 if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) {
1749 print_rsc_history(stream, data_set, node, rsc_entry, operations);
1750 }
1751 }
1752
1753 print_node_end(stream);
1754 }
1755 }
1756
1757 /*!
1758 * \internal
1759 * \brief Print extended information about an attribute if appropriate
1760 *
1761 * \param[in] data_set Working set of CIB state
1762 *
1763 * \return TRUE if extended information was printed, FALSE otherwise
1764 * \note Currently, extended information is only supported for ping/pingd
1765 * resources, for which a message will be printed if connectivity is lost
1766 * or degraded.
1767 */
1768 static gboolean
print_attr_msg(FILE * stream,node_t * node,GListPtr rsc_list,const char * attrname,const char * attrvalue)1769 print_attr_msg(FILE *stream, node_t * node, GListPtr rsc_list, const char *attrname, const char *attrvalue)
1770 {
1771 GListPtr gIter = NULL;
1772
1773 for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
1774 resource_t *rsc = (resource_t *) gIter->data;
1775 const char *type = g_hash_table_lookup(rsc->meta, "type");
1776
1777 if (rsc->children != NULL) {
1778 if (print_attr_msg(stream, node, rsc->children, attrname, attrvalue)) {
1779 return TRUE;
1780 }
1781 }
1782
1783 if (safe_str_eq(type, "ping") || safe_str_eq(type, "pingd")) {
1784 const char *name = g_hash_table_lookup(rsc->parameters, "name");
1785
1786 if (name == NULL) {
1787 name = "pingd";
1788 }
1789
1790 /* To identify the resource with the attribute name. */
1791 if (safe_str_eq(name, attrname)) {
1792 int host_list_num = 0;
1793 int expected_score = 0;
1794 int value = crm_parse_int(attrvalue, "0");
1795 const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list");
1796 const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier");
1797
1798 if(hosts) {
1799 char **host_list = g_strsplit(hosts, " ", 0);
1800 host_list_num = g_strv_length(host_list);
1801 g_strfreev(host_list);
1802 }
1803
1804 /* pingd multiplier is the same as the default value. */
1805 expected_score = host_list_num * crm_parse_int(multiplier, "1");
1806
1807 switch (output_format) {
1808 case mon_output_plain:
1809 case mon_output_console:
1810 if (value <= 0) {
1811 print_as("\t: Connectivity is lost");
1812 } else if (value < expected_score) {
1813 print_as("\t: Connectivity is degraded (Expected=%d)", expected_score);
1814 }
1815 break;
1816
1817 case mon_output_html:
1818 case mon_output_cgi:
1819 if (value <= 0) {
1820 fprintf(stream, " <b>(connectivity is lost)</b>");
1821 } else if (value < expected_score) {
1822 fprintf(stream, " <b>(connectivity is degraded -- expected %d)</b>",
1823 expected_score);
1824 }
1825 break;
1826
1827 case mon_output_xml:
1828 fprintf(stream, " expected=\"%d\"", expected_score);
1829 break;
1830
1831 default:
1832 break;
1833 }
1834 return TRUE;
1835 }
1836 }
1837 }
1838 return FALSE;
1839 }
1840
1841 static int
compare_attribute(gconstpointer a,gconstpointer b)1842 compare_attribute(gconstpointer a, gconstpointer b)
1843 {
1844 int rc;
1845
1846 rc = strcmp((const char *)a, (const char *)b);
1847
1848 return rc;
1849 }
1850
1851 static void
create_attr_list(gpointer name,gpointer value,gpointer data)1852 create_attr_list(gpointer name, gpointer value, gpointer data)
1853 {
1854 int i;
1855 const char *filt_str[] = FILTER_STR;
1856
1857 CRM_CHECK(name != NULL, return);
1858
1859 /* filtering automatic attributes */
1860 for (i = 0; filt_str[i] != NULL; i++) {
1861 if (g_str_has_prefix(name, filt_str[i])) {
1862 return;
1863 }
1864 }
1865
1866 attr_list = g_list_insert_sorted(attr_list, name, compare_attribute);
1867 }
1868
1869 /* structure for passing multiple user data to g_list_foreach() */
1870 struct mon_attr_data {
1871 FILE *stream;
1872 node_t *node;
1873 };
1874
1875 static void
print_node_attribute(gpointer name,gpointer user_data)1876 print_node_attribute(gpointer name, gpointer user_data)
1877 {
1878 const char *value = NULL;
1879 struct mon_attr_data *data = (struct mon_attr_data *) user_data;
1880
1881 value = pe_node_attribute_raw(data->node, name);
1882
1883 /* Print attribute name and value */
1884 switch (output_format) {
1885 case mon_output_plain:
1886 case mon_output_console:
1887 print_as(" + %-32s\t: %-10s", (char *)name, value);
1888 break;
1889
1890 case mon_output_html:
1891 case mon_output_cgi:
1892 fprintf(data->stream, " <li>%s: %s",
1893 (char *)name, value);
1894 break;
1895
1896 case mon_output_xml:
1897 fprintf(data->stream,
1898 " <attribute name=\"%s\" value=\"%s\"",
1899 (char *)name, value);
1900 break;
1901
1902 default:
1903 break;
1904 }
1905
1906 /* Print extended information if appropriate */
1907 print_attr_msg(data->stream, data->node, data->node->details->running_rsc,
1908 name, value);
1909
1910 /* Close out the attribute */
1911 switch (output_format) {
1912 case mon_output_plain:
1913 case mon_output_console:
1914 print_as("\n");
1915 break;
1916
1917 case mon_output_html:
1918 case mon_output_cgi:
1919 fprintf(data->stream, "</li>\n");
1920 break;
1921
1922 case mon_output_xml:
1923 fprintf(data->stream, " />\n");
1924 break;
1925
1926 default:
1927 break;
1928 }
1929 }
1930
1931 static void
print_node_summary(FILE * stream,pe_working_set_t * data_set,gboolean operations)1932 print_node_summary(FILE *stream, pe_working_set_t * data_set, gboolean operations)
1933 {
1934 xmlNode *node_state = NULL;
1935 xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
1936
1937 /* Print heading */
1938 switch (output_format) {
1939 case mon_output_plain:
1940 case mon_output_console:
1941 if (operations) {
1942 print_as("\nOperations:\n");
1943 } else {
1944 print_as("\nMigration Summary:\n");
1945 }
1946 break;
1947
1948 case mon_output_html:
1949 case mon_output_cgi:
1950 if (operations) {
1951 fprintf(stream, " <hr />\n <h2>Operations</h2>\n");
1952 } else {
1953 fprintf(stream, " <hr />\n <h2>Migration Summary</h2>\n");
1954 }
1955 break;
1956
1957 case mon_output_xml:
1958 fprintf(stream, " <node_history>\n");
1959 break;
1960
1961 default:
1962 break;
1963 }
1964
1965 /* Print each node in the CIB status */
1966 for (node_state = __xml_first_child_element(cib_status); node_state != NULL;
1967 node_state = __xml_next_element(node_state)) {
1968 if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) {
1969 print_node_history(stream, data_set, node_state, operations);
1970 }
1971 }
1972
1973 /* Close section */
1974 switch (output_format) {
1975 case mon_output_xml:
1976 fprintf(stream, " </node_history>\n");
1977 break;
1978
1979 default:
1980 break;
1981 }
1982 }
1983
1984 static void
print_ticket(gpointer name,gpointer value,gpointer data)1985 print_ticket(gpointer name, gpointer value, gpointer data)
1986 {
1987 ticket_t *ticket = (ticket_t *) value;
1988 FILE *stream = (FILE *) data;
1989
1990 switch (output_format) {
1991 case mon_output_plain:
1992 case mon_output_console:
1993 print_as("* %s:\t%s%s", ticket->id,
1994 (ticket->granted? "granted" : "revoked"),
1995 (ticket->standby? " [standby]" : ""));
1996 break;
1997
1998 case mon_output_html:
1999 case mon_output_cgi:
2000 fprintf(stream, " <li>%s: %s%s", ticket->id,
2001 (ticket->granted? "granted" : "revoked"),
2002 (ticket->standby? " [standby]" : ""));
2003 break;
2004
2005 case mon_output_xml:
2006 fprintf(stream, " <ticket id=\"%s\" status=\"%s\" standby=\"%s\"",
2007 ticket->id, (ticket->granted? "granted" : "revoked"),
2008 (ticket->standby? "true" : "false"));
2009 break;
2010
2011 default:
2012 break;
2013 }
2014 if (ticket->last_granted > -1) {
2015 print_nvpair(stdout, "last-granted", NULL, NULL, ticket->last_granted);
2016 }
2017 switch (output_format) {
2018 case mon_output_plain:
2019 case mon_output_console:
2020 print_as("\n");
2021 break;
2022
2023 case mon_output_html:
2024 case mon_output_cgi:
2025 fprintf(stream, "</li>\n");
2026 break;
2027
2028 case mon_output_xml:
2029 fprintf(stream, " />\n");
2030 break;
2031
2032 default:
2033 break;
2034 }
2035 }
2036
2037 static void
print_cluster_tickets(FILE * stream,pe_working_set_t * data_set)2038 print_cluster_tickets(FILE *stream, pe_working_set_t * data_set)
2039 {
2040 /* Print section heading */
2041 switch (output_format) {
2042 case mon_output_plain:
2043 case mon_output_console:
2044 print_as("\nTickets:\n");
2045 break;
2046
2047 case mon_output_html:
2048 case mon_output_cgi:
2049 fprintf(stream, " <hr />\n <h2>Tickets</h2>\n <ul>\n");
2050 break;
2051
2052 case mon_output_xml:
2053 fprintf(stream, " <tickets>\n");
2054 break;
2055
2056 default:
2057 break;
2058 }
2059
2060 /* Print each ticket */
2061 g_hash_table_foreach(data_set->tickets, print_ticket, stream);
2062
2063 /* Close section */
2064 switch (output_format) {
2065 case mon_output_html:
2066 case mon_output_cgi:
2067 fprintf(stream, " </ul>\n");
2068 break;
2069
2070 case mon_output_xml:
2071 fprintf(stream, " </tickets>\n");
2072 break;
2073
2074 default:
2075 break;
2076 }
2077 }
2078
2079 /*!
2080 * \internal
2081 * \brief Return human-friendly string representing node name
2082 *
2083 * The returned string will be in the format
2084 * uname[@hostUname] [(nodeID)]
2085 * "@hostUname" will be printed if the node is a guest node.
2086 * "(nodeID)" will be printed if the node ID is different from the node uname,
2087 * and detailed output has been requested.
2088 *
2089 * \param[in] node Node to represent
2090 * \return Newly allocated string with representation of node name
2091 * \note It is the caller's responsibility to free the result with free().
2092 */
2093 static char *
get_node_display_name(node_t * node)2094 get_node_display_name(node_t *node)
2095 {
2096 char *node_name;
2097 const char *node_host = NULL;
2098 const char *node_id = NULL;
2099 int name_len;
2100
2101 CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
2102
2103 /* Host is displayed only if this is a guest node */
2104 if (is_container_remote_node(node)) {
2105 node_t *host_node = pe__current_node(node->details->remote_rsc);
2106
2107 if (host_node && host_node->details) {
2108 node_host = host_node->details->uname;
2109 }
2110 if (node_host == NULL) {
2111 node_host = ""; /* so we at least get "uname@" to indicate guest */
2112 }
2113 }
2114
2115 /* Node ID is displayed if different from uname and detail is requested */
2116 if (print_clone_detail && safe_str_neq(node->details->uname, node->details->id)) {
2117 node_id = node->details->id;
2118 }
2119
2120 /* Determine name length */
2121 name_len = strlen(node->details->uname) + 1;
2122 if (node_host) {
2123 name_len += strlen(node_host) + 1; /* "@node_host" */
2124 }
2125 if (node_id) {
2126 name_len += strlen(node_id) + 3; /* + " (node_id)" */
2127 }
2128
2129 /* Allocate and populate display name */
2130 node_name = malloc(name_len);
2131 CRM_ASSERT(node_name != NULL);
2132 strcpy(node_name, node->details->uname);
2133 if (node_host) {
2134 strcat(node_name, "@");
2135 strcat(node_name, node_host);
2136 }
2137 if (node_id) {
2138 strcat(node_name, " (");
2139 strcat(node_name, node_id);
2140 strcat(node_name, ")");
2141 }
2142 return node_name;
2143 }
2144
2145 /*!
2146 * \internal
2147 * \brief Print a negative location constraint
2148 *
2149 * \param[in] stream File stream to display output to
2150 * \param[in] node Node affected by constraint
2151 * \param[in] location Constraint to print
2152 */
2153 static void
print_ban(FILE * stream,pe_node_t * node,pe__location_t * location)2154 print_ban(FILE *stream, pe_node_t *node, pe__location_t *location)
2155 {
2156 char *node_name = NULL;
2157
2158 switch (output_format) {
2159 case mon_output_plain:
2160 case mon_output_console:
2161 node_name = get_node_display_name(node);
2162 print_as(" %s\tprevents %s from running %son %s\n",
2163 location->id, location->rsc_lh->id,
2164 ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
2165 node_name);
2166 break;
2167
2168 case mon_output_html:
2169 case mon_output_cgi:
2170 node_name = get_node_display_name(node);
2171 fprintf(stream, " <li>%s prevents %s from running %son %s</li>\n",
2172 location->id, location->rsc_lh->id,
2173 ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
2174 node_name);
2175 break;
2176
2177 case mon_output_xml:
2178 fprintf(stream,
2179 " <ban id=\"%s\" resource=\"%s\" node=\"%s\" weight=\"%d\" master_only=\"%s\" />\n",
2180 location->id, location->rsc_lh->id, node->details->uname, node->weight,
2181 ((location->role_filter == RSC_ROLE_MASTER)? "true" : "false"));
2182 break;
2183
2184 default:
2185 break;
2186 }
2187 free(node_name);
2188 }
2189
2190 /*!
2191 * \internal
2192 * \brief Print section for negative location constraints
2193 *
2194 * \param[in] stream File stream to display output to
2195 * \param[in] data_set Working set corresponding to CIB status to display
2196 */
print_neg_locations(FILE * stream,pe_working_set_t * data_set)2197 static void print_neg_locations(FILE *stream, pe_working_set_t *data_set)
2198 {
2199 GListPtr gIter, gIter2;
2200
2201 /* Print section heading */
2202 switch (output_format) {
2203 case mon_output_plain:
2204 case mon_output_console:
2205 print_as("\nNegative Location Constraints:\n");
2206 break;
2207
2208 case mon_output_html:
2209 case mon_output_cgi:
2210 fprintf(stream, " <hr />\n <h2>Negative Location Constraints</h2>\n <ul>\n");
2211 break;
2212
2213 case mon_output_xml:
2214 fprintf(stream, " <bans>\n");
2215 break;
2216
2217 default:
2218 break;
2219 }
2220
2221 /* Print each ban */
2222 for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
2223 pe__location_t *location = gIter->data;
2224 if (!g_str_has_prefix(location->id, print_neg_location_prefix))
2225 continue;
2226 for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
2227 node_t *node = (node_t *) gIter2->data;
2228
2229 if (node->weight < 0) {
2230 print_ban(stream, node, location);
2231 }
2232 }
2233 }
2234
2235 /* Close section */
2236 switch (output_format) {
2237 case mon_output_cgi:
2238 case mon_output_html:
2239 fprintf(stream, " </ul>\n");
2240 break;
2241
2242 case mon_output_xml:
2243 fprintf(stream, " </bans>\n");
2244 break;
2245
2246 default:
2247 break;
2248 }
2249 }
2250
2251 static void
crm_mon_get_parameters(resource_t * rsc,pe_working_set_t * data_set)2252 crm_mon_get_parameters(resource_t *rsc, pe_working_set_t * data_set)
2253 {
2254 get_rsc_attributes(rsc->parameters, rsc, NULL, data_set);
2255 crm_trace("Beekhof: unpacked params for %s (%d)", rsc->id, g_hash_table_size(rsc->parameters));
2256 if(rsc->children) {
2257 GListPtr gIter = NULL;
2258
2259 for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
2260 crm_mon_get_parameters(gIter->data, data_set);
2261 }
2262 }
2263 }
2264
2265 /*!
2266 * \internal
2267 * \brief Print node attributes section
2268 *
2269 * \param[in] stream File stream to display output to
2270 * \param[in] data_set Working set of CIB state
2271 */
2272 static void
print_node_attributes(FILE * stream,pe_working_set_t * data_set)2273 print_node_attributes(FILE *stream, pe_working_set_t *data_set)
2274 {
2275 GListPtr gIter = NULL;
2276
2277 /* Print section heading */
2278 switch (output_format) {
2279 case mon_output_plain:
2280 case mon_output_console:
2281 print_as("\nNode Attributes:\n");
2282 break;
2283
2284 case mon_output_html:
2285 case mon_output_cgi:
2286 fprintf(stream, " <hr />\n <h2>Node Attributes</h2>\n");
2287 break;
2288
2289 case mon_output_xml:
2290 fprintf(stream, " <node_attributes>\n");
2291 break;
2292
2293 default:
2294 break;
2295 }
2296
2297 /* Unpack all resource parameters (it would be more efficient to do this
2298 * only when needed for the first time in print_attr_msg())
2299 */
2300 for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
2301 crm_mon_get_parameters(gIter->data, data_set);
2302 }
2303
2304 /* Display each node's attributes */
2305 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
2306 struct mon_attr_data data;
2307
2308 data.stream = stream;
2309 data.node = (node_t *) gIter->data;
2310
2311 if (data.node && data.node->details && data.node->details->online) {
2312 print_node_start(stream, data.node);
2313 g_hash_table_foreach(data.node->details->attrs, create_attr_list, NULL);
2314 g_list_foreach(attr_list, print_node_attribute, &data);
2315 g_list_free(attr_list);
2316 attr_list = NULL;
2317 print_node_end(stream);
2318 }
2319 }
2320
2321 /* Print section footer */
2322 switch (output_format) {
2323 case mon_output_xml:
2324 fprintf(stream, " </node_attributes>\n");
2325 break;
2326
2327 default:
2328 break;
2329 }
2330 }
2331
2332 /*!
2333 * \internal
2334 * \brief Return resource display options corresponding to command-line choices
2335 *
2336 * \return Bitmask of pe_print_options suitable for resource print functions
2337 */
2338 static int
get_resource_display_options(void)2339 get_resource_display_options(void)
2340 {
2341 int print_opts;
2342
2343 /* Determine basic output format */
2344 switch (output_format) {
2345 case mon_output_console:
2346 print_opts = pe_print_ncurses;
2347 break;
2348 case mon_output_html:
2349 case mon_output_cgi:
2350 print_opts = pe_print_html;
2351 break;
2352 case mon_output_xml:
2353 print_opts = pe_print_xml;
2354 break;
2355 default:
2356 print_opts = pe_print_printf;
2357 break;
2358 }
2359
2360 /* Add optional display elements */
2361 if (print_pending) {
2362 print_opts |= pe_print_pending;
2363 }
2364 if (print_clone_detail) {
2365 print_opts |= pe_print_clone_details|pe_print_implicit;
2366 }
2367 if (!inactive_resources) {
2368 print_opts |= pe_print_clone_active;
2369 }
2370 if (print_brief) {
2371 print_opts |= pe_print_brief;
2372 }
2373 return print_opts;
2374 }
2375
2376 /*!
2377 * \internal
2378 * \brief Return human-friendly string representing current time
2379 *
2380 * \return Current time as string (as by ctime() but without newline) on success
2381 * or "Could not determine current time" on error
2382 * \note The return value points to a statically allocated string which might be
2383 * overwritten by subsequent calls to any of the C library date and time functions.
2384 */
2385 static const char *
crm_now_string(void)2386 crm_now_string(void)
2387 {
2388 time_t a_time = time(NULL);
2389 char *since_epoch = ctime(&a_time);
2390
2391 if ((a_time == (time_t) -1) || (since_epoch == NULL)) {
2392 return "Could not determine current time";
2393 }
2394 since_epoch[strlen(since_epoch) - 1] = EOS; /* trim newline */
2395 return (since_epoch);
2396 }
2397
2398 /*!
2399 * \internal
2400 * \brief Print header for cluster summary if needed
2401 *
2402 * \param[in] stream File stream to display output to
2403 */
2404 static void
print_cluster_summary_header(FILE * stream)2405 print_cluster_summary_header(FILE *stream)
2406 {
2407 switch (output_format) {
2408 case mon_output_html:
2409 case mon_output_cgi:
2410 fprintf(stream, " <h2>Cluster Summary</h2>\n <p>\n");
2411 break;
2412
2413 case mon_output_xml:
2414 fprintf(stream, " <summary>\n");
2415 break;
2416
2417 default:
2418 break;
2419 }
2420 }
2421
2422 /*!
2423 * \internal
2424 * \brief Print footer for cluster summary if needed
2425 *
2426 * \param[in] stream File stream to display output to
2427 */
2428 static void
print_cluster_summary_footer(FILE * stream)2429 print_cluster_summary_footer(FILE *stream)
2430 {
2431 switch (output_format) {
2432 case mon_output_cgi:
2433 case mon_output_html:
2434 fprintf(stream, " </p>\n");
2435 break;
2436
2437 case mon_output_xml:
2438 fprintf(stream, " </summary>\n");
2439 break;
2440
2441 default:
2442 break;
2443 }
2444 }
2445
2446 /*!
2447 * \internal
2448 * \brief Print times the display was last updated and CIB last changed
2449 *
2450 * \param[in] stream File stream to display output to
2451 * \param[in] data_set Working set of CIB state
2452 */
2453 static void
print_cluster_times(FILE * stream,pe_working_set_t * data_set)2454 print_cluster_times(FILE *stream, pe_working_set_t *data_set)
2455 {
2456 const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
2457 const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
2458 const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
2459 const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
2460
2461 switch (output_format) {
2462 case mon_output_plain:
2463 case mon_output_console:
2464 print_as("Last updated: %s", crm_now_string());
2465 print_as((user || client || origin)? "\n" : "\t\t");
2466 print_as("Last change: %s", last_written ? last_written : "");
2467 if (user) {
2468 print_as(" by %s", user);
2469 }
2470 if (client) {
2471 print_as(" via %s", client);
2472 }
2473 if (origin) {
2474 print_as(" on %s", origin);
2475 }
2476 print_as("\n");
2477 break;
2478
2479 case mon_output_html:
2480 case mon_output_cgi:
2481 fprintf(stream, " <b>Last updated:</b> %s<br/>\n", crm_now_string());
2482 fprintf(stream, " <b>Last change:</b> %s", last_written ? last_written : "");
2483 if (user) {
2484 fprintf(stream, " by %s", user);
2485 }
2486 if (client) {
2487 fprintf(stream, " via %s", client);
2488 }
2489 if (origin) {
2490 fprintf(stream, " on %s", origin);
2491 }
2492 fprintf(stream, "<br/>\n");
2493 break;
2494
2495 case mon_output_xml:
2496 fprintf(stream, " <last_update time=\"%s\" />\n", crm_now_string());
2497 fprintf(stream, " <last_change time=\"%s\" user=\"%s\" client=\"%s\" origin=\"%s\" />\n",
2498 last_written ? last_written : "", user ? user : "",
2499 client ? client : "", origin ? origin : "");
2500 break;
2501
2502 default:
2503 break;
2504 }
2505 }
2506
2507 /*!
2508 * \internal
2509 * \brief Print cluster stack
2510 *
2511 * \param[in] stream File stream to display output to
2512 * \param[in] stack_s Stack name
2513 */
2514 static void
print_cluster_stack(FILE * stream,const char * stack_s)2515 print_cluster_stack(FILE *stream, const char *stack_s)
2516 {
2517 switch (output_format) {
2518 case mon_output_plain:
2519 case mon_output_console:
2520 print_as("Stack: %s\n", stack_s);
2521 break;
2522
2523 case mon_output_html:
2524 case mon_output_cgi:
2525 fprintf(stream, " <b>Stack:</b> %s<br/>\n", stack_s);
2526 break;
2527
2528 case mon_output_xml:
2529 fprintf(stream, " <stack type=\"%s\" />\n", stack_s);
2530 break;
2531
2532 default:
2533 break;
2534 }
2535 }
2536
2537 /*!
2538 * \internal
2539 * \brief Print current DC and its version
2540 *
2541 * \param[in] stream File stream to display output to
2542 * \param[in] data_set Working set of CIB state
2543 */
2544 static void
print_cluster_dc(FILE * stream,pe_working_set_t * data_set)2545 print_cluster_dc(FILE *stream, pe_working_set_t *data_set)
2546 {
2547 node_t *dc = data_set->dc_node;
2548 xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
2549 data_set->input, LOG_DEBUG);
2550 const char *dc_version_s = dc_version?
2551 crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
2552 : NULL;
2553 const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
2554 char *dc_name = dc? get_node_display_name(dc) : NULL;
2555
2556 switch (output_format) {
2557 case mon_output_plain:
2558 case mon_output_console:
2559 print_as("Current DC: ");
2560 if (dc) {
2561 print_as("%s (version %s) - partition %s quorum\n",
2562 dc_name, (dc_version_s? dc_version_s : "unknown"),
2563 (crm_is_true(quorum) ? "with" : "WITHOUT"));
2564 } else {
2565 print_as("NONE\n");
2566 }
2567 break;
2568
2569 case mon_output_html:
2570 case mon_output_cgi:
2571 fprintf(stream, " <b>Current DC:</b> ");
2572 if (dc) {
2573 fprintf(stream, "%s (version %s) - partition %s quorum",
2574 dc_name, (dc_version_s? dc_version_s : "unknown"),
2575 (crm_is_true(quorum)? "with" : "<font color=\"red\"><b>WITHOUT</b></font>"));
2576 } else {
2577 fprintf(stream, "<font color=\"red\"><b>NONE</b></font>");
2578 }
2579 fprintf(stream, "<br/>\n");
2580 break;
2581
2582 case mon_output_xml:
2583 fprintf(stream, " <current_dc ");
2584 if (dc) {
2585 fprintf(stream,
2586 "present=\"true\" version=\"%s\" name=\"%s\" id=\"%s\" with_quorum=\"%s\"",
2587 (dc_version_s? dc_version_s : ""), dc->details->uname, dc->details->id,
2588 (crm_is_true(quorum) ? "true" : "false"));
2589 } else {
2590 fprintf(stream, "present=\"false\"");
2591 }
2592 fprintf(stream, " />\n");
2593 break;
2594
2595 default:
2596 break;
2597 }
2598 free(dc_name);
2599 }
2600
2601 /*!
2602 * \internal
2603 * \brief Print counts of configured nodes and resources
2604 *
2605 * \param[in] stream File stream to display output to
2606 * \param[in] data_set Working set of CIB state
2607 * \param[in] stack_s Stack name
2608 */
2609 static void
print_cluster_counts(FILE * stream,pe_working_set_t * data_set,const char * stack_s)2610 print_cluster_counts(FILE *stream, pe_working_set_t *data_set, const char *stack_s)
2611 {
2612 int nnodes = g_list_length(data_set->nodes);
2613 xmlNode *quorum_node = get_xpath_object("//nvpair[@name='" XML_ATTR_EXPECTED_VOTES "']",
2614 data_set->input, LOG_DEBUG);
2615 const char *quorum_votes = quorum_node?
2616 crm_element_value(quorum_node, XML_NVPAIR_ATTR_VALUE)
2617 : "unknown";
2618
2619 switch (output_format) {
2620 case mon_output_plain:
2621 case mon_output_console:
2622
2623 print_as("\n%d node%s configured", nnodes, s_if_plural(nnodes));
2624 if (stack_s && strstr(stack_s, "classic openais") != NULL) {
2625 print_as(" (%s expected votes)", quorum_votes);
2626 }
2627 print_as("\n");
2628
2629 print_as("%d resource instance%s configured",
2630 data_set->ninstances, s_if_plural(data_set->ninstances));
2631 if(data_set->disabled_resources || data_set->blocked_resources) {
2632 print_as(" (");
2633 if (data_set->disabled_resources) {
2634 print_as("%d DISABLED", data_set->disabled_resources);
2635 }
2636 if (data_set->disabled_resources && data_set->blocked_resources) {
2637 print_as(", ");
2638 }
2639 if (data_set->blocked_resources) {
2640 print_as("%d BLOCKED from further action due to failure",
2641 data_set->blocked_resources);
2642 }
2643 print_as(")");
2644 }
2645 print_as("\n");
2646
2647 break;
2648
2649 case mon_output_html:
2650 case mon_output_cgi:
2651
2652 fprintf(stream, " %d node%s configured", nnodes, s_if_plural(nnodes));
2653 if (stack_s && strstr(stack_s, "classic openais") != NULL) {
2654 fprintf(stream, " (%s expected votes)", quorum_votes);
2655 }
2656 fprintf(stream, "<br/>\n");
2657
2658 fprintf(stream, " %d resource instance%s configured",
2659 data_set->ninstances, s_if_plural(data_set->ninstances));
2660 if (data_set->disabled_resources || data_set->blocked_resources) {
2661 fprintf(stream, " (");
2662 if (data_set->disabled_resources) {
2663 fprintf(stream, "%d <strong>DISABLED</strong>",
2664 data_set->disabled_resources);
2665 }
2666 if (data_set->disabled_resources && data_set->blocked_resources) {
2667 fprintf(stream, ", ");
2668 }
2669 if (data_set->blocked_resources) {
2670 fprintf(stream,
2671 "%d <strong>BLOCKED</strong> from further action due to failure",
2672 data_set->blocked_resources);
2673 }
2674 fprintf(stream, ")");
2675 }
2676 fprintf(stream, "<br/>\n");
2677 break;
2678
2679 case mon_output_xml:
2680 fprintf(stream,
2681 " <nodes_configured number=\"%d\" expected_votes=\"%s\" />\n",
2682 g_list_length(data_set->nodes), quorum_votes);
2683 fprintf(stream,
2684 " <resources_configured number=\"%d\" disabled=\"%d\" blocked=\"%d\" />\n",
2685 data_set->ninstances,
2686 data_set->disabled_resources, data_set->blocked_resources);
2687 break;
2688
2689 default:
2690 break;
2691 }
2692 }
2693
2694 /*!
2695 * \internal
2696 * \brief Print cluster-wide options
2697 *
2698 * \param[in] stream File stream to display output to
2699 * \param[in] data_set Working set of CIB state
2700 *
2701 * \note Currently this is only implemented for HTML and XML output, and
2702 * prints only a few options. If there is demand, more could be added.
2703 */
2704 static void
print_cluster_options(FILE * stream,pe_working_set_t * data_set)2705 print_cluster_options(FILE *stream, pe_working_set_t *data_set)
2706 {
2707 switch (output_format) {
2708 case mon_output_plain:
2709 case mon_output_console:
2710 if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
2711 print_as("\n *** Resource management is DISABLED ***");
2712 print_as("\n The cluster will not attempt to start, stop or recover services");
2713 print_as("\n");
2714 }
2715 break;
2716
2717 case mon_output_html:
2718 fprintf(stream, " </p>\n <h3>Config Options</h3>\n");
2719 fprintf(stream, " <table>\n");
2720 fprintf(stream, " <tr><th>STONITH of failed nodes</th><td>%s</td></tr>\n",
2721 is_set(data_set->flags, pe_flag_stonith_enabled)? "enabled" : "disabled");
2722
2723 fprintf(stream, " <tr><th>Cluster is</th><td>%ssymmetric</td></tr>\n",
2724 is_set(data_set->flags, pe_flag_symmetric_cluster)? "" : "a");
2725
2726 fprintf(stream, " <tr><th>No Quorum Policy</th><td>");
2727 switch (data_set->no_quorum_policy) {
2728 case no_quorum_freeze:
2729 fprintf(stream, "Freeze resources");
2730 break;
2731 case no_quorum_stop:
2732 fprintf(stream, "Stop ALL resources");
2733 break;
2734 case no_quorum_ignore:
2735 fprintf(stream, "Ignore");
2736 break;
2737 case no_quorum_suicide:
2738 fprintf(stream, "Suicide");
2739 break;
2740 }
2741 fprintf(stream, "</td></tr>\n");
2742
2743 fprintf(stream, " <tr><th>Resource management</th><td>");
2744 if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
2745 fprintf(stream, "<strong>DISABLED</strong> (the cluster will "
2746 "not attempt to start, stop or recover services)");
2747 } else {
2748 fprintf(stream, "enabled");
2749 }
2750 fprintf(stream, "</td></tr>\n");
2751
2752 fprintf(stream, "</table>\n <p>\n");
2753 break;
2754
2755 case mon_output_xml:
2756 fprintf(stream, " <cluster_options");
2757 fprintf(stream, " stonith-enabled=\"%s\"",
2758 is_set(data_set->flags, pe_flag_stonith_enabled)?
2759 "true" : "false");
2760 fprintf(stream, " symmetric-cluster=\"%s\"",
2761 is_set(data_set->flags, pe_flag_symmetric_cluster)?
2762 "true" : "false");
2763 fprintf(stream, " no-quorum-policy=\"");
2764 switch (data_set->no_quorum_policy) {
2765 case no_quorum_freeze:
2766 fprintf(stream, "freeze");
2767 break;
2768 case no_quorum_stop:
2769 fprintf(stream, "stop");
2770 break;
2771 case no_quorum_ignore:
2772 fprintf(stream, "ignore");
2773 break;
2774 case no_quorum_suicide:
2775 fprintf(stream, "suicide");
2776 break;
2777 }
2778 fprintf(stream, "\"");
2779 fprintf(stream, " maintenance-mode=\"%s\"",
2780 is_set(data_set->flags, pe_flag_maintenance_mode)?
2781 "true" : "false");
2782 fprintf(stream, " />\n");
2783 break;
2784
2785 default:
2786 break;
2787 }
2788 }
2789
2790 /*!
2791 * \internal
2792 * \brief Get the name of the stack in use (or "unknown" if not available)
2793 *
2794 * \param[in] data_set Working set of CIB state
2795 *
2796 * \return String representing stack name
2797 */
2798 static const char *
get_cluster_stack(pe_working_set_t * data_set)2799 get_cluster_stack(pe_working_set_t *data_set)
2800 {
2801 xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
2802 data_set->input, LOG_DEBUG);
2803 return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
2804 }
2805
2806 /*!
2807 * \internal
2808 * \brief Print a summary of cluster-wide information
2809 *
2810 * \param[in] stream File stream to display output to
2811 * \param[in] data_set Working set of CIB state
2812 */
2813 static void
print_cluster_summary(FILE * stream,pe_working_set_t * data_set)2814 print_cluster_summary(FILE *stream, pe_working_set_t *data_set)
2815 {
2816 const char *stack_s = get_cluster_stack(data_set);
2817 gboolean header_printed = FALSE;
2818
2819 if (show & mon_show_stack) {
2820 if (header_printed == FALSE) {
2821 print_cluster_summary_header(stream);
2822 header_printed = TRUE;
2823 }
2824 print_cluster_stack(stream, stack_s);
2825 }
2826
2827 /* Always print DC if none, even if not requested */
2828 if ((data_set->dc_node == NULL) || (show & mon_show_dc)) {
2829 if (header_printed == FALSE) {
2830 print_cluster_summary_header(stream);
2831 header_printed = TRUE;
2832 }
2833 print_cluster_dc(stream, data_set);
2834 }
2835
2836 if (show & mon_show_times) {
2837 if (header_printed == FALSE) {
2838 print_cluster_summary_header(stream);
2839 header_printed = TRUE;
2840 }
2841 print_cluster_times(stream, data_set);
2842 }
2843
2844 if (is_set(data_set->flags, pe_flag_maintenance_mode)
2845 || data_set->disabled_resources
2846 || data_set->blocked_resources
2847 || is_set(show, mon_show_count)) {
2848 if (header_printed == FALSE) {
2849 print_cluster_summary_header(stream);
2850 header_printed = TRUE;
2851 }
2852 print_cluster_counts(stream, data_set, stack_s);
2853 }
2854
2855 /* There is not a separate option for showing cluster options, so show with
2856 * stack for now; a separate option could be added if there is demand
2857 */
2858 if (show & mon_show_stack) {
2859 print_cluster_options(stream, data_set);
2860 }
2861
2862 if (header_printed) {
2863 print_cluster_summary_footer(stream);
2864 }
2865 }
2866
2867 /*!
2868 * \internal
2869 * \brief Print a failed action
2870 *
2871 * \param[in] stream File stream to display output to
2872 * \param[in] xml_op Root of XML tree describing failed action
2873 */
2874 static void
print_failed_action(FILE * stream,xmlNode * xml_op)2875 print_failed_action(FILE *stream, xmlNode *xml_op)
2876 {
2877 const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
2878 const char *op_key_attr = "op_key";
2879 const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE);
2880 const char *node = crm_element_value(xml_op, XML_ATTR_UNAME);
2881 const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
2882 const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
2883 int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0");
2884 int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0");
2885 char *exit_reason_cleaned;
2886
2887 /* If no op_key was given, use id instead */
2888 if (op_key == NULL) {
2889 op_key = ID(xml_op);
2890 op_key_attr = "id";
2891 }
2892
2893 /* If no exit reason was given, use "none" */
2894 if (exit_reason == NULL) {
2895 exit_reason = "none";
2896 }
2897
2898 /* Print common action information */
2899 switch (output_format) {
2900 case mon_output_plain:
2901 case mon_output_console:
2902 print_as("* %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
2903 op_key, node, services_ocf_exitcode_str(rc), rc,
2904 call, services_lrm_status_str(status), exit_reason);
2905 break;
2906
2907 case mon_output_html:
2908 case mon_output_cgi:
2909 fprintf(stream, " <li>%s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
2910 op_key, node, services_ocf_exitcode_str(rc), rc,
2911 call, services_lrm_status_str(status), exit_reason);
2912 break;
2913
2914 case mon_output_xml:
2915 exit_reason_cleaned = crm_xml_escape(exit_reason);
2916 fprintf(stream, " <failure %s=\"%s\" node=\"%s\"",
2917 op_key_attr, op_key, node);
2918 fprintf(stream, " exitstatus=\"%s\" exitreason=\"%s\" exitcode=\"%d\"",
2919 services_ocf_exitcode_str(rc), exit_reason_cleaned, rc);
2920 fprintf(stream, " call=\"%s\" status=\"%s\"",
2921 call, services_lrm_status_str(status));
2922 free(exit_reason_cleaned);
2923 break;
2924
2925 default:
2926 break;
2927 }
2928
2929 /* If last change was given, print timing information as well */
2930 if (last) {
2931 time_t run_at = crm_parse_int(last, "0");
2932 char *run_at_s = ctime(&run_at);
2933
2934 if (run_at_s) {
2935 run_at_s[24] = 0; /* Overwrite the newline */
2936 }
2937
2938 switch (output_format) {
2939 case mon_output_plain:
2940 case mon_output_console:
2941 print_as(",\n last-rc-change='%s', queued=%sms, exec=%sms",
2942 run_at_s? run_at_s : "",
2943 crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2944 crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
2945 break;
2946
2947 case mon_output_html:
2948 case mon_output_cgi:
2949 fprintf(stream, " last-rc-change='%s', queued=%sms, exec=%sms",
2950 run_at_s? run_at_s : "",
2951 crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2952 crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
2953 break;
2954
2955 case mon_output_xml:
2956 fprintf(stream,
2957 " last-rc-change=\"%s\" queued=\"%s\" exec=\"%s\" interval=\"%d\" task=\"%s\"",
2958 run_at_s? run_at_s : "",
2959 crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2960 crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
2961 crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL), "0"),
2962 crm_element_value(xml_op, XML_LRM_ATTR_TASK));
2963 break;
2964
2965 default:
2966 break;
2967 }
2968 }
2969
2970 /* End the action listing */
2971 switch (output_format) {
2972 case mon_output_plain:
2973 case mon_output_console:
2974 print_as("\n");
2975 break;
2976
2977 case mon_output_html:
2978 case mon_output_cgi:
2979 fprintf(stream, "</li>\n");
2980 break;
2981
2982 case mon_output_xml:
2983 fprintf(stream, " />\n");
2984 break;
2985
2986 default:
2987 break;
2988 }
2989 }
2990
2991 /*!
2992 * \internal
2993 * \brief Print a section for failed actions
2994 *
2995 * \param[in] stream File stream to display output to
2996 * \param[in] data_set Working set of CIB state
2997 */
2998 static void
print_failed_actions(FILE * stream,pe_working_set_t * data_set)2999 print_failed_actions(FILE *stream, pe_working_set_t *data_set)
3000 {
3001 xmlNode *xml_op = NULL;
3002
3003 /* Print section heading */
3004 switch (output_format) {
3005 case mon_output_plain:
3006 case mon_output_console:
3007 print_as("\nFailed Resource Actions:\n");
3008 break;
3009
3010 case mon_output_html:
3011 case mon_output_cgi:
3012 fprintf(stream,
3013 " <hr />\n <h2>Failed Resource Actions</h2>\n <ul>\n");
3014 break;
3015
3016 case mon_output_xml:
3017 fprintf(stream, " <failures>\n");
3018 break;
3019
3020 default:
3021 break;
3022 }
3023
3024 /* Print each failed action */
3025 for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
3026 xml_op = __xml_next(xml_op)) {
3027 print_failed_action(stream, xml_op);
3028 }
3029
3030 /* End section */
3031 switch (output_format) {
3032 case mon_output_html:
3033 case mon_output_cgi:
3034 fprintf(stream, " </ul>\n");
3035 break;
3036
3037 case mon_output_xml:
3038 fprintf(stream, " </failures>\n");
3039 break;
3040
3041 default:
3042 break;
3043 }
3044 }
3045
3046 /*!
3047 * \internal
3048 * \brief Reduce the stonith-history
3049 * for successful actions we keep the last of every action-type & target
3050 * for failed actions we record as well who had failed
3051 * for actions in progress we keep full track
3052 *
3053 * \param[in] history List of stonith actions
3054 *
3055 */
3056 static stonith_history_t *
reduce_stonith_history(stonith_history_t * history)3057 reduce_stonith_history(stonith_history_t *history)
3058 {
3059 stonith_history_t *new = history, *hp, *np;
3060
3061 if (new) {
3062 hp = new->next;
3063 new->next = NULL;
3064
3065 while (hp) {
3066 stonith_history_t *hp_next = hp->next;
3067
3068 hp->next = NULL;
3069
3070 for (np = new; ; np = np->next) {
3071 if ((hp->state == st_done) || (hp->state == st_failed)) {
3072 /* action not in progress */
3073 if (safe_str_eq(hp->target, np->target) &&
3074 safe_str_eq(hp->action, np->action) &&
3075 (hp->state == np->state) &&
3076 ((hp->state == st_done) ||
3077 safe_str_eq(hp->delegate, np->delegate))) {
3078 /* purge older hp */
3079 stonith_history_free(hp);
3080 break;
3081 }
3082 }
3083
3084 if (!np->next) {
3085 np->next = hp;
3086 break;
3087 }
3088 }
3089 hp = hp_next;
3090 }
3091 }
3092
3093 return new;
3094 }
3095
3096 /*!
3097 * \internal
3098 * \brief Sort the stonith-history
3099 * sort by competed most current on the top
3100 * pending actions lacking a completed-stamp are gathered at the top
3101 *
3102 * \param[in] history List of stonith actions
3103 *
3104 */
3105 static stonith_history_t *
sort_stonith_history(stonith_history_t * history)3106 sort_stonith_history(stonith_history_t *history)
3107 {
3108 stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
3109
3110 for (hp = history; hp; ) {
3111 tmp = hp->next;
3112 if ((hp->state == st_done) || (hp->state == st_failed)) {
3113 /* sort into new */
3114 if ((!new) || (hp->completed > new->completed)) {
3115 hp->next = new;
3116 new = hp;
3117 } else {
3118 np = new;
3119 do {
3120 if ((!np->next) || (hp->completed > np->next->completed)) {
3121 hp->next = np->next;
3122 np->next = hp;
3123 break;
3124 }
3125 np = np->next;
3126 } while (1);
3127 }
3128 } else {
3129 /* put into pending */
3130 hp->next = pending;
3131 pending = hp;
3132 }
3133 hp = tmp;
3134 }
3135
3136 /* pending actions don't have a completed-stamp so make them go front */
3137 if (pending) {
3138 stonith_history_t *last_pending = pending;
3139
3140 while (last_pending->next) {
3141 last_pending = last_pending->next;
3142 }
3143
3144 last_pending->next = new;
3145 new = pending;
3146 }
3147 return new;
3148 }
3149
3150 /*!
3151 * \internal
3152 * \brief Turn stonith action into a better readable string
3153 *
3154 * \param[in] action Stonith action
3155 */
3156 static char *
fence_action_str(const char * action)3157 fence_action_str(const char *action)
3158 {
3159 char *str = NULL;
3160
3161 if (action == NULL) {
3162 str = strdup("fencing");
3163 } else if (!strcmp(action, "on")) {
3164 str = strdup("unfencing");
3165 } else if (!strcmp(action, "off")) {
3166 str = strdup("turning off");
3167 } else {
3168 str = strdup(action);
3169 }
3170 return str;
3171 }
3172
3173 /*!
3174 * \internal
3175 * \brief Print a stonith action
3176 *
3177 * \param[in] stream File stream to display output to
3178 * \param[in] event stonith event
3179 */
3180 static gboolean
is_later_succeeded(stonith_history_t * event,stonith_history_t * top_history)3181 is_later_succeeded(stonith_history_t *event, stonith_history_t *top_history)
3182 {
3183
3184 gboolean ret = FALSE;
3185
3186 for (stonith_history_t *prev_hp = top_history; prev_hp; prev_hp = prev_hp->next) {
3187 if (prev_hp == event) {
3188 break;
3189 }
3190
3191 if ((prev_hp->state == st_done) &&
3192 safe_str_eq(event->target, prev_hp->target) &&
3193 safe_str_eq(event->action, prev_hp->action) &&
3194 safe_str_eq(event->delegate, prev_hp->delegate) &&
3195 (event->completed < prev_hp->completed)) {
3196 ret = TRUE;
3197 break;
3198 }
3199 }
3200 return ret;
3201 }
3202
3203 static void
print_stonith_action(FILE * stream,stonith_history_t * event,stonith_history_t * top_history)3204 print_stonith_action(FILE *stream, stonith_history_t *event, stonith_history_t *top_history)
3205 {
3206 char *action_s = fence_action_str(event->action);
3207 time_t completed = event->completed;
3208 char *run_at_s = ctime((const time_t *) &completed);
3209
3210 if ((run_at_s) && (run_at_s[0] != 0)) {
3211 run_at_s[strlen(run_at_s)-1] = 0; /* Overwrite the newline */
3212 }
3213
3214 switch(output_format) {
3215 case mon_output_xml:
3216 fprintf(stream, " <fence_event target=\"%s\" action=\"%s\"",
3217 event->target, event->action);
3218 switch(event->state) {
3219 case st_done:
3220 fprintf(stream, " state=\"success\"");
3221 break;
3222 case st_failed:
3223 fprintf(stream, " state=\"failed\"");
3224 break;
3225 default:
3226 fprintf(stream, " state=\"pending\"");
3227 }
3228 fprintf(stream, " origin=\"%s\" client=\"%s\"",
3229 event->origin, event->client);
3230 if (event->delegate) {
3231 fprintf(stream, " delegate=\"%s\"", event->delegate);
3232 }
3233 switch(event->state) {
3234 case st_done:
3235 case st_failed:
3236 fprintf(stream, " completed=\"%s\"", run_at_s?run_at_s:"");
3237 break;
3238 default:
3239 break;
3240 }
3241 fprintf(stream, " />\n");
3242 break;
3243
3244 case mon_output_plain:
3245 case mon_output_console:
3246 switch(event->state) {
3247 case st_done:
3248 print_as("* %s of %s successful: delegate=%s, client=%s, origin=%s,\n"
3249 " %s='%s'\n",
3250 action_s, event->target,
3251 event->delegate ? event->delegate : "",
3252 event->client, event->origin,
3253 fence_full_history?"completed":"last-successful",
3254 run_at_s?run_at_s:"");
3255 break;
3256 case st_failed:
3257 print_as("* %s of %s failed: delegate=%s, client=%s, origin=%s,\n"
3258 " %s='%s' %s\n",
3259 action_s, event->target,
3260 event->delegate ? event->delegate : "",
3261 event->client, event->origin,
3262 fence_full_history?"completed":"last-failed",
3263 run_at_s?run_at_s:"",
3264 is_later_succeeded(event, top_history) ? "(a later attempt succeeded)" : "");
3265 break;
3266 default:
3267 print_as("* %s of %s pending: client=%s, origin=%s\n",
3268 action_s, event->target,
3269 event->client, event->origin);
3270 }
3271 break;
3272
3273 case mon_output_html:
3274 case mon_output_cgi:
3275 switch(event->state) {
3276 case st_done:
3277 fprintf(stream, " <li>%s of %s successful: delegate=%s, "
3278 "client=%s, origin=%s, %s='%s'</li>\n",
3279 action_s, event->target,
3280 event->delegate ? event->delegate : "",
3281 event->client, event->origin,
3282 fence_full_history?"completed":"last-successful",
3283 run_at_s?run_at_s:"");
3284 break;
3285 case st_failed:
3286 fprintf(stream, " <li>%s of %s failed: delegate=%s, "
3287 "client=%s, origin=%s, %s='%s' %s</li>\n",
3288 action_s, event->target,
3289 event->delegate ? event->delegate : "",
3290 event->client, event->origin,
3291 fence_full_history?"completed":"last-failed",
3292 run_at_s?run_at_s:"",
3293 is_later_succeeded(event, top_history) ? "(a later attempt succeeded)" : "");
3294 break;
3295 default:
3296 fprintf(stream, " <li>%s of %s pending: client=%s, "
3297 "origin=%s</li>\n",
3298 action_s, event->target,
3299 event->client, event->origin);
3300 }
3301 break;
3302
3303 default:
3304 /* no support for fence history for other formats so far */
3305 break;
3306 }
3307
3308 free(action_s);
3309 }
3310
3311 /*!
3312 * \internal
3313 * \brief Print a section for failed stonith actions
3314 *
3315 * \param[in] stream File stream to display output to
3316 * \param[in] history List of stonith actions
3317 *
3318 */
3319 static void
print_failed_stonith_actions(FILE * stream,stonith_history_t * history)3320 print_failed_stonith_actions(FILE *stream, stonith_history_t *history)
3321 {
3322 stonith_history_t *hp;
3323
3324 for (hp = history; hp; hp = hp->next) {
3325 if (hp->state == st_failed) {
3326 break;
3327 }
3328 }
3329 if (!hp) {
3330 return;
3331 }
3332
3333 /* Print section heading */
3334 switch (output_format) {
3335 /* no need to take care of xml in here as xml gets full
3336 * history anyway
3337 */
3338 case mon_output_plain:
3339 case mon_output_console:
3340 print_as("\nFailed Fencing Actions:\n");
3341 break;
3342
3343 case mon_output_html:
3344 case mon_output_cgi:
3345 fprintf(stream, " <hr />\n <h2>Failed Fencing Actions</h2>\n <ul>\n");
3346 break;
3347
3348 default:
3349 break;
3350 }
3351
3352 /* Print each failed stonith action */
3353 for (hp = history; hp; hp = hp->next) {
3354 if (hp->state == st_failed) {
3355 print_stonith_action(stream, hp, history);
3356 }
3357 }
3358
3359 /* End section */
3360 switch (output_format) {
3361 case mon_output_html:
3362 case mon_output_cgi:
3363 fprintf(stream, " </ul>\n");
3364 break;
3365
3366 default:
3367 break;
3368 }
3369 }
3370
3371 /*!
3372 * \internal
3373 * \brief Print pending stonith actions
3374 *
3375 * \param[in] stream File stream to display output to
3376 * \param[in] history List of stonith actions
3377 *
3378 */
3379 static void
print_stonith_pending(FILE * stream,stonith_history_t * history)3380 print_stonith_pending(FILE *stream, stonith_history_t *history)
3381 {
3382 /* xml-output always shows the full history
3383 * so we'll never have to show pending-actions
3384 * separately
3385 */
3386 if (history && (history->state != st_failed) &&
3387 (history->state != st_done)) {
3388 stonith_history_t *hp;
3389
3390 /* Print section heading */
3391 switch (output_format) {
3392 case mon_output_plain:
3393 case mon_output_console:
3394 print_as("\nPending Fencing Actions:\n");
3395 break;
3396
3397 case mon_output_html:
3398 case mon_output_cgi:
3399 fprintf(stream, " <hr />\n <h2>Pending Fencing Actions</h2>\n <ul>\n");
3400 break;
3401
3402 default:
3403 break;
3404 }
3405
3406 for (hp = history; hp; hp = hp->next) {
3407 if ((hp->state == st_failed) || (hp->state == st_done)) {
3408 break;
3409 }
3410 print_stonith_action(stream, hp, NULL);
3411 }
3412
3413 /* End section */
3414 switch (output_format) {
3415 case mon_output_html:
3416 case mon_output_cgi:
3417 fprintf(stream, " </ul>\n");
3418 break;
3419
3420 default:
3421 break;
3422 }
3423 }
3424 }
3425
3426 /*!
3427 * \internal
3428 * \brief Print a section for stonith-history
3429 *
3430 * \param[in] stream File stream to display output to
3431 * \param[in] history List of stonith actions
3432 *
3433 */
3434 static void
print_stonith_history(FILE * stream,stonith_history_t * history)3435 print_stonith_history(FILE *stream, stonith_history_t *history)
3436 {
3437 stonith_history_t *hp;
3438
3439 /* Print section heading */
3440 switch (output_format) {
3441 case mon_output_plain:
3442 case mon_output_console:
3443 print_as("\nFencing History:\n");
3444 break;
3445
3446 case mon_output_html:
3447 case mon_output_cgi:
3448 fprintf(stream, " <hr />\n <h2>Fencing History</h2>\n <ul>\n");
3449 break;
3450
3451 case mon_output_xml:
3452 fprintf(stream, " <fence_history>\n");
3453 break;
3454
3455 default:
3456 break;
3457 }
3458
3459 for (hp = history; hp; hp = hp->next) {
3460 if ((hp->state != st_failed) || (output_format == mon_output_xml)) {
3461 print_stonith_action(stream, hp, NULL);
3462 }
3463 }
3464
3465 /* End section */
3466 switch (output_format) {
3467 case mon_output_html:
3468 case mon_output_cgi:
3469 fprintf(stream, " </ul>\n");
3470 break;
3471
3472 case mon_output_xml:
3473 fprintf(stream, " </fence_history>\n");
3474 break;
3475
3476 default:
3477 break;
3478 }
3479 }
3480
3481 /*!
3482 * \internal
3483 * \brief Print cluster status to screen
3484 *
3485 * This uses the global display preferences set by command-line options
3486 * to display cluster status in a human-friendly way.
3487 *
3488 * \param[in] data_set Working set of CIB state
3489 * \param[in] stonith_history List of stonith actions
3490 */
3491 static void
print_status(pe_working_set_t * data_set,stonith_history_t * stonith_history)3492 print_status(pe_working_set_t * data_set,
3493 stonith_history_t *stonith_history)
3494 {
3495 GListPtr gIter = NULL;
3496 int print_opts = get_resource_display_options();
3497
3498 /* space-separated lists of node names */
3499 char *online_nodes = NULL;
3500 char *online_remote_nodes = NULL;
3501 char *online_guest_nodes = NULL;
3502 char *offline_nodes = NULL;
3503 char *offline_remote_nodes = NULL;
3504
3505 if (output_format == mon_output_console) {
3506 blank_screen();
3507 }
3508 print_cluster_summary(stdout, data_set);
3509 print_as("\n");
3510
3511 /* Gather node information (and print if in bad state or grouping by node) */
3512 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3513 node_t *node = (node_t *) gIter->data;
3514 const char *node_mode = NULL;
3515 char *node_name = get_node_display_name(node);
3516
3517 /* Get node mode */
3518 if (node->details->unclean) {
3519 if (node->details->online) {
3520 node_mode = "UNCLEAN (online)";
3521
3522 } else if (node->details->pending) {
3523 node_mode = "UNCLEAN (pending)";
3524
3525 } else {
3526 node_mode = "UNCLEAN (offline)";
3527 }
3528
3529 } else if (node->details->pending) {
3530 node_mode = "pending";
3531
3532 } else if (node->details->standby_onfail && node->details->online) {
3533 node_mode = "standby (on-fail)";
3534
3535 } else if (node->details->standby) {
3536 if (node->details->online) {
3537 if (node->details->running_rsc) {
3538 node_mode = "standby (with active resources)";
3539 } else {
3540 node_mode = "standby";
3541 }
3542 } else {
3543 node_mode = "OFFLINE (standby)";
3544 }
3545
3546 } else if (node->details->maintenance) {
3547 if (node->details->online) {
3548 node_mode = "maintenance";
3549 } else {
3550 node_mode = "OFFLINE (maintenance)";
3551 }
3552
3553 } else if (node->details->online) {
3554 node_mode = "online";
3555 if (group_by_node == FALSE) {
3556 if (is_container_remote_node(node)) {
3557 online_guest_nodes = add_list_element(online_guest_nodes, node_name);
3558 } else if (is_baremetal_remote_node(node)) {
3559 online_remote_nodes = add_list_element(online_remote_nodes, node_name);
3560 } else {
3561 online_nodes = add_list_element(online_nodes, node_name);
3562 }
3563 free(node_name);
3564 continue;
3565 }
3566 } else {
3567 node_mode = "OFFLINE";
3568 if (group_by_node == FALSE) {
3569 if (is_baremetal_remote_node(node)) {
3570 offline_remote_nodes = add_list_element(offline_remote_nodes, node_name);
3571 } else if (is_container_remote_node(node)) {
3572 /* ignore offline guest nodes */
3573 } else {
3574 offline_nodes = add_list_element(offline_nodes, node_name);
3575 }
3576 free(node_name);
3577 continue;
3578 }
3579 }
3580
3581 /* If we get here, node is in bad state, or we're grouping by node */
3582
3583 /* Print the node name and status */
3584 if (is_container_remote_node(node)) {
3585 print_as("Guest");
3586 } else if (is_baremetal_remote_node(node)) {
3587 print_as("Remote");
3588 }
3589 print_as("Node %s: %s\n", node_name, node_mode);
3590
3591 /* If we're grouping by node, print its resources */
3592 if (group_by_node) {
3593 if (print_brief) {
3594 print_rscs_brief(node->details->running_rsc, "\t", print_opts | pe_print_rsconly,
3595 stdout, FALSE);
3596 } else {
3597 GListPtr gIter2 = NULL;
3598
3599 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
3600 resource_t *rsc = (resource_t *) gIter2->data;
3601
3602 rsc->fns->print(rsc, "\t", print_opts | pe_print_rsconly, stdout);
3603 }
3604 }
3605 }
3606 free(node_name);
3607 }
3608
3609 /* If we're not grouping by node, summarize nodes by status */
3610 if (online_nodes) {
3611 print_as("Online: [%s ]\n", online_nodes);
3612 free(online_nodes);
3613 }
3614 if (offline_nodes) {
3615 print_as("OFFLINE: [%s ]\n", offline_nodes);
3616 free(offline_nodes);
3617 }
3618 if (online_remote_nodes) {
3619 print_as("RemoteOnline: [%s ]\n", online_remote_nodes);
3620 free(online_remote_nodes);
3621 }
3622 if (offline_remote_nodes) {
3623 print_as("RemoteOFFLINE: [%s ]\n", offline_remote_nodes);
3624 free(offline_remote_nodes);
3625 }
3626 if (online_guest_nodes) {
3627 print_as("GuestOnline: [%s ]\n", online_guest_nodes);
3628 free(online_guest_nodes);
3629 }
3630
3631 /* Print resources section, if needed */
3632 print_resources(stdout, data_set, print_opts);
3633
3634 /* print Node Attributes section if requested */
3635 if (show & mon_show_attributes) {
3636 print_node_attributes(stdout, data_set);
3637 }
3638
3639 /* If requested, print resource operations (which includes failcounts)
3640 * or just failcounts
3641 */
3642 if (show & (mon_show_operations | mon_show_failcounts)) {
3643 print_node_summary(stdout, data_set,
3644 ((show & mon_show_operations)? TRUE : FALSE));
3645 }
3646
3647 /* If there were any failed actions, print them */
3648 if (xml_has_children(data_set->failed)) {
3649 print_failed_actions(stdout, data_set);
3650 }
3651
3652 /* Print failed stonith actions */
3653 if (fence_history) {
3654 print_failed_stonith_actions(stdout, stonith_history);
3655 }
3656
3657 /* Print tickets if requested */
3658 if (show & mon_show_tickets) {
3659 print_cluster_tickets(stdout, data_set);
3660 }
3661
3662 /* Print negative location constraints if requested */
3663 if (show & mon_show_bans) {
3664 print_neg_locations(stdout, data_set);
3665 }
3666
3667 /* Print stonith history */
3668 if (fence_history) {
3669 if (show & mon_show_fence_history) {
3670 print_stonith_history(stdout, stonith_history);
3671 } else {
3672 print_stonith_pending(stdout, stonith_history);
3673 }
3674 }
3675
3676 #if CURSES_ENABLED
3677 if (output_format == mon_output_console) {
3678 refresh();
3679 }
3680 #endif
3681 }
3682
3683 /*!
3684 * \internal
3685 * \brief Print cluster status in XML format
3686 *
3687 * \param[in] data_set Working set of CIB state
3688 */
3689 static void
print_xml_status(pe_working_set_t * data_set,stonith_history_t * stonith_history)3690 print_xml_status(pe_working_set_t * data_set,
3691 stonith_history_t *stonith_history)
3692 {
3693 FILE *stream = stdout;
3694 GListPtr gIter = NULL;
3695 int print_opts = get_resource_display_options();
3696
3697 fprintf(stream, "<?xml version=\"1.0\"?>\n");
3698 fprintf(stream, "<crm_mon version=\"%s\">\n", VERSION);
3699
3700 print_cluster_summary(stream, data_set);
3701
3702 /*** NODES ***/
3703 fprintf(stream, " <nodes>\n");
3704 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3705 node_t *node = (node_t *) gIter->data;
3706 const char *node_type = "unknown";
3707
3708 switch (node->details->type) {
3709 case node_member:
3710 node_type = "member";
3711 break;
3712 case node_remote:
3713 node_type = "remote";
3714 break;
3715 case node_ping:
3716 node_type = "ping";
3717 break;
3718 }
3719
3720 fprintf(stream, " <node name=\"%s\" ", node->details->uname);
3721 fprintf(stream, "id=\"%s\" ", node->details->id);
3722 fprintf(stream, "online=\"%s\" ", node->details->online ? "true" : "false");
3723 fprintf(stream, "standby=\"%s\" ", node->details->standby ? "true" : "false");
3724 fprintf(stream, "standby_onfail=\"%s\" ", node->details->standby_onfail ? "true" : "false");
3725 fprintf(stream, "maintenance=\"%s\" ", node->details->maintenance ? "true" : "false");
3726 fprintf(stream, "pending=\"%s\" ", node->details->pending ? "true" : "false");
3727 fprintf(stream, "unclean=\"%s\" ", node->details->unclean ? "true" : "false");
3728 fprintf(stream, "shutdown=\"%s\" ", node->details->shutdown ? "true" : "false");
3729 fprintf(stream, "expected_up=\"%s\" ", node->details->expected_up ? "true" : "false");
3730 fprintf(stream, "is_dc=\"%s\" ", node->details->is_dc ? "true" : "false");
3731 fprintf(stream, "resources_running=\"%d\" ", g_list_length(node->details->running_rsc));
3732 fprintf(stream, "type=\"%s\" ", node_type);
3733 if (is_container_remote_node(node)) {
3734 fprintf(stream, "id_as_resource=\"%s\" ", node->details->remote_rsc->container->id);
3735 }
3736
3737 if (group_by_node) {
3738 GListPtr lpc2 = NULL;
3739
3740 fprintf(stream, ">\n");
3741 for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
3742 resource_t *rsc = (resource_t *) lpc2->data;
3743
3744 rsc->fns->print(rsc, " ", print_opts | pe_print_rsconly, stream);
3745 }
3746 fprintf(stream, " </node>\n");
3747 } else {
3748 fprintf(stream, "/>\n");
3749 }
3750 }
3751 fprintf(stream, " </nodes>\n");
3752
3753 /* Print resources section, if needed */
3754 print_resources(stream, data_set, print_opts);
3755
3756 /* print Node Attributes section if requested */
3757 if (show & mon_show_attributes) {
3758 print_node_attributes(stream, data_set);
3759 }
3760
3761 /* If requested, print resource operations (which includes failcounts)
3762 * or just failcounts
3763 */
3764 if (show & (mon_show_operations | mon_show_failcounts)) {
3765 print_node_summary(stream, data_set,
3766 ((show & mon_show_operations)? TRUE : FALSE));
3767 }
3768
3769 /* If there were any failed actions, print them */
3770 if (xml_has_children(data_set->failed)) {
3771 print_failed_actions(stream, data_set);
3772 }
3773
3774 /* Print stonith history */
3775 if (fence_history) {
3776 print_stonith_history(stdout, stonith_history);
3777 }
3778
3779 /* Print tickets if requested */
3780 if (show & mon_show_tickets) {
3781 print_cluster_tickets(stream, data_set);
3782 }
3783
3784 /* Print negative location constraints if requested */
3785 if (show & mon_show_bans) {
3786 print_neg_locations(stream, data_set);
3787 }
3788
3789 fprintf(stream, "</crm_mon>\n");
3790 fflush(stream);
3791 fclose(stream);
3792 }
3793
3794 /*!
3795 * \internal
3796 * \brief Print cluster status in HTML format (with HTTP headers if CGI)
3797 *
3798 * \param[in] data_set Working set of CIB state
3799 * \param[in] filename Name of file to write HTML to (ignored if CGI)
3800 *
3801 * \return 0 on success, -1 on error
3802 */
3803 static int
print_html_status(pe_working_set_t * data_set,const char * filename,stonith_history_t * stonith_history)3804 print_html_status(pe_working_set_t * data_set,
3805 const char *filename,
3806 stonith_history_t *stonith_history)
3807 {
3808 FILE *stream;
3809 GListPtr gIter = NULL;
3810 char *filename_tmp = NULL;
3811 int print_opts = get_resource_display_options();
3812
3813 if (output_format == mon_output_cgi) {
3814 stream = stdout;
3815 fprintf(stream, "Content-Type: text/html\n\n");
3816
3817 } else {
3818 filename_tmp = crm_concat(filename, "tmp", '.');
3819 stream = fopen(filename_tmp, "w");
3820 if (stream == NULL) {
3821 crm_perror(LOG_ERR, "Cannot open %s for writing", filename_tmp);
3822 free(filename_tmp);
3823 return -1;
3824 }
3825 }
3826
3827 fprintf(stream, "<html>\n");
3828 fprintf(stream, " <head>\n");
3829 fprintf(stream, " <title>Cluster status</title>\n");
3830 fprintf(stream, " <meta http-equiv=\"refresh\" content=\"%d\">\n", reconnect_msec / 1000);
3831 fprintf(stream, " </head>\n");
3832 fprintf(stream, "<body>\n");
3833
3834 print_cluster_summary(stream, data_set);
3835
3836 /*** NODE LIST ***/
3837
3838 fprintf(stream, " <hr />\n <h2>Node List</h2>\n");
3839 fprintf(stream, "<ul>\n");
3840 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3841 node_t *node = (node_t *) gIter->data;
3842 char *node_name = get_node_display_name(node);
3843
3844 fprintf(stream, "<li>Node: %s: ", node_name);
3845 if (node->details->standby_onfail && node->details->online) {
3846 fprintf(stream, "<font color=\"orange\">standby (on-fail)</font>\n");
3847 } else if (node->details->standby && node->details->online) {
3848
3849 fprintf(stream, "<font color=\"orange\">standby%s</font>\n",
3850 node->details->running_rsc?" (with active resources)":"");
3851 } else if (node->details->standby) {
3852 fprintf(stream, "<font color=\"red\">OFFLINE (standby)</font>\n");
3853 } else if (node->details->maintenance && node->details->online) {
3854 fprintf(stream, "<font color=\"blue\">maintenance</font>\n");
3855 } else if (node->details->maintenance) {
3856 fprintf(stream, "<font color=\"red\">OFFLINE (maintenance)</font>\n");
3857 } else if (node->details->online) {
3858 fprintf(stream, "<font color=\"green\">online</font>\n");
3859 } else {
3860 fprintf(stream, "<font color=\"red\">OFFLINE</font>\n");
3861 }
3862 if (print_brief && group_by_node) {
3863 fprintf(stream, "<ul>\n");
3864 print_rscs_brief(node->details->running_rsc, NULL, print_opts | pe_print_rsconly,
3865 stream, FALSE);
3866 fprintf(stream, "</ul>\n");
3867
3868 } else if (group_by_node) {
3869 GListPtr lpc2 = NULL;
3870
3871 fprintf(stream, "<ul>\n");
3872 for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
3873 resource_t *rsc = (resource_t *) lpc2->data;
3874
3875 fprintf(stream, "<li>");
3876 rsc->fns->print(rsc, NULL, print_opts | pe_print_rsconly, stream);
3877 fprintf(stream, "</li>\n");
3878 }
3879 fprintf(stream, "</ul>\n");
3880 }
3881 fprintf(stream, "</li>\n");
3882 free(node_name);
3883 }
3884 fprintf(stream, "</ul>\n");
3885
3886 /* Print resources section, if needed */
3887 print_resources(stream, data_set, print_opts);
3888
3889 /* print Node Attributes section if requested */
3890 if (show & mon_show_attributes) {
3891 print_node_attributes(stream, data_set);
3892 }
3893
3894 /* If requested, print resource operations (which includes failcounts)
3895 * or just failcounts
3896 */
3897 if (show & (mon_show_operations | mon_show_failcounts)) {
3898 print_node_summary(stream, data_set,
3899 ((show & mon_show_operations)? TRUE : FALSE));
3900 }
3901
3902 /* If there were any failed actions, print them */
3903 if (xml_has_children(data_set->failed)) {
3904 print_failed_actions(stream, data_set);
3905 }
3906
3907 /* Print failed stonith actions */
3908 if (fence_history) {
3909 print_failed_stonith_actions(stream, stonith_history);
3910 }
3911
3912 /* Print stonith history */
3913 if (fence_history) {
3914 if (show & mon_show_fence_history) {
3915 print_stonith_history(stream, stonith_history);
3916 } else {
3917 print_stonith_pending(stdout, stonith_history);
3918 }
3919 }
3920
3921 /* Print tickets if requested */
3922 if (show & mon_show_tickets) {
3923 print_cluster_tickets(stream, data_set);
3924 }
3925
3926 /* Print negative location constraints if requested */
3927 if (show & mon_show_bans) {
3928 print_neg_locations(stream, data_set);
3929 }
3930
3931 fprintf(stream, "</body>\n");
3932 fprintf(stream, "</html>\n");
3933 fflush(stream);
3934 fclose(stream);
3935
3936 if (output_format != mon_output_cgi) {
3937 if (rename(filename_tmp, filename) != 0) {
3938 crm_perror(LOG_ERR, "Unable to rename %s->%s", filename_tmp, filename);
3939 }
3940 free(filename_tmp);
3941 }
3942 return 0;
3943 }
3944
3945 #if ENABLE_SNMP
3946 # include <net-snmp/net-snmp-config.h>
3947 # include <net-snmp/snmpv3_api.h>
3948 # include <net-snmp/agent/agent_trap.h>
3949 # include <net-snmp/library/snmp_client.h>
3950 # include <net-snmp/library/mib.h>
3951 # include <net-snmp/library/snmp_debug.h>
3952
3953 # define add_snmp_field(list, oid_string, value) do { \
3954 oid name[MAX_OID_LEN]; \
3955 size_t name_length = MAX_OID_LEN; \
3956 if (snmp_parse_oid(oid_string, name, &name_length)) { \
3957 int s_rc = snmp_add_var(list, name, name_length, 's', (value)); \
3958 if(s_rc != 0) { \
3959 crm_err("Could not add %s=%s rc=%d", oid_string, value, s_rc); \
3960 } else { \
3961 crm_trace("Added %s=%s", oid_string, value); \
3962 } \
3963 } else { \
3964 crm_err("Could not parse OID: %s", oid_string); \
3965 } \
3966 } while(0) \
3967
3968 # define add_snmp_field_int(list, oid_string, value) do { \
3969 oid name[MAX_OID_LEN]; \
3970 size_t name_length = MAX_OID_LEN; \
3971 if (snmp_parse_oid(oid_string, name, &name_length)) { \
3972 if(NULL == snmp_pdu_add_variable( \
3973 list, name, name_length, ASN_INTEGER, \
3974 (u_char *) & value, sizeof(value))) { \
3975 crm_err("Could not add %s=%d", oid_string, value); \
3976 } else { \
3977 crm_trace("Added %s=%d", oid_string, value); \
3978 } \
3979 } else { \
3980 crm_err("Could not parse OID: %s", oid_string); \
3981 } \
3982 } while(0) \
3983
3984 static int
snmp_input(int operation,netsnmp_session * session,int reqid,netsnmp_pdu * pdu,void * magic)3985 snmp_input(int operation, netsnmp_session * session, int reqid, netsnmp_pdu * pdu, void *magic)
3986 {
3987 return 1;
3988 }
3989
3990 static netsnmp_session *
crm_snmp_init(const char * target,char * community)3991 crm_snmp_init(const char *target, char *community)
3992 {
3993 static netsnmp_session *session = NULL;
3994
3995 # ifdef NETSNMPV53
3996 char target53[128];
3997
3998 snprintf(target53, sizeof(target53), "%s:162", target);
3999 # endif
4000
4001 if (session) {
4002 return session;
4003 }
4004
4005 if (target == NULL) {
4006 return NULL;
4007 }
4008
4009 if (get_crm_log_level() > LOG_INFO) {
4010 char *debug_tokens = strdup("run:shell,snmptrap,tdomain");
4011
4012 debug_register_tokens(debug_tokens);
4013 snmp_set_do_debugging(1);
4014 }
4015
4016 session = calloc(1, sizeof(netsnmp_session));
4017 snmp_sess_init(session);
4018 session->version = SNMP_VERSION_2c;
4019 session->callback = snmp_input;
4020 session->callback_magic = NULL;
4021
4022 if (community) {
4023 session->community_len = strlen(community);
4024 session->community = (unsigned char *)community;
4025 }
4026
4027 session = snmp_add(session,
4028 # ifdef NETSNMPV53
4029 netsnmp_tdomain_transport(target53, 0, "udp"),
4030 # else
4031 netsnmp_transport_open_client("snmptrap", target),
4032 # endif
4033 NULL, NULL);
4034
4035 if (session == NULL) {
4036 snmp_sess_perror("Could not create snmp transport", session);
4037 }
4038 return session;
4039 }
4040
4041 #endif
4042
4043 static int
send_snmp_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4044 send_snmp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4045 int status, const char *desc)
4046 {
4047 int ret = 1;
4048
4049 #if ENABLE_SNMP
4050 static oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
4051 static oid sysuptime_oid[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
4052
4053 netsnmp_pdu *trap_pdu;
4054 netsnmp_session *session = crm_snmp_init(snmp_target, snmp_community);
4055
4056 trap_pdu = snmp_pdu_create(SNMP_MSG_TRAP2);
4057 if (!trap_pdu) {
4058 crm_err("Failed to create SNMP notification");
4059 return SNMPERR_GENERR;
4060 }
4061
4062 if (1) {
4063 /* send uptime */
4064 char csysuptime[20];
4065 time_t now = time(NULL);
4066
4067 sprintf(csysuptime, "%lld", (long long) now);
4068 snmp_add_var(trap_pdu, sysuptime_oid, sizeof(sysuptime_oid) / sizeof(oid), 't', csysuptime);
4069 }
4070
4071 /* Indicate what the trap is by setting snmpTrapOid.0 */
4072 ret =
4073 snmp_add_var(trap_pdu, snmptrap_oid, sizeof(snmptrap_oid) / sizeof(oid), 'o',
4074 snmp_crm_trap_oid);
4075 if (ret != 0) {
4076 crm_err("Failed set snmpTrapOid.0=%s", snmp_crm_trap_oid);
4077 return ret;
4078 }
4079
4080 /* Add extries to the trap */
4081 if (rsc) {
4082 add_snmp_field(trap_pdu, snmp_crm_oid_rsc, rsc);
4083 }
4084 add_snmp_field(trap_pdu, snmp_crm_oid_node, node);
4085 add_snmp_field(trap_pdu, snmp_crm_oid_task, task);
4086 add_snmp_field(trap_pdu, snmp_crm_oid_desc, desc);
4087
4088 add_snmp_field_int(trap_pdu, snmp_crm_oid_rc, rc);
4089 add_snmp_field_int(trap_pdu, snmp_crm_oid_trc, target_rc);
4090 add_snmp_field_int(trap_pdu, snmp_crm_oid_status, status);
4091
4092 /* Send and cleanup */
4093 ret = snmp_send(session, trap_pdu);
4094 if (ret == 0) {
4095 /* error */
4096 snmp_sess_perror("Could not send SNMP trap", session);
4097 snmp_free_pdu(trap_pdu);
4098 ret = SNMPERR_GENERR;
4099 } else {
4100 ret = SNMPERR_SUCCESS;
4101 }
4102 #else
4103 crm_err("Sending SNMP traps is not supported by this installation");
4104 #endif
4105 return ret;
4106 }
4107
4108 #if ENABLE_ESMTP
4109 # include <auth-client.h>
4110 # include <libesmtp.h>
4111
4112 static void
print_recipient_status(smtp_recipient_t recipient,const char * mailbox,void * arg)4113 print_recipient_status(smtp_recipient_t recipient, const char *mailbox, void *arg)
4114 {
4115 const smtp_status_t *status;
4116
4117 status = smtp_recipient_status(recipient);
4118 printf("%s: %d %s", mailbox, status->code, status->text);
4119 }
4120
4121 static void
event_cb(smtp_session_t session,int event_no,void * arg,...)4122 event_cb(smtp_session_t session, int event_no, void *arg, ...)
4123 {
4124 int *ok;
4125 va_list alist;
4126
4127 va_start(alist, arg);
4128 switch (event_no) {
4129 case SMTP_EV_CONNECT:
4130 case SMTP_EV_MAILSTATUS:
4131 case SMTP_EV_RCPTSTATUS:
4132 case SMTP_EV_MESSAGEDATA:
4133 case SMTP_EV_MESSAGESENT:
4134 case SMTP_EV_DISCONNECT:
4135 break;
4136
4137 case SMTP_EV_WEAK_CIPHER:{
4138 int bits = va_arg(alist, long);
4139 ok = va_arg(alist, int *);
4140
4141 crm_debug("SMTP_EV_WEAK_CIPHER, bits=%d - accepted.", bits);
4142 *ok = 1;
4143 break;
4144 }
4145 case SMTP_EV_STARTTLS_OK:
4146 crm_debug("SMTP_EV_STARTTLS_OK - TLS started here.");
4147 break;
4148
4149 case SMTP_EV_INVALID_PEER_CERTIFICATE:{
4150 long vfy_result = va_arg(alist, long);
4151 ok = va_arg(alist, int *);
4152
4153 /* There is a table in handle_invalid_peer_certificate() of mail-file.c */
4154 crm_err("SMTP_EV_INVALID_PEER_CERTIFICATE: %ld", vfy_result);
4155 *ok = 1;
4156 break;
4157 }
4158 case SMTP_EV_NO_PEER_CERTIFICATE:
4159 ok = va_arg(alist, int *);
4160
4161 crm_debug("SMTP_EV_NO_PEER_CERTIFICATE - accepted.");
4162 *ok = 1;
4163 break;
4164 case SMTP_EV_WRONG_PEER_CERTIFICATE:
4165 ok = va_arg(alist, int *);
4166
4167 crm_debug("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted.");
4168 *ok = 1;
4169 break;
4170 case SMTP_EV_NO_CLIENT_CERTIFICATE:
4171 ok = va_arg(alist, int *);
4172
4173 crm_debug("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted.");
4174 *ok = 1;
4175 break;
4176 default:
4177 crm_debug("Got event: %d - ignored.", event_no);
4178 }
4179 va_end(alist);
4180 }
4181 #endif
4182
4183 #define BODY_MAX 2048
4184
4185 #if ENABLE_ESMTP
4186 static void
crm_smtp_debug(const char * buf,int buflen,int writing,void * arg)4187 crm_smtp_debug(const char *buf, int buflen, int writing, void *arg)
4188 {
4189 char type = 0;
4190 int lpc = 0, last = 0, level = *(int *)arg;
4191
4192 if (writing == SMTP_CB_HEADERS) {
4193 type = 'H';
4194 } else if (writing) {
4195 type = 'C';
4196 } else {
4197 type = 'S';
4198 }
4199
4200 for (; lpc < buflen; lpc++) {
4201 switch (buf[lpc]) {
4202 case 0:
4203 case '\n':
4204 if (last > 0) {
4205 do_crm_log(level, " %.*s", lpc - last, buf + last);
4206 } else {
4207 do_crm_log(level, "%c: %.*s", type, lpc - last, buf + last);
4208 }
4209 last = lpc + 1;
4210 break;
4211 }
4212 }
4213 }
4214 #endif
4215
4216 static int
send_custom_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4217 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4218 int status, const char *desc)
4219 {
4220 pid_t pid;
4221
4222 /*setenv needs chars, these are ints */
4223 char *rc_s = crm_itoa(rc);
4224 char *status_s = crm_itoa(status);
4225 char *target_rc_s = crm_itoa(target_rc);
4226
4227 crm_debug("Sending external notification to '%s' via '%s'", external_recipient, external_agent);
4228
4229 if(rsc) {
4230 setenv("CRM_notify_rsc", rsc, 1);
4231 }
4232 if (external_recipient) {
4233 setenv("CRM_notify_recipient", external_recipient, 1);
4234 }
4235 setenv("CRM_notify_node", node, 1);
4236 setenv("CRM_notify_task", task, 1);
4237 setenv("CRM_notify_desc", desc, 1);
4238 setenv("CRM_notify_rc", rc_s, 1);
4239 setenv("CRM_notify_target_rc", target_rc_s, 1);
4240 setenv("CRM_notify_status", status_s, 1);
4241
4242 pid = fork();
4243 if (pid == -1) {
4244 crm_perror(LOG_ERR, "notification fork() failed.");
4245 }
4246 if (pid == 0) {
4247 /* crm_debug("notification: I am the child. Executing the nofitication program."); */
4248 execl(external_agent, external_agent, NULL);
4249 exit(EXIT_FAILURE);
4250 }
4251
4252 crm_trace("Finished running custom notification program '%s'.", external_agent);
4253 free(target_rc_s);
4254 free(status_s);
4255 free(rc_s);
4256 return 0;
4257 }
4258
4259 static int
send_smtp_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4260 send_smtp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4261 int status, const char *desc)
4262 {
4263 #if ENABLE_ESMTP
4264 smtp_session_t session;
4265 smtp_message_t message;
4266 auth_context_t authctx;
4267 struct sigaction sa;
4268
4269 int len = 25; /* Note: Check extra padding on the Subject line below */
4270 int noauth = 1;
4271 int smtp_debug = LOG_DEBUG;
4272 char crm_mail_body[BODY_MAX];
4273 char *crm_mail_subject = NULL;
4274
4275 memset(&sa, 0, sizeof(struct sigaction));
4276
4277 if (node == NULL) {
4278 node = "-";
4279 }
4280 if (rsc == NULL) {
4281 rsc = "-";
4282 }
4283 if (desc == NULL) {
4284 desc = "-";
4285 }
4286
4287 if (crm_mail_to == NULL) {
4288 return 1;
4289 }
4290
4291 if (crm_mail_host == NULL) {
4292 crm_mail_host = "localhost:25";
4293 }
4294
4295 if (crm_mail_prefix == NULL) {
4296 crm_mail_prefix = "Cluster notification";
4297 }
4298
4299 crm_debug("Sending '%s' mail to %s via %s", crm_mail_prefix, crm_mail_to, crm_mail_host);
4300
4301 len += strlen(crm_mail_prefix);
4302 len += strlen(task);
4303 len += strlen(rsc);
4304 len += strlen(node);
4305 len += strlen(desc);
4306 len++;
4307
4308 crm_mail_subject = calloc(1, len);
4309 /* If you edit this line, ensure you allocate enough memory for it by altering 'len' above */
4310 snprintf(crm_mail_subject, len, "%s - %s event for %s on %s: %s\r\n", crm_mail_prefix, task,
4311 rsc, node, desc);
4312
4313 len = 0;
4314 len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\n%s\r\n", crm_mail_prefix);
4315 len += snprintf(crm_mail_body + len, BODY_MAX - len, "====\r\n\r\n");
4316 if (rc == target_rc) {
4317 len += snprintf(crm_mail_body + len, BODY_MAX - len,
4318 "Completed operation %s for resource %s on %s\r\n", task, rsc, node);
4319 } else {
4320 len += snprintf(crm_mail_body + len, BODY_MAX - len,
4321 "Operation %s for resource %s on %s failed: %s\r\n", task, rsc, node, desc);
4322 }
4323
4324 len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\nDetails:\r\n");
4325 len += snprintf(crm_mail_body + len, BODY_MAX - len,
4326 "\toperation status: (%d) %s\r\n", status, services_lrm_status_str(status));
4327 if (status == PCMK_LRM_OP_DONE) {
4328 len += snprintf(crm_mail_body + len, BODY_MAX - len,
4329 "\tscript returned: (%d) %s\r\n", rc, services_ocf_exitcode_str(rc));
4330 len += snprintf(crm_mail_body + len, BODY_MAX - len,
4331 "\texpected return value: (%d) %s\r\n", target_rc,
4332 services_ocf_exitcode_str(target_rc));
4333 }
4334
4335 auth_client_init();
4336 session = smtp_create_session();
4337 message = smtp_add_message(session);
4338
4339 smtp_starttls_enable(session, Starttls_ENABLED);
4340
4341 sa.sa_handler = SIG_IGN;
4342 sigemptyset(&sa.sa_mask);
4343 sa.sa_flags = 0;
4344 sigaction(SIGPIPE, &sa, NULL);
4345
4346 smtp_set_server(session, crm_mail_host);
4347
4348 authctx = auth_create_context();
4349 auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
4350
4351 smtp_set_eventcb(session, event_cb, NULL);
4352
4353 /* Now tell libESMTP it can use the SMTP AUTH extension.
4354 */
4355 if (!noauth) {
4356 crm_debug("Adding authentication context");
4357 smtp_auth_set_context(session, authctx);
4358 }
4359
4360 if (crm_mail_from == NULL) {
4361 struct utsname us;
4362 char auto_from[BODY_MAX];
4363
4364 CRM_ASSERT(uname(&us) == 0);
4365 snprintf(auto_from, BODY_MAX, "crm_mon@%s", us.nodename);
4366 smtp_set_reverse_path(message, auto_from);
4367
4368 } else {
4369 /* NULL is ok */
4370 smtp_set_reverse_path(message, crm_mail_from);
4371 }
4372
4373 smtp_set_header(message, "To", NULL /*phrase */ , NULL /*addr */ ); /* "Phrase" <addr> */
4374 smtp_add_recipient(message, crm_mail_to);
4375
4376 /* Set the Subject: header and override any subject line in the message headers. */
4377 smtp_set_header(message, "Subject", crm_mail_subject);
4378 smtp_set_header_option(message, "Subject", Hdr_OVERRIDE, 1);
4379
4380 smtp_set_message_str(message, crm_mail_body);
4381 smtp_set_monitorcb(session, crm_smtp_debug, &smtp_debug, 1);
4382
4383 if (smtp_start_session(session)) {
4384 char buf[128];
4385 int rc = smtp_errno();
4386
4387 crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
4388
4389 } else {
4390 char buf[128];
4391 int rc = smtp_errno();
4392 const smtp_status_t *smtp_status = smtp_message_transfer_status(message);
4393
4394 if (rc != 0) {
4395 crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
4396 }
4397 crm_info("Send status: %d %s", smtp_status->code, crm_str(smtp_status->text));
4398 smtp_enumerate_recipients(message, print_recipient_status, NULL);
4399 }
4400
4401 smtp_destroy_session(session);
4402 auth_destroy_context(authctx);
4403 auth_client_exit();
4404 #endif
4405 return 0;
4406 }
4407
4408 static void
handle_rsc_op(xmlNode * xml,const char * node_id)4409 handle_rsc_op(xmlNode * xml, const char *node_id)
4410 {
4411 int rc = -1;
4412 int status = -1;
4413 int action = -1;
4414 int target_rc = -1;
4415 int transition_num = -1;
4416 gboolean notify = TRUE;
4417
4418 char *rsc = NULL;
4419 char *task = NULL;
4420 const char *desc = NULL;
4421 const char *magic = NULL;
4422 const char *id = NULL;
4423 char *update_te_uuid = NULL;
4424 const char *node = NULL;
4425
4426 xmlNode *n = xml;
4427 xmlNode * rsc_op = xml;
4428
4429 if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
4430 xmlNode *cIter;
4431
4432 for(cIter = xml->children; cIter; cIter = cIter->next) {
4433 handle_rsc_op(cIter, node_id);
4434 }
4435
4436 return;
4437 }
4438
4439 id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
4440 if (id == NULL) {
4441 /* Compatibility with <= 1.1.5 */
4442 id = ID(rsc_op);
4443 }
4444
4445 magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
4446 if (magic == NULL) {
4447 /* non-change */
4448 return;
4449 }
4450
4451 if (FALSE == decode_transition_magic(magic, &update_te_uuid, &transition_num, &action,
4452 &status, &rc, &target_rc)) {
4453 crm_err("Invalid event %s detected for %s", magic, id);
4454 return;
4455 }
4456
4457 if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
4458 crm_err("Invalid event detected for %s", id);
4459 goto bail;
4460 }
4461
4462 node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
4463
4464 while (n != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) {
4465 n = n->parent;
4466 }
4467
4468 if(node == NULL && n) {
4469 node = crm_element_value(n, XML_ATTR_UNAME);
4470 }
4471
4472 if (node == NULL && n) {
4473 node = ID(n);
4474 }
4475
4476 if (node == NULL) {
4477 node = node_id;
4478 }
4479
4480 if (node == NULL) {
4481 crm_err("No node detected for event %s (%s)", magic, id);
4482 goto bail;
4483 }
4484
4485 /* look up where we expected it to be? */
4486 desc = pcmk_strerror(pcmk_ok);
4487 if (status == PCMK_LRM_OP_DONE && target_rc == rc) {
4488 crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
4489 if (rc == PCMK_OCF_NOT_RUNNING) {
4490 notify = FALSE;
4491 }
4492
4493 } else if (status == PCMK_LRM_OP_DONE) {
4494 desc = services_ocf_exitcode_str(rc);
4495 crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
4496
4497 } else {
4498 desc = services_lrm_status_str(status);
4499 crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
4500 }
4501
4502 if (notify && snmp_target) {
4503 send_snmp_trap(node, rsc, task, target_rc, rc, status, desc);
4504 }
4505 if (notify && crm_mail_to) {
4506 send_smtp_trap(node, rsc, task, target_rc, rc, status, desc);
4507 }
4508 if (notify && external_agent) {
4509 send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
4510 }
4511 bail:
4512 free(update_te_uuid);
4513 free(rsc);
4514 free(task);
4515 }
4516
4517 static gboolean
mon_trigger_refresh(gpointer user_data)4518 mon_trigger_refresh(gpointer user_data)
4519 {
4520 mainloop_set_trigger(refresh_trigger);
4521 return FALSE;
4522 }
4523
4524 #define NODE_PATT "/lrm[@id="
get_node_from_xpath(const char * xpath)4525 static char *get_node_from_xpath(const char *xpath)
4526 {
4527 char *nodeid = NULL;
4528 char *tmp = strstr(xpath, NODE_PATT);
4529
4530 if(tmp) {
4531 tmp += strlen(NODE_PATT);
4532 tmp += 1;
4533
4534 nodeid = strdup(tmp);
4535 tmp = strstr(nodeid, "\'");
4536 CRM_ASSERT(tmp);
4537 tmp[0] = 0;
4538 }
4539 return nodeid;
4540 }
4541
crm_diff_update_v2(const char * event,xmlNode * msg)4542 static void crm_diff_update_v2(const char *event, xmlNode * msg)
4543 {
4544 xmlNode *change = NULL;
4545 xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
4546
4547 for (change = __xml_first_child(diff); change != NULL; change = __xml_next(change)) {
4548 const char *name = NULL;
4549 const char *op = crm_element_value(change, XML_DIFF_OP);
4550 const char *xpath = crm_element_value(change, XML_DIFF_PATH);
4551 xmlNode *match = NULL;
4552 const char *node = NULL;
4553
4554 if(op == NULL) {
4555 continue;
4556
4557 } else if(strcmp(op, "create") == 0) {
4558 match = change->children;
4559
4560 } else if(strcmp(op, "move") == 0) {
4561 continue;
4562
4563 } else if(strcmp(op, "delete") == 0) {
4564 continue;
4565
4566 } else if(strcmp(op, "modify") == 0) {
4567 match = first_named_child(change, XML_DIFF_RESULT);
4568 if(match) {
4569 match = match->children;
4570 }
4571 }
4572
4573 if(match) {
4574 name = (const char *)match->name;
4575 }
4576
4577 crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
4578 if(xpath == NULL) {
4579 /* Version field, ignore */
4580
4581 } else if(name == NULL) {
4582 crm_debug("No result for %s operation to %s", op, xpath);
4583 CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
4584
4585 } else if(strcmp(name, XML_TAG_CIB) == 0) {
4586 xmlNode *state = NULL;
4587 xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
4588
4589 for (state = __xml_first_child_element(status); state != NULL;
4590 state = __xml_next_element(state)) {
4591
4592 node = crm_element_value(state, XML_ATTR_UNAME);
4593 if (node == NULL) {
4594 node = ID(state);
4595 }
4596 handle_rsc_op(state, node);
4597 }
4598
4599 } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
4600 xmlNode *state = NULL;
4601
4602 for (state = __xml_first_child_element(match); state != NULL;
4603 state = __xml_next_element(state)) {
4604
4605 node = crm_element_value(state, XML_ATTR_UNAME);
4606 if (node == NULL) {
4607 node = ID(state);
4608 }
4609 handle_rsc_op(state, node);
4610 }
4611
4612 } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
4613 node = crm_element_value(match, XML_ATTR_UNAME);
4614 if (node == NULL) {
4615 node = ID(match);
4616 }
4617 handle_rsc_op(match, node);
4618
4619 } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
4620 node = ID(match);
4621 handle_rsc_op(match, node);
4622
4623 } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
4624 char *local_node = get_node_from_xpath(xpath);
4625
4626 handle_rsc_op(match, local_node);
4627 free(local_node);
4628
4629 } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
4630 char *local_node = get_node_from_xpath(xpath);
4631
4632 handle_rsc_op(match, local_node);
4633 free(local_node);
4634
4635 } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
4636 char *local_node = get_node_from_xpath(xpath);
4637
4638 handle_rsc_op(match, local_node);
4639 free(local_node);
4640
4641 } else {
4642 crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
4643 }
4644 }
4645 }
4646
crm_diff_update_v1(const char * event,xmlNode * msg)4647 static void crm_diff_update_v1(const char *event, xmlNode * msg)
4648 {
4649 /* Process operation updates */
4650 xmlXPathObject *xpathObj = xpath_search(msg,
4651 "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
4652 "//" XML_LRM_TAG_RSC_OP);
4653 int lpc = 0, max = numXpathResults(xpathObj);
4654
4655 for (lpc = 0; lpc < max; lpc++) {
4656 xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
4657
4658 handle_rsc_op(rsc_op, NULL);
4659 }
4660 freeXpathObject(xpathObj);
4661 }
4662
4663 void
crm_diff_update(const char * event,xmlNode * msg)4664 crm_diff_update(const char *event, xmlNode * msg)
4665 {
4666 int rc = -1;
4667 static bool stale = FALSE;
4668 gboolean cib_updated = FALSE;
4669 xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
4670
4671 print_dot();
4672
4673 if (current_cib != NULL) {
4674 rc = xml_apply_patchset(current_cib, diff, TRUE);
4675
4676 switch (rc) {
4677 case -pcmk_err_diff_resync:
4678 case -pcmk_err_diff_failed:
4679 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
4680 free_xml(current_cib); current_cib = NULL;
4681 break;
4682 case pcmk_ok:
4683 cib_updated = TRUE;
4684 break;
4685 default:
4686 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
4687 free_xml(current_cib); current_cib = NULL;
4688 }
4689 }
4690
4691 if (current_cib == NULL) {
4692 crm_trace("Re-requesting the full cib");
4693 cib->cmds->query(cib, NULL, ¤t_cib, cib_scope_local | cib_sync_call);
4694 }
4695
4696 if (crm_mail_to || snmp_target || external_agent) {
4697 int format = 0;
4698 crm_element_value_int(diff, "format", &format);
4699 switch(format) {
4700 case 1:
4701 crm_diff_update_v1(event, msg);
4702 break;
4703 case 2:
4704 crm_diff_update_v2(event, msg);
4705 break;
4706 default:
4707 crm_err("Unknown patch format: %d", format);
4708 }
4709 }
4710
4711 if (current_cib == NULL) {
4712 if(!stale) {
4713 print_as("--- Stale data ---");
4714 }
4715 stale = TRUE;
4716 return;
4717 }
4718
4719 stale = FALSE;
4720 kick_refresh(cib_updated);
4721 }
4722
4723 gboolean
mon_refresh_display(gpointer user_data)4724 mon_refresh_display(gpointer user_data)
4725 {
4726 xmlNode *cib_copy = copy_xml(current_cib);
4727 stonith_history_t *stonith_history = NULL;
4728
4729 last_refresh = time(NULL);
4730
4731 if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
4732 if (cib) {
4733 cib->cmds->signoff(cib);
4734 }
4735 print_as("Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation));
4736 if (output_format == mon_output_console) {
4737 sleep(2);
4738 }
4739 clean_up(EX_USAGE);
4740 return FALSE;
4741 }
4742
4743 /* get the stonith-history if there is evidence we need it
4744 */
4745 while (fence_history) {
4746 if (st != NULL) {
4747 if (st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120)) {
4748 fprintf(stderr, "Critical: Unable to get stonith-history\n");
4749 mon_cib_connection_destroy(NULL);
4750 } else {
4751 stonith_history = sort_stonith_history(stonith_history);
4752 if ((!fence_full_history) && (output_format != mon_output_xml)) {
4753 stonith_history = reduce_stonith_history(stonith_history);
4754 }
4755 break; /* all other cases are errors */
4756 }
4757 } else {
4758 fprintf(stderr, "Critical: No stonith-API\n");
4759 }
4760 free_xml(cib_copy);
4761 print_as("Reading stonith-history failed");
4762 if (output_format == mon_output_console) {
4763 sleep(2);
4764 }
4765 return FALSE;
4766 }
4767
4768 if (mon_data_set == NULL) {
4769 mon_data_set = pe_new_working_set();
4770 CRM_ASSERT(mon_data_set != NULL);
4771 }
4772 set_bit(mon_data_set->flags, pe_flag_no_compat);
4773
4774 mon_data_set->input = cib_copy;
4775 cluster_status(mon_data_set);
4776
4777 /* Unpack constraints if any section will need them
4778 * (tickets may be referenced in constraints but not granted yet,
4779 * and bans need negative location constraints) */
4780 if (show & (mon_show_bans | mon_show_tickets)) {
4781 xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
4782 mon_data_set->input);
4783 unpack_constraints(cib_constraints, mon_data_set);
4784 }
4785
4786 switch (output_format) {
4787 case mon_output_html:
4788 case mon_output_cgi:
4789 if (print_html_status(mon_data_set, output_filename, stonith_history) != 0) {
4790 fprintf(stderr, "Critical: Unable to output html file\n");
4791 clean_up(EX_USAGE);
4792 return FALSE;
4793 }
4794 break;
4795
4796 case mon_output_xml:
4797 print_xml_status(mon_data_set, stonith_history);
4798 break;
4799
4800 case mon_output_monitor:
4801 print_simple_status(mon_data_set, stonith_history);
4802 if (has_warnings) {
4803 clean_up(MON_STATUS_WARN);
4804 return FALSE;
4805 }
4806 break;
4807
4808 case mon_output_plain:
4809 case mon_output_console:
4810 print_status(mon_data_set, stonith_history);
4811 break;
4812
4813 case mon_output_none:
4814 break;
4815 }
4816
4817 stonith_history_free(stonith_history);
4818 stonith_history = NULL;
4819 pe_reset_working_set(mon_data_set);
4820 return TRUE;
4821 }
4822
4823 void
mon_st_callback_event(stonith_t * st,stonith_event_t * e)4824 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
4825 {
4826 if (st->state == stonith_disconnected) {
4827 /* disconnect cib as well and have everything reconnect */
4828 mon_cib_connection_destroy(NULL);
4829 } else {
4830 char *desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
4831 e->operation, e->origin, e->target, pcmk_strerror(e->result),
4832 e->id);
4833
4834 if (snmp_target) {
4835 send_snmp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4836 }
4837 if (crm_mail_to) {
4838 send_smtp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4839 }
4840 if (external_agent) {
4841 send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4842 }
4843 free(desc);
4844 }
4845 }
4846
kick_refresh(gboolean data_updated)4847 void kick_refresh(gboolean data_updated)
4848 {
4849 static int updates = 0;
4850 long now = time(NULL);
4851
4852 if (data_updated) {
4853 updates++;
4854 }
4855
4856 if(refresh_timer == NULL) {
4857 refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
4858 }
4859
4860 /* Refresh
4861 * - immediately if the last update was more than 5s ago
4862 * - every 10 cib-updates
4863 * - at most 2s after the last update
4864 */
4865 if ((now - last_refresh) > (reconnect_msec / 1000)) {
4866 mainloop_set_trigger(refresh_trigger);
4867 mainloop_timer_stop(refresh_timer);
4868 updates = 0;
4869
4870 } else if(updates >= 10) {
4871 mainloop_set_trigger(refresh_trigger);
4872 mainloop_timer_stop(refresh_timer);
4873 updates = 0;
4874
4875 } else {
4876 mainloop_timer_start(refresh_timer);
4877 }
4878 }
4879
4880 void
mon_st_callback_display(stonith_t * st,stonith_event_t * e)4881 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
4882 {
4883 if (st->state == stonith_disconnected) {
4884 /* disconnect cib as well and have everything reconnect */
4885 mon_cib_connection_destroy(NULL);
4886 } else {
4887 print_dot();
4888 kick_refresh(TRUE);
4889 }
4890 }
4891
4892 void
clean_up_connections(void)4893 clean_up_connections(void)
4894 {
4895 if (cib != NULL) {
4896 cib->cmds->signoff(cib);
4897 cib_delete(cib);
4898 cib = NULL;
4899 }
4900
4901 if (st != NULL) {
4902 if (st->state != stonith_disconnected) {
4903 st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
4904 st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
4905 st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
4906 st->cmds->disconnect(st);
4907 }
4908 stonith_api_delete(st);
4909 st = NULL;
4910 }
4911 }
4912
4913 /*
4914 * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
4915 * deallocate memory and show usage-message if requested.
4916 *
4917 * We don't actually return, but nominally returning crm_exit_t allows a usage
4918 * like "return clean_up(exit_code);" which helps static analysis understand the
4919 * code flow.
4920 */
4921 static int
clean_up(int exit_code)4922 clean_up(int exit_code)
4923 {
4924 #if ENABLE_SNMP
4925 netsnmp_session *session = crm_snmp_init(NULL, NULL);
4926
4927 if (session) {
4928 snmp_close(session);
4929 snmp_shutdown("snmpapp");
4930 }
4931 #endif
4932
4933 #if CURSES_ENABLED
4934 if (curses_console_initialized) {
4935 output_format = mon_output_plain;
4936 echo();
4937 nocbreak();
4938 endwin();
4939 curses_console_initialized = FALSE;
4940 }
4941 #endif
4942
4943 clean_up_connections();
4944
4945 free(output_filename);
4946 free(pid_file);
4947
4948 pe_free_working_set(mon_data_set);
4949 mon_data_set = NULL;
4950
4951 if (exit_code == EX_USAGE) {
4952 if (output_format == mon_output_cgi) {
4953 fprintf(stdout, "Content-Type: text/plain\n"
4954 "Status: 500\n\n");
4955 } else {
4956 return crm_help('?', EX_USAGE);
4957 }
4958 }
4959 return crm_exit(exit_code);
4960 }
4961