diff --git a/include/crm/crm.h b/include/crm/crm.h index 389b6aa30f..4eca278523 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,232 +1,232 @@ /* * Copyright 2004-2020 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 CRM__H # define CRM__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ # include # include # include # include # include # include /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently used by pacemaker: * * >2.1: Operation updates include timing data * >=3.0.5: XML v2 digests are created * >=3.0.8: Peers do not need acks for cancellations * >=3.0.9: DC will send its own shutdown request to all peers * XML v2 patchsets are created by default * >=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])) ) # ifndef MAX_NAME # define MAX_NAME 256 # endif # ifndef __GNUC__ # define __builtin_expect(expr, result) (expr) # endif /* Some handy macros used by the Linux kernel */ # define __likely(expr) __builtin_expect(expr, 1) # define __unlikely(expr) __builtin_expect(expr, 0) # define CRM_META "CRM_meta" extern char *crm_system_name; /* *INDENT-OFF* */ // Used for some internal IPC timeouts (maybe should be configurable option) # define MAX_IPC_DELAY 120 // How we represent "infinite" scores # define CRM_SCORE_INFINITY 1000000 # define CRM_INFINITY_S "INFINITY" # define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S # define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S /* @COMPAT API < 2.0.0 Deprecated "infinity" aliases * * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first. * This, of course, complicates any attempt to use the other definition in any * code that includes this header. */ # undef INFINITY # define INFINITY_S "INFINITY" # define MINUS_INFINITY_S "-INFINITY" # define INFINITY 1000000 /* Sub-systems */ # define CRM_SYSTEM_DC "dc" # define CRM_SYSTEM_DCIB "dcib" /* The master CIB */ # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_STONITHD "stonithd" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_RA_VERSION "#ra-version" # define CRM_ATTR_PROTOCOL "#attrd-protocol" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_LOCAL_SHUTDOWN "start_shutdown" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN "do_shutdown" # define CRM_OP_FENCE "stonith" # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_REFRESH "lrm_refresh" /* Deprecated */ # define CRM_OP_LRM_QUERY "lrm_query" # define CRM_OP_LRM_DELETE "lrm_delete" # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT "clear_failcount" # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RELAXED_SET "one-or-more" # define CRM_OP_RELAXED_CLONE "clone-one-or-more" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES "maintenance_nodes" /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # define CRMD_ACTION_DELETE "delete" # define CRMD_ACTION_CANCEL "cancel" # define CRMD_ACTION_RELOAD "reload" # define CRMD_ACTION_MIGRATE "migrate_to" # define CRMD_ACTION_MIGRATED "migrate_from" # define CRMD_ACTION_START "start" # define CRMD_ACTION_STARTED "running" # define CRMD_ACTION_STOP "stop" # define CRMD_ACTION_STOPPED "stopped" # define CRMD_ACTION_PROMOTE "promote" # define CRMD_ACTION_PROMOTED "promoted" # define CRMD_ACTION_DEMOTE "demote" # define CRMD_ACTION_DEMOTED "demoted" # define CRMD_ACTION_NOTIFY "notify" # define CRMD_ACTION_NOTIFIED "notified" # define CRMD_ACTION_STATUS "monitor" # define CRMD_ACTION_METADATA "meta-data" # define CRMD_METADATA_CALL_TIMEOUT 30000 /* short names */ # define RSC_DELETE CRMD_ACTION_DELETE # define RSC_CANCEL CRMD_ACTION_CANCEL # define RSC_MIGRATE CRMD_ACTION_MIGRATE # define RSC_MIGRATED CRMD_ACTION_MIGRATED # define RSC_START CRMD_ACTION_START # define RSC_STARTED CRMD_ACTION_STARTED # define RSC_STOP CRMD_ACTION_STOP # define RSC_STOPPED CRMD_ACTION_STOPPED # define RSC_PROMOTE CRMD_ACTION_PROMOTE # define RSC_PROMOTED CRMD_ACTION_PROMOTED # define RSC_DEMOTE CRMD_ACTION_DEMOTE # define RSC_DEMOTED CRMD_ACTION_DEMOTED # define RSC_NOTIFY CRMD_ACTION_NOTIFY # define RSC_NOTIFIED CRMD_ACTION_NOTIFIED # define RSC_STATUS CRMD_ACTION_STATUS # define RSC_METADATA CRMD_ACTION_METADATA /* *INDENT-ON* */ typedef GList *GListPtr; # include # include static inline const char * crm_action_str(const char *task, guint interval_ms) { if ((task != NULL) && (interval_ms == 0) && (strcasecmp(task, RSC_STATUS) == 0)) { return "probe"; } return task; } #ifdef __cplusplus } #endif #endif diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 0b87d01cff..b80a31a754 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,618 +1,772 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include // atoi() #include // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include +#include #include #include #include #include #include #define SUMMARY "query and manage the Pacemaker controller" #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 static guint message_timer_id = 0; static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; static GMainLoop *mainloop = NULL; 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); static enum { cmd_none, cmd_shutdown, cmd_health, cmd_elect_dc, cmd_whois_dc, cmd_list_nodes, cmd_pacemakerd_health, } command = cmd_none; 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; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry command_options[] = { { "status", 'S', 0, G_OPTION_ARG_CALLBACK, command_cb, "Display the status of the specified node." "\n Result is state of node's internal finite state" "\n machine, which can be useful for debugging", NULL }, { "pacemakerd", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the status of local pacemakerd." "\n Result is the state of the sub-daemons watched" "\n by pacemakerd.", NULL }, { "dc_lookup", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the uname of the node co-ordinating the cluster." "\n This is an internal detail rarely useful to" "\n administrators except when deciding on which" "\n node to examine the logs.", NULL }, { "nodes", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the uname of all member nodes", NULL }, { "election", 'E', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Start an election for the cluster co-ordinator", NULL }, { "kill", 'K', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Stop controller (not rest of cluster stack) on specified node", NULL }, { "health", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.health, NULL, NULL }, { NULL } }; static GOptionEntry additional_options[] = { { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout, "Time (in milliseconds) to wait before declaring the" "\n operation failed", NULL }, { "bash-export", 'B', 0, G_OPTION_ARG_NONE, &BASH_EXPORT, "Display nodes as shell commands of the form 'export uname=uuid'" "\n (valid with -N/--nodes)", }, { "ipc-name", 'i', 0, G_OPTION_ARG_STRING, &ipc_name, "Name to use for ipc instead of 'crmadmin' (with -P/--pacemakerd).", NULL }, { NULL } }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (!strcmp(option_name, "--status") || !strcmp(option_name, "-S")) { command = cmd_health; crm_trace("Option %c => %s", 'S', optarg); } if (!strcmp(option_name, "--pacemakerd") || !strcmp(option_name, "-P")) { command = cmd_pacemakerd_health; need_pacemakerd_api = true; need_controld_api = false; } if (!strcmp(option_name, "--dc_lookup") || !strcmp(option_name, "-D")) { command = cmd_whois_dc; } if (!strcmp(option_name, "--nodes") || !strcmp(option_name, "-N")) { command = cmd_list_nodes; need_controld_api = false; } if (!strcmp(option_name, "--election") || !strcmp(option_name, "-E")) { command = cmd_elect_dc; } if (!strcmp(option_name, "--kill") || !strcmp(option_name, "-K")) { command = cmd_shutdown; crm_trace("Option %c => %s", 'K', optarg); } if (optarg) { if (dest_node != NULL) { free(dest_node); } dest_node = strdup(optarg); } 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, "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) { exit_code = ec; if (mainloop != NULL) { GMainLoop *mloop = mainloop; mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static void controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_controld_api_reply_t *reply = event_data; 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; case pcmk_ipc_event_reply: break; default: return; } if (message_timer_id != 0) { g_source_remove(message_timer_id); message_timer_id = 0; } 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; } // 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; default: // Not really possible here exit_code = CRM_EX_SOFTWARE; break; } done: pcmk_disconnect_ipc(controld_api); quit_main_loop(exit_code); } static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_pacemakerd_api_reply_t *reply = event_data; 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; case pcmk_ipc_event_reply: break; default: return; } if (message_timer_id != 0) { g_source_remove(message_timer_id); message_timer_id = 0; } 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; } // Parse desired information from reply switch (command) { case cmd_pacemakerd_health: { crm_time_t *crm_when = crm_time_new(NULL); char *pinged_buf = NULL; crm_time_set_timet(crm_when, &reply->data.ping.last_good); pinged_buf = crm_time_as_string(crm_when, 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); } break; default: // Not really possible here exit_code = CRM_EX_SOFTWARE; break; } done: pcmk_disconnect_ipc(pacemakerd_api); quit_main_loop(exit_code); } // \return Standard Pacemaker return code static int list_nodes() { cib_t *the_cib = cib_new(); xmlNode *output = NULL; int rc; if (the_cib == NULL) { return ENOMEM; } rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { return pcmk_legacy2rc(rc); } 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); return pcmk_legacy2rc(rc); } 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 * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "command", "Commands:", "Show command options", command_options); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", additional_options); return context; } int main(int argc, char **argv) { int argerr = 0; int rc; pcmk_ipc_api_t *controld_api = NULL; pcmk_ipc_api_t *pacemakerd_api = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); 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"); processed_args = pcmk__cmdline_preproc(argv, "itBDEHKNPS"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); exit_code = CRM_EX_USAGE; goto done; } for (int i = 0; i < args->verbosity; i++) { BE_VERBOSE = TRUE; 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 (!pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname())) { + goto done; + } + 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) { message_timeout_ms = (guint) options.timeout; if (message_timeout_ms < 1) { message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } } - 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; } if (optind > argc) { ++argerr; } 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; } // Connect to the controller if needed 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; } 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; } } // Connect to pacemakerd if needed 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; } 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; } } if (do_work(controld_api?controld_api:pacemakerd_api)) { // A reply is needed from controller, so run main loop to get it exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects mainloop = g_main_loop_new(NULL, FALSE); message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); g_main_loop_run(mainloop); } done: if (controld_api != NULL) { pcmk_ipc_api_t *capi = controld_api; controld_api = NULL; // Ensure we can't free this twice pcmk_free_ipc_api(capi); } if (pacemakerd_api != NULL) { pcmk_ipc_api_t *capi = pacemakerd_api; pacemakerd_api = NULL; // Ensure we can't free this twice pcmk_free_ipc_api(capi); } if (mainloop != NULL) { g_main_loop_unref(mainloop); mainloop = NULL; } 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); } // \return True if reply from controller is needed bool do_work(pcmk_ipc_api_t *api) { bool need_reply = false; int rc = pcmk_rc_ok; switch (command) { case cmd_shutdown: rc = pcmk_controld_api_shutdown(api, dest_node); break; case cmd_health: // dest_node != NULL case cmd_whois_dc: // dest_node == NULL rc = pcmk_controld_api_ping(api, dest_node); need_reply = true; break; case cmd_elect_dc: rc = pcmk_controld_api_start_election(api); break; case cmd_list_nodes: rc = list_nodes(); break; case cmd_pacemakerd_health: rc = pcmk_pacemakerd_api_ping(api, ipc_name); need_reply = true; break; case cmd_none: // not actually possible here 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; } 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"); - } -} diff --git a/xml/Makefile.am b/xml/Makefile.am index c045522847..892c811a1e 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,263 +1,263 @@ # # Copyright 2004-2019 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk noarch_pkgconfig_DATA = $(builddir)/pacemaker-schemas.pc # Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line # tool XML output), and a legacy schema for crm_mon --as-xml. # # See Readme.md for details on updating CIB schema files (API is similar) # The CIB and crm_mon schemas are installed directly in CRM_SCHEMA_DIRECTORY # for historical reasons, while the API schema is installed in a subdirectory. APIdir = $(CRM_SCHEMA_DIRECTORY)/api CIBdir = $(CRM_SCHEMA_DIRECTORY) MONdir = $(CRM_SCHEMA_DIRECTORY) # Extract a sorted list of available numeric schema versions # from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng numeric_versions = $(shell ls -1 $(1) \ | sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \ | sort -u -t. -k 1,1n -k 2,2n -k 3,3n) version_pairs = $(join \ $(1),$(addprefix \ -,$(wordlist \ 2,$(words $(1)),$(1) \ ) next \ ) \ ) version_pairs_last = $(wordlist \ $(words \ $(wordlist \ 2,$(1),$(2) \ ) \ ),$(1),$(2) \ ) # 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 # Names of all schemas (including top level and those included by others) API_base = $(API_request_base) fence-event item status CIB_base = cib $(CIB_cfg_base) status score rule nvset # Static schema files and transforms (only CIB has transforms) # # This is more complicated than it should be due to the need to support # VPATH builds and "make distcheck". We need the absolute paths for reliable # substitution back and forth, and relative paths for distributed files. API_abs_files = $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)*.rng)) CIB_abs_files = $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng)) CIB_abs_xsl = $(abs_srcdir)/upgrade-1.3.xsl \ $(abs_srcdir)/upgrade-2.10.xsl \ $(wildcard $(abs_srcdir)/upgrade-*enter.xsl) \ $(wildcard $(abs_srcdir)/upgrade-*leave.xsl) MON_abs_files = $(abs_srcdir)/crm_mon.rng API_files = $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)*.rng)) CIB_files = $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng)) CIB_xsl = $(srcdir)/upgrade-1.3.xsl \ $(srcdir)/upgrade-2.10.xsl \ $(wildcard $(srcdir)/upgrade-*enter.xsl) \ $(wildcard $(srcdir)/upgrade-*leave.xsl) MON_files = $(srcdir)/crm_mon.rng # Sorted lists of all numeric schema versions API_numeric_versions = $(call numeric_versions,${API_files}) CIB_numeric_versions = $(call numeric_versions,${CIB_files}) # The highest numeric schema version API_max ?= $(lastword $(API_numeric_versions)) CIB_max ?= $(lastword $(CIB_numeric_versions)) # Sorted lists of all schema versions (including "next") API_versions = next $(API_numeric_versions) CIB_versions = next $(CIB_numeric_versions) # Build tree locations of static schema files and transforms (for VPATH builds) API_build_copies = $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) CIB_build_copies = $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f))) MON_build_copies = $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) # Dynamically generated schema files API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng) CIB_generated = pacemaker.rng $(foreach base,$(CIB_versions),pacemaker-$(base).rng) versions.rng CIB_version_pairs = $(call version_pairs,${CIB_numeric_versions}) CIB_version_pairs_cnt = $(words ${CIB_version_pairs}) CIB_version_pairs_last = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs}) dist_API_DATA = $(API_files) dist_CIB_DATA = $(CIB_files) $(CIB_xsl) dist_MON_DATA = $(MON_files) nodist_API_DATA = $(API_generated) nodist_CIB_DATA = $(CIB_generated) EXTRA_DIST = Readme.md \ best-match.sh \ cibtr-2.rng \ context-of.xsl \ ocf-meta2man.xsl \ regression.sh \ upgrade-2.10-roundtrip.xsl \ upgrade-detail.xsl \ xslt_cibtr-2.rng \ assets \ test-2 \ test-2-enter \ test-2-leave \ test-2-roundtrip cib-versions: @echo "Max: $(CIB_max)" @echo "Available: $(CIB_versions)" api-versions: @echo "Max: $(API_max)" @echo "Available: $(API_versions)" # Dynamically generated top-level API schema api/api-result.rng: api/api-result-$(API_max).rng $(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ api/api-result-%.rng: $(API_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(API_request_base); do $(srcdir)/best-match.sh api/$$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh api/status $(*) $(@) " " || : $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated top-level CIB schema pacemaker.rng: pacemaker-$(CIB_max).rng $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ pacemaker-%.rng: $(CIB_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh cib $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(CIB_cfg_base); do $(srcdir)/best-match.sh $$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh status $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated CIB schema listing all pacemaker versions versions.rng: Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' none' >> $@ $(AM_V_at)echo ' pacemaker-0.6' >> $@ $(AM_V_at)echo ' transitional-0.6' >> $@ $(AM_V_at)echo ' pacemaker-0.7' >> $@ $(AM_V_at)echo ' pacemaker-1.1' >> $@ $(AM_V_at)for rng in $(CIB_versions); do echo " pacemaker-$$rng" >> $@; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # diff fails with ec=2 if no predecessor is found; # this uses '=' GNU extension to sed, if that's not available, # one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`; # XXX: use line information from hunk to avoid "not detected" for ambiguity version_diff = \ @for p in $(1); do \ set `echo "$${p}" | tr '-' ' '`; \ echo "\#\#\# *-$$2.rng vs. predecessor"; \ for v in *-$$2.rng; do \ echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \ old=`./best-match.sh $${b} $$1`; \ p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \ case $$? in \ 1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \ -e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \ | while read hline; do \ read h && read i || break; \ iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \ ctxt="(not detected)"; \ if test `echo "$${iline}" | wc -l` -eq 1; then \ ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \ echo "$${i}"; \ sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \ } | $(XSLTPROC) --param skip 1 context-of.xsl -`; \ fi; \ echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \ -e '/^\(+++\|---\)/p'; \ echo "$${h} context: $${ctxt}"; \ echo "$${p}" | sed -n -e "1,$${hline}d" \ -e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \ done; \ ;; \ 2) echo "\#\#\#\#\# $${v} has no predecessor";; \ esac; \ done; \ done diff: best-match.sh @echo "# Comparing changes in + since $(CIB_max)" $(call version_diff,${CIB_version_pairs_last}) fulldiff: best-match.sh @echo "# Comparing all changes across all the subsequent increments" $(call version_diff,${CIB_version_pairs}) CLEANFILES = $(API_generated) $(CIB_generated) clean-local: if [ "x$(srcdir)" != "x$(builddir)" ]; then \ rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies); \ fi # Enable ability to use $@ in prerequisite .SECONDEXPANSION: # For VPATH builds, copy the static schema files into the build tree $(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@)) $(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then \ $(MKDIR_P) "$(dir $(@))"; \ cp "$(<)" "$(@)"; \ fi diff --git a/xml/api/crmadmin-2.4.rng b/xml/api/crmadmin-2.4.rng new file mode 100644 index 0000000000..34c9ca4307 --- /dev/null +++ b/xml/api/crmadmin-2.4.rng @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unknown + member + remote + ping + + + + + + + +