From d81282f1ac5e1226fbf6cfa1bd239d317e106def Mon Sep 17 00:00:00 2001 From: Oyvind Albrigtsen Date: Tue, 13 Oct 2020 10:08:21 +0200 Subject: [PATCH 1/3] Feature: crmadmin: implement formatted output --- include/crm/crm.h | 2 +- tools/crmadmin.c | 312 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 232 insertions(+), 82 deletions(-) diff --git a/include/crm/crm.h b/include/crm/crm.h index 389b6aa..4eca278 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -51,7 +51,7 @@ extern "C" { * >=3.0.13: Fail counts include operation name and interval * >=3.2.0: DC supports PCMK_LRM_OP_INVALID and PCMK_LRM_OP_NOT_CONNECTED */ -# define CRM_FEATURE_SET "3.6.1" +# define CRM_FEATURE_SET "3.6.2" # define EOS '\0' # define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 0b87d01..14078e6 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -38,7 +39,6 @@ bool need_controld_api = true; bool need_pacemakerd_api = false; bool do_work(pcmk_ipc_api_t *api); -void do_find_node_list(xmlNode *xml_node); static char *ipc_name = NULL; gboolean admin_message_timeout(gpointer data); @@ -55,13 +55,12 @@ static enum { static gboolean BE_VERBOSE = FALSE; static gboolean BASH_EXPORT = FALSE; -static gboolean BE_SILENT = FALSE; static char *dest_node = NULL; static crm_exit_t exit_code = CRM_EX_OK; +pcmk__output_t *out = NULL; struct { - gboolean quiet; gboolean health; gint timeout; } options; @@ -168,6 +167,191 @@ command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError return TRUE; } +PCMK__OUTPUT_ARGS("health", "char *", "char *", "char *", "char *") +static int +health_text(pcmk__output_t *out, va_list args) +{ + char *sys_from = va_arg(args, char *); + char *host_from = va_arg(args, char *); + char *fsa_state = va_arg(args, char *); + char *result = va_arg(args, char *); + + if (!out->is_quiet(out)) { + out->info(out, "Status of %s@%s: %s (%s)", crm_str(sys_from), + crm_str(host_from), crm_str(fsa_state), crm_str(result)); + } else if (fsa_state != NULL) { + out->info(out, "%s", fsa_state); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("health", "char *", "char *", "char *", "char *") +static int +health_xml(pcmk__output_t *out, va_list args) +{ + char *sys_from = va_arg(args, char *); + char *host_from = va_arg(args, char *); + char *fsa_state = va_arg(args, char *); + char *result = va_arg(args, char *); + + xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from)); + xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(host_from)); + xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(fsa_state)); + xmlSetProp(node, (pcmkXmlStr) "result", (pcmkXmlStr) crm_str(result)); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("pacemakerd-health", "char *", "char *", "char *") +static int +pacemakerd_health_text(pcmk__output_t *out, va_list args) +{ + char *sys_from = va_arg(args, char *); + char *state = va_arg(args, char *); + char *last_updated = va_arg(args, char *); + + if (!out->is_quiet(out)) { + out->info(out, "Status of %s: '%s' %s %s", crm_str(sys_from), + crm_str(state), (!pcmk__str_empty(last_updated))? + "last updated":"", crm_str(last_updated)); + } else { + out->info(out, "%s", crm_str(state)); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("pacemakerd-health", "char *", "char *", "char *") +static int +pacemakerd_health_xml(pcmk__output_t *out, va_list args) +{ + char *sys_from = va_arg(args, char *); + char *state = va_arg(args, char *); + char *last_updated = va_arg(args, char *); + + + xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from)); + xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(state)); + xmlSetProp(node, (pcmkXmlStr) "last_updated", (pcmkXmlStr) crm_str(last_updated)); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("dc", "char *") +static int +dc_text(pcmk__output_t *out, va_list args) +{ + char *dc = va_arg(args, char *); + + if (!out->is_quiet(out)) { + out->info(out, "Designated Controller is: %s", crm_str(dc)); + } else if (dc != NULL) { + out->info(out, "%s", dc); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("dc", "char *") +static int +dc_xml(pcmk__output_t *out, va_list args) +{ + char *dc = va_arg(args, char *); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "dc"); + xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(dc)); + + return pcmk_rc_ok; +} + + +PCMK__OUTPUT_ARGS("crmadmin-node-list", "xmlNode *") +static int +crmadmin_node_list(pcmk__output_t *out, va_list args) +{ + xmlNode *xml_node = va_arg(args, xmlNode *); + int found = 0; + xmlNode *node = NULL; + xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); + + out->begin_list(out, NULL, NULL, "Nodes"); + + for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL; + node = crm_next_same_xml(node)) { + const char *node_type = BASH_EXPORT ? NULL : + crm_element_value(node, XML_ATTR_TYPE); + out->message(out, "crmadmin-node", node_type, + crm_str(crm_element_value(node, XML_ATTR_UNAME)), + crm_str(crm_element_value(node, XML_ATTR_ID))); + + found++; + } + // @TODO List Pacemaker Remote nodes that don't have a entry + + out->end_list(out); + + if (found == 0) { + out->info(out, "No nodes configured"); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("crmadmin-node", "char *", "char *", "char *") +static int +crmadmin_node_text(pcmk__output_t *out, va_list args) +{ + char *type = va_arg(args, char *); + char *name = va_arg(args, char *); + char *id = va_arg(args, char *); + + if (BASH_EXPORT) { + out->info(out, "export %s=%s", crm_str(name), crm_str(id)); + } else { + out->info(out, "%s node: %s (%s)", type ? type : "member", + crm_str(name), crm_str(id)); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("crmadmin-node", "char *", "char *", "char *") +static int +crmadmin_node_xml(pcmk__output_t *out, va_list args) +{ + char *type = va_arg(args, char *); + char *name = va_arg(args, char *); + char *id = va_arg(args, char *); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "crmadmin-node"); + xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) (type ? type : "member")); + xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) crm_str(name)); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) crm_str(id)); + + return pcmk_rc_ok; +} + +static pcmk__message_entry_t fmt_functions[] = { + {"health", "default", health_text }, + {"health", "xml", health_xml }, + {"pacemakerd-health", "default", pacemakerd_health_text }, + {"pacemakerd-health", "xml", pacemakerd_health_xml }, + {"dc", "default", dc_text }, + {"dc", "xml", dc_xml }, + {"crmadmin-node-list", "default", crmadmin_node_list }, + {"crmadmin-node", "default", crmadmin_node_text }, + {"crmadmin-node", "xml", crmadmin_node_xml }, + + { NULL, NULL, NULL } +}; + +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + static void quit_main_loop(crm_exit_t ec) { @@ -191,7 +375,7 @@ controller_event_cb(pcmk_ipc_api_t *controld_api, switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected - fprintf(stderr, "error: Lost connection to controller\n"); + out->err(out, "error: Lost connection to controller"); } goto done; break; @@ -209,14 +393,14 @@ controller_event_cb(pcmk_ipc_api_t *controld_api, } if (status != CRM_EX_OK) { - fprintf(stderr, "error: Bad reply from controller: %s", + out->err(out, "error: Bad reply from controller: %s", crm_exit_str(status)); exit_code = status; goto done; } if (reply->reply_type != pcmk_controld_reply_ping) { - fprintf(stderr, "error: Unknown reply type %d from controller\n", + out->err(out, "error: Unknown reply type %d from controller", reply->reply_type); goto done; } @@ -224,22 +408,16 @@ controller_event_cb(pcmk_ipc_api_t *controld_api, // Parse desired information from reply switch (command) { case cmd_health: - printf("Status of %s@%s: %s (%s)\n", + out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); - if (BE_SILENT && (reply->data.ping.fsa_state != NULL)) { - fprintf(stderr, "%s\n", reply->data.ping.fsa_state); - } exit_code = CRM_EX_OK; break; case cmd_whois_dc: - printf("Designated Controller is: %s\n", reply->host_from); - if (BE_SILENT && (reply->host_from != NULL)) { - fprintf(stderr, "%s\n", reply->host_from); - } + out->message(out, "dc", reply->host_from); exit_code = CRM_EX_OK; break; @@ -263,7 +441,7 @@ pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected - fprintf(stderr, "error: Lost connection to pacemakerd\n"); + out->err(out, "error: Lost connection to pacemakerd"); } goto done; break; @@ -281,14 +459,14 @@ pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, } if (status != CRM_EX_OK) { - fprintf(stderr, "error: Bad reply from pacemakerd: %s", + out->err(out, "error: Bad reply from pacemakerd: %s", crm_exit_str(status)); exit_code = status; goto done; } if (reply->reply_type != pcmk_pacemakerd_reply_ping) { - fprintf(stderr, "error: Unknown reply type %d from pacemakerd\n", + out->err(out, "error: Unknown reply type %d from pacemakerd", reply->reply_type); goto done; } @@ -305,21 +483,12 @@ pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); - printf("Status of %s: '%s' %s %s\n", + out->message(out, "pacemakerd-health", reply->data.ping.sys_from, (reply->data.ping.status == pcmk_rc_ok)? pcmk_pacemakerd_api_daemon_state_enum2text( reply->data.ping.state):"query failed", - (reply->data.ping.status == pcmk_rc_ok)?"last updated":"", (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:""); - if (BE_SILENT && - (reply->data.ping.state != pcmk_pacemakerd_state_invalid)) { - fprintf(stderr, "%s\n", - (reply->data.ping.status == pcmk_rc_ok)? - pcmk_pacemakerd_api_daemon_state_enum2text( - reply->data.ping.state): - "query failed"); - } exit_code = CRM_EX_OK; free(pinged_buf); } @@ -354,7 +523,7 @@ list_nodes() rc = the_cib->cmds->query(the_cib, NULL, &output, cib_scope_local | cib_sync_call); if (rc == pcmk_ok) { - do_find_node_list(output); + out->message(out, "crmadmin-node-list", output); free_xml(output); } the_cib->cmds->signoff(the_cib); @@ -362,20 +531,20 @@ list_nodes() } static GOptionContext * -build_arg_context(pcmk__common_args_t *args) { +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; const char *description = "Report bugs to users@clusterlabs.org"; GOptionEntry extra_prog_entries[] = { - { "quiet", 'q', 0, G_OPTION_ARG_NONE, &options.quiet, + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Display only the essential query information", NULL }, { NULL } }; - context = pcmk__build_arg_context(args, NULL, NULL, NULL); + context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); /* Add the -q option, which cannot be part of the globally supported options @@ -402,9 +571,11 @@ main(int argc, char **argv) GError *error = NULL; GOptionContext *context = NULL; + GOptionGroup *output_group = NULL; gchar **processed_args = NULL; - context = build_arg_context(args); + context = build_arg_context(args, &output_group); + pcmk__register_formats(output_group, formats); crm_log_cli_init("crmadmin"); @@ -421,9 +592,21 @@ main(int argc, char **argv) crm_bump_log_level(argc, argv); } + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + fprintf(stderr, "Error creating output format %s: %s\n", + args->output_ty, pcmk_rc_str(rc)); + exit_code = CRM_EX_ERROR; + goto done; + } + + out->quiet = args->quiet; + + pcmk__register_messages(out, fmt_functions); + if (args->version) { - /* FIXME: When crmadmin is converted to use formatted output, this can go. */ - pcmk__cli_help('v', CRM_EX_USAGE); + out->version(out, false); + goto done; } if (options.timeout) { @@ -433,12 +616,8 @@ main(int argc, char **argv) } } - if (options.quiet) { - BE_SILENT = TRUE; - } - if (options.health) { - fprintf(stderr, "Cluster-wide health option not supported\n"); + out->err(out, "Cluster-wide health option not supported"); ++argerr; } @@ -447,14 +626,14 @@ main(int argc, char **argv) } if (command == cmd_none) { - fprintf(stderr, "error: Must specify a command option\n\n"); + out->err(out, "error: Must specify a command option"); ++argerr; } if (argerr) { char *help = g_option_context_get_help(context, TRUE, NULL); - fprintf(stderr, "%s", help); + out->err(out, "%s", help); g_free(help); exit_code = CRM_EX_USAGE; goto done; @@ -464,7 +643,7 @@ main(int argc, char **argv) if (need_controld_api) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (controld_api == NULL) { - fprintf(stderr, "error: Could not connect to controller: %s\n", + out->err(out, "error: Could not connect to controller: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; @@ -472,7 +651,7 @@ main(int argc, char **argv) pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { - fprintf(stderr, "error: Could not connect to controller: %s\n", + out->err(out, "error: Could not connect to controller: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; @@ -483,7 +662,7 @@ main(int argc, char **argv) if (need_pacemakerd_api) { rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd); if (pacemakerd_api == NULL) { - fprintf(stderr, "error: Could not connect to pacemakerd: %s\n", + out->err(out, "error: Could not connect to pacemakerd: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; @@ -491,7 +670,7 @@ main(int argc, char **argv) pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, NULL); rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { - fprintf(stderr, "error: Could not connect to pacemakerd: %s\n", + out->err(out, "error: Could not connect to pacemakerd: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; @@ -528,6 +707,10 @@ done: g_strfreev(processed_args); g_clear_error(&error); pcmk__free_arg_context(context); + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } return crm_exit(exit_code); } @@ -567,7 +750,7 @@ do_work(pcmk_ipc_api_t *api) break; } if (rc != pcmk_rc_ok) { - fprintf(stderr, "error: Command failed: %s", pcmk_rc_str(rc)); + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); } return need_reply; @@ -576,43 +759,10 @@ do_work(pcmk_ipc_api_t *api) gboolean admin_message_timeout(gpointer data) { - fprintf(stderr, - "error: No reply received from controller before timeout (%dms)\n", + out->err(out, + "error: No reply received from controller before timeout (%dms)", message_timeout_ms); message_timer_id = 0; quit_main_loop(CRM_EX_TIMEOUT); return FALSE; // Tells glib to remove source } - -void -do_find_node_list(xmlNode * xml_node) -{ - int found = 0; - xmlNode *node = NULL; - xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); - - for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL; - node = crm_next_same_xml(node)) { - - if (BASH_EXPORT) { - printf("export %s=%s\n", - crm_element_value(node, XML_ATTR_UNAME), - crm_element_value(node, XML_ATTR_ID)); - } else { - const char *node_type = crm_element_value(node, XML_ATTR_TYPE); - - if (node_type == NULL) { - node_type = "member"; - } - printf("%s node: %s (%s)\n", node_type, - crm_element_value(node, XML_ATTR_UNAME), - crm_element_value(node, XML_ATTR_ID)); - } - found++; - } - // @TODO List Pacemaker Remote nodes that don't have a entry - - if (found == 0) { - printf("No nodes configured\n"); - } -} -- 1.8.3.1 From 2b121066c8eead96e85dc3bf6ecc1c2674cbdf32 Mon Sep 17 00:00:00 2001 From: Oyvind Albrigtsen Date: Tue, 20 Oct 2020 16:19:03 +0200 Subject: [PATCH 2/3] Refactor: crmadmin: use simple XML lists --- tools/crmadmin.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 14078e6..b80a31a 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -275,7 +275,7 @@ crmadmin_node_list(pcmk__output_t *out, va_list args) xmlNode *node = NULL; xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); - out->begin_list(out, NULL, NULL, "Nodes"); + out->begin_list(out, NULL, NULL, "nodes"); for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL; node = crm_next_same_xml(node)) { @@ -324,7 +324,7 @@ crmadmin_node_xml(pcmk__output_t *out, va_list args) char *name = va_arg(args, char *); char *id = va_arg(args, char *); - xmlNodePtr node = pcmk__output_create_xml_node(out, "crmadmin-node"); + xmlNodePtr node = pcmk__output_create_xml_node(out, "node"); xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) (type ? type : "member")); xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) crm_str(name)); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) crm_str(id)); @@ -604,6 +604,10 @@ main(int argc, char **argv) pcmk__register_messages(out, fmt_functions); + if (!pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname())) { + goto done; + } + if (args->version) { out->version(out, false); goto done; -- 1.8.3.1 From 9b53a7eda078db736be5212aeb77daf8117e2f17 Mon Sep 17 00:00:00 2001 From: Oyvind Albrigtsen Date: Tue, 20 Oct 2020 16:21:12 +0200 Subject: [PATCH 3/3] Feature: xml: add schema for new crmadmin output --- xml/Makefile.am | 2 +- xml/api/crmadmin-2.4.rng | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 xml/api/crmadmin-2.4.rng diff --git a/xml/Makefile.am b/xml/Makefile.am index c045522..892c811 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -45,7 +45,7 @@ version_pairs_last = $(wordlist \ ) # Names of API schemas that form the choices for pacemaker-result content -API_request_base = command-output crm_mon stonith_admin version +API_request_base = command-output crm_mon crmadmin stonith_admin version # Names of CIB schemas that form the choices for cib/configuration content CIB_cfg_base = options nodes resources constraints fencing acls tags alerts diff --git a/xml/api/crmadmin-2.4.rng b/xml/api/crmadmin-2.4.rng new file mode 100644 index 0000000..34c9ca4 --- /dev/null +++ b/xml/api/crmadmin-2.4.rng @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unknown + member + remote + ping + + + + + + + + -- 1.8.3.1