diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h index 13eb0e7ef7..138b0dbd81 100644 --- a/include/crm/msg_xml.h +++ b/include/crm/msg_xml.h @@ -1,317 +1,318 @@ /* * Copyright 2004-2024 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. */ #ifndef PCMK__CRM_MSG_XML__H # define PCMK__CRM_MSG_XML__H # include #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus extern "C" { #endif /* This file defines constants for various XML syntax (mainly element and * attribute names). * * For consistency, new constants should start with "PCMK_", followed by: * * "XE" for XML element names * * "XA" for XML attribute names * * "OPT" for cluster option (property) names * * "META" for meta-attribute names * * "VALUE" for enumerated values for various options * * Old names that don't follow this policy should eventually be deprecated and * replaced with names that do. * * Symbols should be public if the user may specify them somewhere (especially * the CIB) or if they're part of a well-defined structure that a user may need * to parse. They should be internal if they're used only internally to * Pacemaker (such as daemon IPC/CPG message XML). * * Constants belong in the following locations: * * Public "XE" and "XA": msg_xml.h * * Internal "XE" and "XA": crm_internal.h * * Public "OPT", "META", and "VALUE": options.h * * Internal "OPT", "META", and "VALUE": options_internal.h * * For meta-attributes that can be specified as either XML attributes or nvpair * names, use "META" unless using both "XA" and "META" constants adds clarity. * An example is operation attributes, which can be specified either as * attributes of the PCMK_XE_OP element or as nvpairs in a meta-attribute set * beneath the PCMK_XE_OP element. */ /* * XML elements */ #define PCMK_XE_ACL_GROUP "acl_group" #define PCMK_XE_ACL_PERMISSION "acl_permission" #define PCMK_XE_ACL_ROLE "acl_role" #define PCMK_XE_ACL_TARGET "acl_target" #define PCMK_XE_ACLS "acls" #define PCMK_XE_ACTION "action" #define PCMK_XE_ACTIONS "actions" #define PCMK_XE_ALERT "alert" #define PCMK_XE_ALERTS "alerts" #define PCMK_XE_ATTRIBUTE "attribute" #define PCMK_XE_BUNDLE "bundle" #define PCMK_XE_CHANGE "change" #define PCMK_XE_CHANGE_ATTR "change-attr" #define PCMK_XE_CHANGE_LIST "change-list" #define PCMK_XE_CHANGE_RESULT "change-result" #define PCMK_XE_CIB "cib" #define PCMK_XE_CLONE "clone" #define PCMK_XE_CLUSTER_PROPERTY_SET "cluster_property_set" #define PCMK_XE_CONFIGURATION "configuration" #define PCMK_XE_CONSTRAINTS "constraints" #define PCMK_XE_CONTENT "content" #define PCMK_XE_CRM_CONFIG "crm_config" #define PCMK_XE_DATE_EXPRESSION "date_expression" #define PCMK_XE_DATE_SPEC "date_spec" #define PCMK_XE_DIFF "diff" #define PCMK_XE_DURATION "duration" #define PCMK_XE_EXPRESSION "expression" #define PCMK_XE_FEATURE "feature" #define PCMK_XE_FEATURES "features" #define PCMK_XE_FENCE_EVENT "fence_event" +#define PCMK_XE_FENCE_HISTORY "fence_history" #define PCMK_XE_FENCING_LEVEL "fencing-level" #define PCMK_XE_FENCING_TOPOLOGY "fencing-topology" #define PCMK_XE_GROUP "group" #define PCMK_XE_INSTANCE_ATTRIBUTES "instance_attributes" #define PCMK_XE_LAST_FENCED "last-fenced" #define PCMK_XE_LONGDESC "longdesc" #define PCMK_XE_META_ATTRIBUTES "meta_attributes" #define PCMK_XE_NETWORK "network" #define PCMK_XE_NODE "node" #define PCMK_XE_NODES "nodes" #define PCMK_XE_NVPAIR "nvpair" #define PCMK_XE_OBJ_REF "obj_ref" #define PCMK_XE_OP "op" #define PCMK_XE_OP_DEFAULTS "op_defaults" #define PCMK_XE_OPERATION "operation" #define PCMK_XE_OP_EXPRESSION "op_expression" #define PCMK_XE_OPTION "option" #define PCMK_XE_PACEMAKERD "pacemakerd" #define PCMK_XE_PARAMETER "parameter" #define PCMK_XE_PARAMETERS "parameters" #define PCMK_XE_PORT_MAPPING "port-mapping" #define PCMK_XE_POSITION "position" #define PCMK_XE_PRIMITIVE "primitive" #define PCMK_XE_RECIPIENT "recipient" #define PCMK_XE_RESOURCE "resource" #define PCMK_XE_RESOURCE_AGENT "resource-agent" #define PCMK_XE_RESOURCE_REF "resource_ref" #define PCMK_XE_RESOURCE_SET "resource_set" #define PCMK_XE_RESOURCES "resources" #define PCMK_XE_ROLE "role" #define PCMK_XE_RSC_ACTION "rsc_action" #define PCMK_XE_RSC_COLOCATION "rsc_colocation" #define PCMK_XE_RSC_DEFAULTS "rsc_defaults" #define PCMK_XE_RSC_EXPRESSION "rsc_expression" #define PCMK_XE_RSC_LOCATION "rsc_location" #define PCMK_XE_RSC_ORDER "rsc_order" #define PCMK_XE_RSC_TICKET "rsc_ticket" #define PCMK_XE_RULE "rule" #define PCMK_XE_SELECT "select" #define PCMK_XE_SELECT_ATTRIBUTES "select_attributes" #define PCMK_XE_SELECT_FENCING "select_fencing" #define PCMK_XE_SELECT_NODES "select_nodes" #define PCMK_XE_SELECT_RESOURCES "select_resources" #define PCMK_XE_SHORTDESC "shortdesc" #define PCMK_XE_SOURCE "source" #define PCMK_XE_STATUS "status" #define PCMK_XE_STORAGE "storage" #define PCMK_XE_STORAGE_MAPPING "storage-mapping" #define PCMK_XE_TAG "tag" #define PCMK_XE_TAGS "tags" #define PCMK_XE_TARGET "target" #define PCMK_XE_TEMPLATE "template" #define PCMK_XE_TICKET "ticket" #define PCMK_XE_TICKETS "tickets" #define PCMK_XE_UTILIZATION "utilization" #define PCMK_XE_VERSION "version" /* * XML attributes */ #define PCMK_XA_ACTION "action" #define PCMK_XA_ACTIVE "active" #define PCMK_XA_ADD_HOST "add-host" #define PCMK_XA_ADMIN_EPOCH "admin_epoch" #define PCMK_XA_ATTRIBUTE "attribute" #define PCMK_XA_AUTHOR "author" #define PCMK_XA_BLOCKED "blocked" #define PCMK_XA_BOOLEAN_OP "boolean-op" #define PCMK_XA_BUILD "build" #define PCMK_XA_CIB_LAST_WRITTEN "cib-last-written" #define PCMK_XA_CLASS "class" #define PCMK_XA_CLIENT "client" #define PCMK_XA_COMPLETED "completed" #define PCMK_XA_CONTROL_PORT "control-port" #define PCMK_XA_CRM_DEBUG_ORIGIN "crm-debug-origin" #define PCMK_XA_CRM_FEATURE_SET "crm_feature_set" #define PCMK_XA_CRM_TIMESTAMP "crm-timestamp" #define PCMK_XA_DAYS "days" #define PCMK_XA_DC_UUID "dc-uuid" #define PCMK_XA_DEFAULT "default" #define PCMK_XA_DELEGATE "delegate" #define PCMK_XA_DESCRIPTION "description" #define PCMK_XA_DEST "dest" #define PCMK_XA_DEVICES "devices" #define PCMK_XA_DISABLED "disabled" #define PCMK_XA_DURATION "duration" #define PCMK_XA_END "end" #define PCMK_XA_EPOCH "epoch" #define PCMK_XA_EXEC_TIME "exec-time" #define PCMK_XA_EXECUTION_DATE "execution-date" #define PCMK_XA_EXIT_REASON "exit-reason" #define PCMK_XA_EXPECTED_UP "expected_up" #define PCMK_XA_EXTENDED_STATUS "extended-status" #define PCMK_XA_FAILED "failed" #define PCMK_XA_FAILURE_IGNORED "failure_ignored" #define PCMK_XA_FEATURE_SET "feature_set" #define PCMK_XA_FEATURES "features" #define PCMK_XA_FIRST "first" #define PCMK_XA_FIRST_ACTION "first-action" #define PCMK_XA_FORMAT "format" #define PCMK_XA_HAVE_QUORUM "have-quorum" #define PCMK_XA_HEALTH "health" #define PCMK_XA_HOST "host" #define PCMK_XA_HOST_INTERFACE "host-interface" #define PCMK_XA_HOST_NETMASK "host-netmask" #define PCMK_XA_HOURS "hours" #define PCMK_XA_ID "id" #define PCMK_XA_ID_REF "id-ref" #define PCMK_XA_IMAGE "image" #define PCMK_XA_INDEX "index" #define PCMK_XA_INFLUENCE "influence" #define PCMK_XA_INTERNAL_PORT "internal-port" #define PCMK_XA_IP_RANGE_START "ip-range-start" #define PCMK_XA_IS_DC "is_dc" #define PCMK_XA_KIND "kind" #define PCMK_XA_LANG "lang" #define PCMK_XA_LAST_GRANTED "last-granted" #define PCMK_XA_LAST_RC_CHANGE "last-rc-change" #define PCMK_XA_LOCKED_TO "locked_to" #define PCMK_XA_LOSS_POLICY "loss-policy" #define PCMK_XA_MAINTENANCE "maintenance" #define PCMK_XA_MANAGED "managed" #define PCMK_XA_MINUTES "minutes" #define PCMK_XA_MIXED_VERSION "mixed_version" #define PCMK_XA_MONTHDAYS "monthdays" #define PCMK_XA_MONTHS "months" #define PCMK_XA_MULTI_STATE "multi_state" #define PCMK_XA_NAME "name" #define PCMK_XA_NETWORK "network" #define PCMK_XA_NEXT_ROLE "next-role" #define PCMK_XA_NO_QUORUM_PANIC "no-quorum-panic" #define PCMK_XA_NODE "node" #define PCMK_XA_NODE_ATTRIBUTE "node-attribute" #define PCMK_XA_NODES_RUNNING_ON "nodes_running_on" #define PCMK_XA_NUM_UPDATES "num_updates" #define PCMK_XA_NUMBER "number" #define PCMK_XA_OBJECT_TYPE "object-type" #define PCMK_XA_ONLINE "online" #define PCMK_XA_OP "op" #define PCMK_XA_OPERATION "operation" #define PCMK_XA_OPTIONS "options" #define PCMK_XA_ORIGIN "origin" #define PCMK_XA_ORPHANED "orphaned" #define PCMK_XA_PATH "path" #define PCMK_XA_PENDING "pending" #define PCMK_XA_PORT "port" #define PCMK_XA_PRESENT "present" #define PCMK_XA_PROGRAM "program" #define PCMK_XA_PROMOTED_MAX "promoted-max" #define PCMK_XA_PROVIDER "provider" #define PCMK_XA_QUEUE_TIME "queue-time" #define PCMK_XA_RANGE "range" #define PCMK_XA_REASON "reason" #define PCMK_XA_REFERENCE "reference" #define PCMK_XA_RELOADABLE "reloadable" #define PCMK_XA_REMOTE_CLEAR_PORT "remote-clear-port" #define PCMK_XA_REMOTE_TLS_PORT "remote-tls-port" #define PCMK_XA_REPLICAS "replicas" #define PCMK_XA_REPLICAS_PER_HOST "replicas-per-host" #define PCMK_XA_REQUEST "request" #define PCMK_XA_REQUIRE_ALL "require-all" #define PCMK_XA_RESOURCE "resource" #define PCMK_XA_RESOURCE_AGENT "resource_agent" #define PCMK_XA_RESOURCE_DISCOVERY "resource-discovery" #define PCMK_XA_RESOURCES_RUNNING "resources_running" #define PCMK_XA_RESULT "result" #define PCMK_XA_ROLE "role" #define PCMK_XA_RSC "rsc" #define PCMK_XA_RSC_PATTERN "rsc-pattern" #define PCMK_XA_RSC_ROLE "rsc-role" #define PCMK_XA_RUN_COMMAND "run-command" #define PCMK_XA_RUNNING "running" #define PCMK_XA_SCOPE "scope" #define PCMK_XA_SCORE "score" #define PCMK_XA_SCORE_ATTRIBUTE "score-attribute" #define PCMK_XA_SEQUENTIAL "sequential" #define PCMK_XA_SECONDS "seconds" #define PCMK_XA_SHUTDOWN "shutdown" #define PCMK_XA_SOURCE "source" #define PCMK_XA_SOURCE_DIR "source-dir" #define PCMK_XA_SOURCE_DIR_ROOT "source-dir-root" #define PCMK_XA_STANDBY "standby" #define PCMK_XA_STANDBY_ONFAIL "standby_onfail" #define PCMK_XA_START "start" #define PCMK_XA_STATUS "status" #define PCMK_XA_SYMMETRICAL "symmetrical" #define PCMK_XA_TARGET "target" #define PCMK_XA_TARGET_ATTRIBUTE "target-attribute" #define PCMK_XA_TARGET_DIR "target-dir" #define PCMK_XA_TARGET_PATTERN "target-pattern" #define PCMK_XA_TARGET_ROLE "target_role" #define PCMK_XA_TARGET_VALUE "target-value" #define PCMK_XA_TEMPLATE "template" #define PCMK_XA_TICKET "ticket" #define PCMK_XA_TIME "time" #define PCMK_XA_THEN "then" #define PCMK_XA_THEN_ACTION "then-action" #define PCMK_XA_TYPE "type" #define PCMK_XA_UNAME "uname" #define PCMK_XA_UNCLEAN "unclean" #define PCMK_XA_UNIQUE "unique" #define PCMK_XA_UPDATE_CLIENT "update-client" #define PCMK_XA_UPDATE_ORIGIN "update-origin" #define PCMK_XA_UPDATE_USER "update-user" #define PCMK_XA_USER "user" #define PCMK_XA_VALIDATE_WITH "validate-with" #define PCMK_XA_VALUE "value" #define PCMK_XA_VALUE_SOURCE "value-source" #define PCMK_XA_VERSION "version" #define PCMK_XA_WEEKDAYS "weekdays" #define PCMK_XA_WEEKS "weeks" #define PCMK_XA_WEEKYEARS "weekyears" #define PCMK_XA_WHEN "when" #define PCMK_XA_WITH_QUORUM "with_quorum" #define PCMK_XA_WITH_RSC "with-rsc" #define PCMK_XA_WITH_RSC_ROLE "with-rsc-role" #define PCMK_XA_XPATH "xpath" #define PCMK_XA_YEARDAYS "yeardays" #define PCMK_XA_YEARS "years" # define ID(x) crm_element_value(x, PCMK_XA_ID) #ifdef __cplusplus } #endif #endif diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index f6e080ce00..5beed0c8af 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,562 +1,562 @@ /* * Copyright 2019-2024 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 #include #include #include #include #include #include #include #include #include #include #include // pcmk__xml2fd static gboolean legacy_xml = FALSE; static gboolean simple_list = FALSE; static gboolean substitute = FALSE; GOptionEntry pcmk__xml_output_entries[] = { { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml, NULL, NULL }, { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list, NULL, NULL }, { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute, NULL, NULL }, { NULL } }; typedef struct subst_s { const char *from; const char *to; } subst_t; static subst_t substitutions[] = { { "Active Resources", PCMK_XE_RESOURCES, }, { "Assignment Scores", "allocations", }, { "Assignment Scores and Utilization Information", "allocations_utilizations", }, { "Cluster Summary", "summary", }, { "Current cluster status", "cluster_status", }, { "Executing Cluster Transition", "transition", }, { "Failed Resource Actions", "failures", }, { "Fencing History", - "fence_history", }, + PCMK_XE_FENCE_HISTORY, }, { "Full List of Resources", PCMK_XE_RESOURCES, }, { "Inactive Resources", PCMK_XE_RESOURCES, }, { "Migration Summary", "node_history", }, { "Negative Location Constraints", "bans", }, { "Node Attributes", "node_attributes", }, { "Operations", "node_history", }, { "Resource Config", "resource_config", }, { "Resource Operations", "operations", }, { "Revised Cluster Status", "revised_cluster_status", }, { "Transition Summary", PCMK_XE_ACTIONS, }, { "Utilization Information", "utilizations", }, { NULL, NULL } }; /* The first several elements of this struct must be the same as the first * several elements of private_data_s in lib/common/output_html.c. That * struct gets passed to a bunch of the pcmk__output_xml_* functions which * assume an XML private_data_s. Keeping them laid out the same means this * still works. */ typedef struct private_data_s { /* Begin members that must match the HTML version */ xmlNode *root; GQueue *parent_q; GSList *errors; /* End members that must match the HTML version */ bool legacy_xml; } private_data_t; static void xml_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; free_xml(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); out->priv = NULL; } static bool xml_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); /* If xml_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } if (legacy_xml) { priv->root = create_xml_node(NULL, "crm_mon"); crm_xml_add(priv->root, PCMK_XA_VERSION, PACEMAKER_VERSION); } else { priv->root = create_xml_node(NULL, "pacemaker-result"); crm_xml_add(priv->root, "api-version", PCMK__API_VERSION); crm_xml_add(priv->root, PCMK_XA_REQUEST, out->request); } priv->parent_q = g_queue_new(); priv->errors = NULL; g_queue_push_tail(priv->parent_q, priv->root); /* Copy this from the file-level variable. This means that it is only settable * as a command line option, and that pcmk__output_new must be called after all * command line processing is completed. */ priv->legacy_xml = legacy_xml; return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; xmlNodePtr node = (xmlNodePtr) user_data; pcmk_create_xml_text_node(node, "error", str); } static void xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = NULL; xmlNodePtr node; CRM_ASSERT(out != NULL); priv = out->priv; /* If root is NULL, xml_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } if (legacy_xml) { GSList *node = priv->errors; if (exit_status != CRM_EX_OK) { fprintf(stderr, "%s\n", crm_exit_str(exit_status)); } while (node != NULL) { fprintf(stderr, "%s\n", (char *) node->data); node = node->next; } } else { char *rc_as_str = pcmk__itoa(exit_status); node = create_xml_node(priv->root, PCMK_XE_STATUS); pcmk__xe_set_props(node, "code", rc_as_str, "message", crm_exit_str(exit_status), NULL); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = create_xml_node(node, "errors"); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } free(rc_as_str); } if (print) { pcmk__xml2fd(fileno(out->dest), priv->root); } if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } } static void xml_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); xml_free_priv(out); xml_init(out); } static void xml_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { xmlNodePtr node, child_node; char *rc_as_str = NULL; CRM_ASSERT(out != NULL); rc_as_str = pcmk__itoa(exit_status); node = pcmk__output_xml_create_parent(out, "command", "code", rc_as_str, NULL); if (proc_stdout != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stdout); crm_xml_add(child_node, PCMK_XA_SOURCE, "stdout"); } if (proc_stderr != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stderr); crm_xml_add(child_node, PCMK_XA_SOURCE, "stderr"); } free(rc_as_str); } static void xml_version(pcmk__output_t *out, bool extended) { const char *author = "Andrew Beekhof and the Pacemaker project " "contributors"; CRM_ASSERT(out != NULL); pcmk__output_create_xml_node(out, PCMK_XE_VERSION, PCMK_XA_PROGRAM, "Pacemaker", PCMK_XA_VERSION, PACEMAKER_VERSION, PCMK_XA_AUTHOR, author, PCMK_XA_BUILD, BUILD_VERSION, PCMK_XA_FEATURES, CRM_FEATURES, NULL); } G_GNUC_PRINTF(2, 3) static void xml_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static int xml_info(pcmk__output_t *out, const char *format, ...) { return pcmk_rc_no_output; } static void xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr parent = NULL; xmlNodePtr cdata_node = NULL; CRM_ASSERT(out != NULL); parent = pcmk__output_create_xml_node(out, name, NULL); if (parent == NULL) { return; } cdata_node = xmlNewCDataBlock(parent->doc, (pcmkXmlStr) buf, strlen(buf)); xmlAddChild(parent, cdata_node); } G_GNUC_PRINTF(4, 5) static void xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { va_list ap; char *name = NULL; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (substitute) { for (subst_t *s = substitutions; s->from != NULL; s++) { if (!strcmp(s->from, buf)) { name = g_strdup(s->to); break; } } } if (name == NULL) { name = g_ascii_strdown(buf, -1); } if (legacy_xml || simple_list) { pcmk__output_xml_create_parent(out, name, NULL); } else { pcmk__output_xml_create_parent(out, "list", PCMK_XA_NAME, name, NULL); } g_free(name); free(buf); } G_GNUC_PRINTF(3, 4) static void xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { xmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "item", buf); if (name != NULL) { crm_xml_add(item_node, PCMK_XA_NAME, name); } free(buf); } static void xml_increment_list(pcmk__output_t *out) { /* This function intentially left blank */ } static void xml_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (priv->legacy_xml || simple_list) { g_queue_pop_tail(priv->parent_q); } else { char *buf = NULL; xmlNodePtr node; node = g_queue_pop_tail(priv->parent_q); buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); crm_xml_add(node, "count", buf); free(buf); } } static bool xml_is_quiet(pcmk__output_t *out) { return false; } static void xml_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } static void xml_progress(pcmk__output_t *out, bool end) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_xml_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "xml"; retval->request = pcmk__quote_cmdline(argv); retval->init = xml_init; retval->free_priv = xml_free_priv; retval->finish = xml_finish; retval->reset = xml_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = xml_subprocess_output; retval->version = xml_version; retval->info = xml_info; retval->transient = xml_info; retval->err = xml_err; retval->output_xml = xml_output_xml; retval->begin_list = xml_begin_list; retval->list_item = xml_list_item; retval->increment_list = xml_increment_list; retval->end_list = xml_end_list; retval->is_quiet = xml_is_quiet; retval->spacer = xml_spacer; retval->progress = xml_progress; retval->prompt = pcmk__text_prompt; return retval; } xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) { va_list args; xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); node = pcmk__output_create_xml_node(out, name, NULL); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); pcmk__output_xml_push_parent(out, node); return node; } void pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node) { private_data_t *priv = NULL; xmlNodePtr parent = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(node != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; parent = g_queue_peek_tail(priv->parent_q); // Shouldn't happen unless the caller popped priv->root CRM_CHECK(parent != NULL, return); add_node_copy(parent, node); } xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) { xmlNodePtr node = NULL; private_data_t *priv = NULL; va_list args; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); priv = out->priv; node = create_xml_node(g_queue_peek_tail(priv->parent_q), name); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); return node; } xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) { xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); node = pcmk__output_create_xml_node(out, name, NULL); xmlNodeSetContent(node, (pcmkXmlStr) content); return node; } void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(parent != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; g_queue_push_tail(priv->parent_q, parent); } void pcmk__output_xml_pop_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); g_queue_pop_tail(priv->parent_q); } xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); priv = out->priv; /* If queue is empty NULL will be returned */ return g_queue_peek_tail(priv->parent_q); } diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c index 276a00b064..7c54101c80 100644 --- a/lib/fencing/st_output.c +++ b/lib/fencing/st_output.c @@ -1,605 +1,605 @@ /* * Copyright 2019-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include /*! * \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, PCMK_XA_DELEGATE "=", history->delegate, ", ", NULL); } // Add information about originator pcmk__g_strcat(str, PCMK_XA_CLIENT "=", history->client, ", " PCMK_XA_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", + pcmk__output_create_xml_node(out, PCMK_XE_FENCE_HISTORY, PCMK_XA_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, PCMK_XE_LAST_FENCED, PCMK_XA_TARGET, target, PCMK_XA_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 = NULL; node = pcmk__output_create_xml_node(out, PCMK_XE_FENCE_EVENT, PCMK_XA_ACTION, event->action, PCMK_XA_TARGET, event->target, PCMK_XA_CLIENT, event->client, PCMK_XA_ORIGIN, event->origin, NULL); switch (event->state) { case st_failed: pcmk__xe_set_props(node, PCMK_XA_STATUS, PCMK_VALUE_FAILED, PCMK_XA_EXIT_REASON, event->exit_reason, NULL); break; case st_done: crm_xml_add(node, PCMK_XA_STATUS, PCMK_VALUE_SUCCESS); break; default: { char *state = pcmk__itoa(event->state); pcmk__xe_set_props(node, PCMK_XA_STATUS, PCMK_VALUE_PENDING, PCMK_XA_EXTENDED_STATUS, state, NULL); free(state); break; } } if (event->delegate != NULL) { crm_xml_add(node, PCMK_XA_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, PCMK_XA_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); }