summaryrefslogtreecommitdiffstats
path: root/lib/fencing/st_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/fencing/st_output.c')
-rw-r--r--lib/fencing/st_output.c600
1 files changed, 600 insertions, 0 deletions
diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c
new file mode 100644
index 0000000..a0ce8e7
--- /dev/null
+++ b/lib/fencing/st_output.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2019-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/msg_xml.h>
+#include <crm/common/iso8601.h>
+#include <crm/common/util.h>
+#include <crm/common/xml.h>
+#include <crm/common/output.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/xml_internal.h>
+#include <crm/fencing/internal.h>
+#include <crm/pengine/internal.h>
+
+/*!
+ * \internal
+ * \brief Convert seconds and nanoseconds to a date/time/time-zone string
+ *
+ * \param[in] sec Seconds
+ * \param[in] nsec Nanoseconds
+ * \param[in] show_usec Whether to show time in microseconds resolution (if
+ * false, use seconds resolution)
+ *
+ * \return A string representation of \p sec and \nsec
+ *
+ * \note The caller is responsible for freeing the return value using \p free().
+ */
+static char *
+timespec_string(time_t sec, long nsec, bool show_usec) {
+ const struct timespec ts = {
+ .tv_sec = sec,
+ .tv_nsec = nsec,
+ };
+
+ return pcmk__timespec2str(&ts,
+ crm_time_log_date
+ |crm_time_log_timeofday
+ |crm_time_log_with_timezone
+ |(show_usec? crm_time_usecs : 0));
+}
+
+/*!
+ * \internal
+ * \brief Return a status-friendly description of fence history entry state
+ *
+ * \param[in] history Fence history entry to describe
+ *
+ * \return One-word description of history entry state
+ * \note This is similar to stonith_op_state_str() except user-oriented (i.e.
+ * for cluster status) instead of developer-oriented (for debug logs).
+ */
+static const char *
+state_str(const stonith_history_t *history)
+{
+ switch (history->state) {
+ case st_failed: return "failed";
+ case st_done: return "successful";
+ default: return "pending";
+ }
+}
+
+/*!
+ * \internal
+ * \brief Create a description of a fencing history entry for status displays
+ *
+ * \param[in] history Fencing history entry to describe
+ * \param[in] full_history Whether this is for full or condensed history
+ * \param[in] later_succeeded Node that a later equivalent attempt succeeded
+ * from, or NULL if none
+ * \param[in] show_opts Flag group of pcmk_show_opt_e
+ *
+ * \return Newly created string with fencing history entry description
+ *
+ * \note The caller is responsible for freeing the return value with g_free().
+ * \note This is similar to stonith__event_description(), except this is used
+ * for history entries (stonith_history_t) in status displays rather than
+ * event notifications (stonith_event_t) in log messages.
+ */
+gchar *
+stonith__history_description(const stonith_history_t *history,
+ bool full_history, const char *later_succeeded,
+ uint32_t show_opts)
+{
+ GString *str = g_string_sized_new(256); // Generous starting size
+ char *completed_time_s = NULL;
+
+ if ((history->state == st_failed) || (history->state == st_done)) {
+ completed_time_s = timespec_string(history->completed,
+ history->completed_nsec, true);
+ }
+
+ pcmk__g_strcat(str,
+ stonith_action_str(history->action), " of ", history->target,
+ NULL);
+
+ if (!pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
+ // More human-friendly
+ if (((history->state == st_failed) || (history->state == st_done))
+ && (history->delegate != NULL)) {
+
+ pcmk__g_strcat(str, " by ", history->delegate, NULL);
+ }
+ pcmk__g_strcat(str, " for ", history->client, "@", history->origin,
+ NULL);
+ if (!full_history) {
+ g_string_append(str, " last"); // For example, "last failed at ..."
+ }
+ }
+
+ pcmk__add_word(&str, 0, state_str(history));
+
+ // For failed actions, add exit reason if available
+ if ((history->state == st_failed) && (history->exit_reason != NULL)) {
+ pcmk__g_strcat(str, " (", history->exit_reason, ")", NULL);
+ }
+
+ if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
+ // More technical
+ g_string_append(str, ": ");
+
+ // For completed actions, add delegate if available
+ if (((history->state == st_failed) || (history->state == st_done))
+ && (history->delegate != NULL)) {
+
+ pcmk__g_strcat(str, "delegate=", history->delegate, ", ", NULL);
+ }
+
+ // Add information about originator
+ pcmk__g_strcat(str,
+ "client=", history->client, ", origin=", history->origin,
+ NULL);
+
+ // For completed actions, add completion time
+ if (completed_time_s != NULL) {
+ if (full_history) {
+ g_string_append(str, ", completed");
+ } else if (history->state == st_failed) {
+ g_string_append(str, ", last-failed");
+ } else {
+ g_string_append(str, ", last-successful");
+ }
+ pcmk__g_strcat(str, "='", completed_time_s, "'", NULL);
+ }
+ } else if (completed_time_s != NULL) {
+ // More human-friendly
+ pcmk__g_strcat(str, " at ", completed_time_s, NULL);
+ }
+
+ if ((history->state == st_failed) && (later_succeeded != NULL)) {
+ pcmk__g_strcat(str,
+ " (a later attempt from ", later_succeeded,
+ " succeeded)", NULL);
+ }
+
+ free(completed_time_s);
+ return g_string_free(str, FALSE);
+}
+
+PCMK__OUTPUT_ARGS("failed-fencing-list", "stonith_history_t *", "GList *",
+ "uint32_t", "uint32_t", "bool")
+static int
+failed_history(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *history = va_arg(args, stonith_history_t *);
+ GList *only_node = va_arg(args, GList *);
+ uint32_t section_opts = va_arg(args, uint32_t);
+ uint32_t show_opts = va_arg(args, uint32_t);
+ bool print_spacer = va_arg(args, int);
+
+ int rc = pcmk_rc_no_output;
+
+ for (stonith_history_t *hp = history; hp; hp = hp->next) {
+ if (hp->state != st_failed) {
+ continue;
+ }
+
+ if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
+ continue;
+ }
+
+ PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Fencing Actions");
+ out->message(out, "stonith-event", hp,
+ pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
+ false, stonith__later_succeeded(hp, history), show_opts);
+ out->increment_list(out);
+ }
+
+ PCMK__OUTPUT_LIST_FOOTER(out, rc);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("fencing-list", "stonith_history_t *", "GList *", "uint32_t",
+ "uint32_t", "bool")
+static int
+stonith_history(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *history = va_arg(args, stonith_history_t *);
+ GList *only_node = va_arg(args, GList *);
+ uint32_t section_opts = va_arg(args, uint32_t);
+ uint32_t show_opts = va_arg(args, uint32_t);
+ bool print_spacer = va_arg(args, int);
+
+ int rc = pcmk_rc_no_output;
+
+ for (stonith_history_t *hp = history; hp; hp = hp->next) {
+ if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
+ continue;
+ }
+
+ if (hp->state != st_failed) {
+ PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History");
+ out->message(out, "stonith-event", hp,
+ pcmk_all_flags_set(section_opts,
+ pcmk_section_fencing_all),
+ false, stonith__later_succeeded(hp, history), show_opts);
+ out->increment_list(out);
+ }
+ }
+
+ PCMK__OUTPUT_LIST_FOOTER(out, rc);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *",
+ "GList *", "uint32_t", "uint32_t", "bool")
+static int
+full_history(pcmk__output_t *out, va_list args)
+{
+ crm_exit_t history_rc G_GNUC_UNUSED = va_arg(args, crm_exit_t);
+ stonith_history_t *history = va_arg(args, stonith_history_t *);
+ GList *only_node = va_arg(args, GList *);
+ uint32_t section_opts = va_arg(args, uint32_t);
+ uint32_t show_opts = va_arg(args, uint32_t);
+ bool print_spacer = va_arg(args, int);
+
+ int rc = pcmk_rc_no_output;
+
+ for (stonith_history_t *hp = history; hp; hp = hp->next) {
+ if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
+ continue;
+ }
+
+ PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History");
+ out->message(out, "stonith-event", hp,
+ pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
+ false, stonith__later_succeeded(hp, history), show_opts);
+ out->increment_list(out);
+ }
+
+ PCMK__OUTPUT_LIST_FOOTER(out, rc);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *",
+ "GList *", "uint32_t", "uint32_t", "bool")
+static int
+full_history_xml(pcmk__output_t *out, va_list args)
+{
+ crm_exit_t history_rc = va_arg(args, crm_exit_t);
+ stonith_history_t *history = va_arg(args, stonith_history_t *);
+ GList *only_node = va_arg(args, GList *);
+ uint32_t section_opts = va_arg(args, uint32_t);
+ uint32_t show_opts = va_arg(args, uint32_t);
+ bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
+
+ int rc = pcmk_rc_no_output;
+
+ if (history_rc == 0) {
+ for (stonith_history_t *hp = history; hp; hp = hp->next) {
+ if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
+ continue;
+ }
+
+ PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Fencing History");
+ out->message(out, "stonith-event", hp,
+ pcmk_all_flags_set(section_opts,
+ pcmk_section_fencing_all),
+ false, stonith__later_succeeded(hp, history), show_opts);
+ out->increment_list(out);
+ }
+
+ PCMK__OUTPUT_LIST_FOOTER(out, rc);
+ } else {
+ char *rc_s = pcmk__itoa(history_rc);
+
+ pcmk__output_create_xml_node(out, "fence_history",
+ "status", rc_s,
+ NULL);
+ free(rc_s);
+
+ rc = pcmk_rc_ok;
+ }
+
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
+static int
+last_fenced_html(pcmk__output_t *out, va_list args) {
+ const char *target = va_arg(args, const char *);
+ time_t when = va_arg(args, time_t);
+
+ if (when) {
+ char *buf = crm_strdup_printf("Node %s last fenced at: %s", target, ctime(&when));
+ pcmk__output_create_html_node(out, "div", NULL, NULL, buf);
+ free(buf);
+ return pcmk_rc_ok;
+ } else {
+ return pcmk_rc_no_output;
+ }
+}
+
+PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
+static int
+last_fenced_text(pcmk__output_t *out, va_list args) {
+ const char *target = va_arg(args, const char *);
+ time_t when = va_arg(args, time_t);
+
+ if (when) {
+ pcmk__indented_printf(out, "Node %s last fenced at: %s", target, ctime(&when));
+ } else {
+ pcmk__indented_printf(out, "Node %s has never been fenced\n", target);
+ }
+
+ return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
+static int
+last_fenced_xml(pcmk__output_t *out, va_list args) {
+ const char *target = va_arg(args, const char *);
+ time_t when = va_arg(args, time_t);
+
+ if (when) {
+ char *buf = timespec_string(when, 0, false);
+
+ pcmk__output_create_xml_node(out, "last-fenced",
+ "target", target,
+ "when", buf,
+ NULL);
+
+ free(buf);
+ return pcmk_rc_ok;
+ } else {
+ return pcmk_rc_no_output;
+ }
+}
+
+PCMK__OUTPUT_ARGS("pending-fencing-list", "stonith_history_t *", "GList *",
+ "uint32_t", "uint32_t", "bool")
+static int
+pending_actions(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *history = va_arg(args, stonith_history_t *);
+ GList *only_node = va_arg(args, GList *);
+ uint32_t section_opts = va_arg(args, uint32_t);
+ uint32_t show_opts = va_arg(args, uint32_t);
+ bool print_spacer = va_arg(args, int);
+
+ int rc = pcmk_rc_no_output;
+
+ for (stonith_history_t *hp = history; hp; hp = hp->next) {
+ if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
+ continue;
+ }
+
+ /* Skip the rest of the history after we see a failed/done action */
+ if ((hp->state == st_failed) || (hp->state == st_done)) {
+ break;
+ }
+
+ PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Pending Fencing Actions");
+ out->message(out, "stonith-event", hp,
+ pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
+ false, stonith__later_succeeded(hp, history), show_opts);
+ out->increment_list(out);
+ }
+
+ PCMK__OUTPUT_LIST_FOOTER(out, rc);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
+ "const char *", "uint32_t")
+static int
+stonith_event_html(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *event = va_arg(args, stonith_history_t *);
+ bool full_history = va_arg(args, int);
+ bool completed_only G_GNUC_UNUSED = va_arg(args, int);
+ const char *succeeded = va_arg(args, const char *);
+ uint32_t show_opts = va_arg(args, uint32_t);
+
+ gchar *desc = stonith__history_description(event, full_history, succeeded,
+ show_opts);
+
+ switch(event->state) {
+ case st_done:
+ out->list_item(out, "successful-stonith-event", "%s", desc);
+ break;
+
+ case st_failed:
+ out->list_item(out, "failed-stonith-event", "%s", desc);
+ break;
+
+ default:
+ out->list_item(out, "pending-stonith-event", "%s", desc);
+ break;
+ }
+ g_free(desc);
+ return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
+ "const char *", "uint32_t")
+static int
+stonith_event_text(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *event = va_arg(args, stonith_history_t *);
+ bool full_history = va_arg(args, int);
+ bool completed_only = va_arg(args, int);
+ const char *succeeded = va_arg(args, const char *);
+ uint32_t show_opts = va_arg(args, uint32_t);
+
+ if (completed_only) {
+ pcmk__formatted_printf(out, "%lld\n", (long long) event->completed);
+ } else {
+ gchar *desc = stonith__history_description(event, full_history, succeeded,
+ show_opts);
+
+ pcmk__indented_printf(out, "%s\n", desc);
+ g_free(desc);
+ }
+
+ return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
+ "const char *", "uint32_t")
+static int
+stonith_event_xml(pcmk__output_t *out, va_list args)
+{
+ stonith_history_t *event = va_arg(args, stonith_history_t *);
+ bool full_history G_GNUC_UNUSED = va_arg(args, int);
+ bool completed_only G_GNUC_UNUSED = va_arg(args, int);
+ const char *succeeded G_GNUC_UNUSED = va_arg(args, const char *);
+ uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
+
+ xmlNodePtr node = pcmk__output_create_xml_node(out, "fence_event",
+ "action", event->action,
+ "target", event->target,
+ "client", event->client,
+ "origin", event->origin,
+ NULL);
+
+ switch (event->state) {
+ case st_failed:
+ pcmk__xe_set_props(node, "status", "failed",
+ XML_LRM_ATTR_EXIT_REASON, event->exit_reason,
+ NULL);
+ break;
+
+ case st_done:
+ crm_xml_add(node, "status", "success");
+ break;
+
+ default: {
+ char *state = pcmk__itoa(event->state);
+ pcmk__xe_set_props(node, "status", "pending",
+ "extended-status", state,
+ NULL);
+ free(state);
+ break;
+ }
+ }
+
+ if (event->delegate != NULL) {
+ crm_xml_add(node, "delegate", event->delegate);
+ }
+
+ if ((event->state == st_failed) || (event->state == st_done)) {
+ char *time_s = timespec_string(event->completed, event->completed_nsec,
+ true);
+
+ crm_xml_add(node, "completed", time_s);
+ free(time_s);
+ }
+
+ return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
+ "const char *", "int")
+static int
+validate_agent_html(pcmk__output_t *out, va_list args) {
+ const char *agent = va_arg(args, const char *);
+ const char *device = va_arg(args, const char *);
+ const char *output = va_arg(args, const char *);
+ const char *error_output = va_arg(args, const char *);
+ int rc = va_arg(args, int);
+
+ if (device) {
+ char *buf = crm_strdup_printf("Validation of %s on %s %s", agent, device,
+ rc ? "failed" : "succeeded");
+ pcmk__output_create_html_node(out, "div", NULL, NULL, buf);
+ free(buf);
+ } else {
+ char *buf = crm_strdup_printf("Validation of %s %s", agent,
+ rc ? "failed" : "succeeded");
+ pcmk__output_create_html_node(out, "div", NULL, NULL, buf);
+ free(buf);
+ }
+
+ out->subprocess_output(out, rc, output, error_output);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
+ "const char *", "int")
+static int
+validate_agent_text(pcmk__output_t *out, va_list args) {
+ const char *agent = va_arg(args, const char *);
+ const char *device = va_arg(args, const char *);
+ const char *output = va_arg(args, const char *);
+ const char *error_output = va_arg(args, const char *);
+ int rc = va_arg(args, int);
+
+ if (device) {
+ pcmk__indented_printf(out, "Validation of %s on %s %s\n", agent, device,
+ rc ? "failed" : "succeeded");
+ } else {
+ pcmk__indented_printf(out, "Validation of %s %s\n", agent,
+ rc ? "failed" : "succeeded");
+ }
+
+ out->subprocess_output(out, rc, output, error_output);
+ return rc;
+}
+
+PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
+ "const char *", "int")
+static int
+validate_agent_xml(pcmk__output_t *out, va_list args) {
+ const char *agent = va_arg(args, const char *);
+ const char *device = va_arg(args, const char *);
+ const char *output = va_arg(args, const char *);
+ const char *error_output = va_arg(args, const char *);
+ int rc = va_arg(args, int);
+
+ xmlNodePtr node = pcmk__output_create_xml_node(
+ out, "validate", "agent", agent, "valid", pcmk__btoa(rc == pcmk_ok),
+ NULL);
+
+ if (device != NULL) {
+ crm_xml_add(node, "device", device);
+ }
+
+ pcmk__output_xml_push_parent(out, node);
+ out->subprocess_output(out, rc, output, error_output);
+ pcmk__output_xml_pop_parent(out);
+
+ return rc;
+}
+
+static pcmk__message_entry_t fmt_functions[] = {
+ { "failed-fencing-list", "default", failed_history },
+ { "fencing-list", "default", stonith_history },
+ { "full-fencing-list", "default", full_history },
+ { "full-fencing-list", "xml", full_history_xml },
+ { "last-fenced", "html", last_fenced_html },
+ { "last-fenced", "log", last_fenced_text },
+ { "last-fenced", "text", last_fenced_text },
+ { "last-fenced", "xml", last_fenced_xml },
+ { "pending-fencing-list", "default", pending_actions },
+ { "stonith-event", "html", stonith_event_html },
+ { "stonith-event", "log", stonith_event_text },
+ { "stonith-event", "text", stonith_event_text },
+ { "stonith-event", "xml", stonith_event_xml },
+ { "validate", "html", validate_agent_html },
+ { "validate", "log", validate_agent_text },
+ { "validate", "text", validate_agent_text },
+ { "validate", "xml", validate_agent_xml },
+
+ { NULL, NULL, NULL }
+};
+
+void
+stonith__register_messages(pcmk__output_t *out) {
+ pcmk__register_messages(out, fmt_functions);
+}