diff --git a/Makefile.common b/Makefile.common index 20bde69966..9dabeb13f6 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1,101 +1,102 @@ # # Copyright 2014-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. # # # Some variables to help with silent rules # https://www.gnu.org/software/automake/manual/html_node/Automake-silent_002drules-Option.html # # We require a minimum automake version of 1.11, which includes AM_V_GEN and # AM_V_at, but AM_V_P is not available until 1.13. V ?= $(AM_DEFAULT_VERBOSITY) # When a make command is prefixed with one of the AM_V_* macros, it may also be # desirable to suffix the command with this, to silence stdout. PCMK_quiet = $(pcmk_quiet_$(V)) pcmk_quiet_0 = >/dev/null pcmk_quiet_1 = # AM_V_GEN is intended to be used in custom pattern rules, and replaces echoing # the command used with a more concise line with "GEN" and the name of the file # being generated. Our AM_V_* macros are similar but more descriptive. AM_V_MAN = $(am__v_MAN_$(V)) am__v_MAN_0 = @echo " MAN $@"; am__v_MAN_1 = AM_V_SCHEMA = $(am__v_SCHEMA_$(V)) am__v_SCHEMA_0 = @echo " SCHEMA $@"; am__v_SCHEMA_1 = AM_V_PUB = $(am__v_PUB_$(V)) am__v_PUB_0 = @echo " PUB $@: $(DOCBOOK_FORMATS)"; am__v_PUB_1 = MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl # # Man page builders # # We have three types of man pages: # - man pages for the tools # - man pages for OCF agents # - man pages for cluster properties used by daemons # # "BUILD_HELP" actually means "help2man is available", so it only controls the # tool man pages, which are generated by help2man. The other man pages are # generated via XSL transforms. # if BUILD_HELP man8_MANS = $(sbin_PROGRAMS:%=%.8) $(sbin_SCRIPTS:%=%.8) HELP2MAN_ARGS = -N --section 8 --name "Part of the Pacemaker cluster resource manager" # Some of our tools' help are just shell script invocations of another tool's # help. Putting the real tool in MAN8DEPS helps detect when the wrapped help # needs updating. # # If a ".inc" file exists, the tool has been converted to use glib for # argument parsing, otherwise it still uses the libcrmcommon functions. # # @TODO Drop MAN8DEPS once we've converted all tools to libpacemaker API calls # and all wrappers to C code. %.8: % $(MAN8DEPS) $(AM_V_at)chmod a+x $(abs_builddir)/$< - $(AM_V_MAN)if [ -f $(abs_builddir)/$@.inc ]; then \ + $(AM_V_MAN)if [ -f $(top_srcdir)/tools/$@.inc ]; then \ PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) \ - -h --help-all \ - -i $(abs_builddir)/$@.inc $(abs_builddir)/$< \ + -h --help-all \ + --no-discard-stderr \ + -i $(top_srcdir)/$@.inc $(abs_builddir)/$< \ | sed -e '/.SS "Usage:"/,+3d' > $@ ; \ else \ PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) \ $(abs_builddir)/$< --output $@ ; \ fi endif # Save raw XML meta-data from daemon executables, for later conversion into man # pages. (Note that more specific rules may override this for creating other # types of XML files.) %.xml: % $(AM_V_at)$(abs_builddir)/$< metadata > $@ # Process the raw daemon and OCF agent meta-data output using our # meta-data-to-docbook-XML tranform. %.dbook: %.xml $(AM_V_at)$(XSLTPROC) --nonet --novalid --stringparam man.name $* \ $(DBOOK_OPTS) $(top_srcdir)/xml/ocf-meta2man.xsl \ $(abs_builddir)/$< > $(abs_builddir)/$@ # Generate the actual man page for an OCF resource agent from the intermediate # docbook XML. %.7: %.dbook $(AM_V_MAN)$(XSLTPROC) $(MANPAGE_XSLT) $(abs_builddir)/$< $(PCMK_quiet) diff --git a/tools/Makefile.am b/tools/Makefile.am index 00d9276fc2..d43a544dc4 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,157 +1,158 @@ # # 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)/Makefile.common if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h crm_resource.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif noinst_SCRIPTS = pcmk_simtimes EXTRA_DIST = crm_mon.sysconfig \ crm_mon.8.inc \ + crm_node.8.inc \ stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see Makefile.common). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_print.c crm_mon_runtime.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c crm_resource_ban.c crm_resource_runtime.c crm_resource_print.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/crm_node.8.inc b/tools/crm_node.8.inc new file mode 100644 index 0000000000..84cef12190 --- /dev/null +++ b/tools/crm_node.8.inc @@ -0,0 +1,5 @@ +[synopsis] +crm_node [command] [options] + +/level node information/ +.SH OPTIONS diff --git a/tools/crm_node.c b/tools/crm_node.c index ffbde6407a..b74c1533a0 100644 --- a/tools/crm_node.c +++ b/tools/crm_node.c @@ -1,590 +1,661 @@ /* * 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 #include #include #include #include #include +#include #include #include #include #include -static int command = 0; +#define SUMMARY "crm_node - Tool for displaying low-level node information" + +struct { + gboolean corosync; + gboolean dangerous_cmd; + gboolean force_flag; + char command; + int nodeid; + const char *target_uname; +} options = { + .command = '\0', + .force_flag = FALSE +}; + +gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); + static char *pid_s = NULL; static GMainLoop *mainloop = NULL; static crm_exit_t exit_code = CRM_EX_OK; -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - {"quiet", 0, 0, 'Q', "\tEssential output only"}, - - {"-spacer-", 1, 0, '-', "\nCommands:"}, - {"name", 0, 0, 'n', "\tDisplay the name used by the cluster for this node"}, - {"name-for-id", 1, 0, 'N', "\tDisplay the name used by the cluster for the node with the specified id"}, - {"quorum", 0, 0, 'q', "\tDisplay a 1 if our partition has quorum, 0 if not"}, - {"list", 0, 0, 'l', "\tDisplay all known members (past and present) of this cluster"}, - {"partition", 0, 0, 'p', "Display the members of this partition"}, - {"cluster-id", 0, 0, 'i', "Display this node's cluster id"}, - {"remove", 1, 0, 'R', "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's configuration and caches"}, - {"-spacer-", 1, 0, '-', "(the node must already have been removed from the underlying cluster stack configuration)"}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {"force", 0, 0, 'f'}, +#define INDENT " " + +static GOptionEntry command_entries[] = { + { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, + "Display this node's cluster id", + NULL }, + { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, + "Display all known members (past and present) of this cluster", + NULL }, + { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, + "Display the name used by the cluster for this node", + NULL }, + { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, + "Display the members of this partition", + NULL }, + { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, + "Display a 1 if our partition has quorum, 0 if not", + NULL }, + { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb, + "Display the name used by the cluster for the node with the specified ID", + "ID" }, + { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb, + "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n" + INDENT "configuration and caches (the node must already have been removed from\n" + INDENT "the underlying cluster stack configuration", + "NAME" }, + + { NULL } +}; + +static GOptionEntry addl_entries[] = { + { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag, + NULL, + NULL }, #if SUPPORT_COROSYNC - { "corosync", 0, 0, 'C', NULL, pcmk_option_hidden }, + /* Unused and deprecated */ + { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync, + NULL, + NULL }, #endif // @TODO add timeout option for when IPC replies are needed - {0, 0, 0, 0} + { NULL } }; +gboolean +command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (safe_str_eq("-i", option_name) || safe_str_eq("--cluster-id", option_name)) { + options.command = 'i'; + } else if (safe_str_eq("-l", option_name) || safe_str_eq("--list", option_name)) { + options.command = 'l'; + } else if (safe_str_eq("-n", option_name) || safe_str_eq("--name", option_name)) { + options.command = 'n'; + } else if (safe_str_eq("-p", option_name) || safe_str_eq("--partition", option_name)) { + options.command = 'p'; + } else if (safe_str_eq("-q", option_name) || safe_str_eq("--quorum", option_name)) { + options.command = 'q'; + } else { + g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name); + return FALSE; + } + + return TRUE; +} + +gboolean +name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'N'; + options.nodeid = crm_parse_int(optarg, NULL); + return TRUE; +} + +gboolean +remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (optarg == NULL) { + crm_err("-R option requires an argument"); + g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument"); + return FALSE; + } + + options.command = 'R'; + options.dangerous_cmd = TRUE; + options.target_uname = optarg; + return TRUE; +} + /*! * \internal * \brief Exit crm_node * Clean up memory, and either quit mainloop (if running) or exit * * \param[in] value Exit status */ static void crm_node_exit(crm_exit_t value) { if (pid_s) { free(pid_s); pid_s = NULL; } exit_code = value; if (mainloop && g_main_loop_is_running(mainloop)) { g_main_loop_quit(mainloop); } else { crm_exit(exit_code); } } static void exit_disconnect(gpointer user_data) { fprintf(stderr, "error: Lost connection to cluster\n"); crm_node_exit(CRM_EX_DISCONNECT); } typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length, gpointer userdata); static crm_ipc_t * new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch) { mainloop_io_t *source = NULL; crm_ipc_t *ipc = NULL; struct ipc_client_callbacks ipc_callbacks = { .dispatch = dispatch, .destroy = exit_disconnect }; mainloop = g_main_loop_new(NULL, FALSE); source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0, NULL, &ipc_callbacks); ipc = mainloop_get_ipc_client(source); if (ipc == NULL) { fprintf(stderr, "error: Could not connect to cluster (is it running?)\n"); crm_node_exit(CRM_EX_DISCONNECT); } return ipc; } static void run_mainloop_and_exit() { g_main_loop_run(mainloop); g_main_loop_unref(mainloop); mainloop = NULL; crm_node_exit(exit_code); } static int send_controller_hello(crm_ipc_t *controller) { xmlNode *hello = NULL; int rc; pid_s = crm_getpid_s(); hello = create_hello_message(pid_s, crm_system_name, "1", "0"); rc = crm_ipc_send(controller, hello, 0, 0, NULL); free_xml(hello); return (rc < 0)? rc : 0; } static int send_node_info_request(crm_ipc_t *controller, uint32_t nodeid) { xmlNode *ping = NULL; int rc; ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD, crm_system_name, pid_s); if (nodeid > 0) { crm_xml_add_int(ping, XML_ATTR_ID, nodeid); } rc = crm_ipc_send(controller, ping, 0, 0, NULL); free_xml(ping); return (rc < 0)? rc : 0; } static int dispatch_controller(const char *buffer, ssize_t length, gpointer userdata) { xmlNode *message = string2xml(buffer); xmlNode *data = NULL; const char *value = NULL; if (message == NULL) { fprintf(stderr, "error: Could not understand reply from controller\n"); crm_node_exit(CRM_EX_PROTOCOL); return 0; } crm_log_xml_trace(message, "controller reply"); exit_code = CRM_EX_PROTOCOL; // Validate reply value = crm_element_value(message, F_CRM_MSG_TYPE); if (safe_str_neq(value, XML_ATTR_RESPONSE)) { fprintf(stderr, "error: Message from controller was not a reply\n"); goto done; } value = crm_element_value(message, XML_ATTR_REFERENCE); if (value == NULL) { fprintf(stderr, "error: Controller reply did not specify original message\n"); goto done; } data = get_message_xml(message, F_CRM_DATA); if (data == NULL) { fprintf(stderr, "error: Controller reply did not contain any data\n"); goto done; } - switch (command) { + switch (options.command) { case 'i': value = crm_element_value(data, XML_ATTR_ID); if (value == NULL) { fprintf(stderr, "error: Controller reply did not contain node ID\n"); } else { printf("%s\n", value); exit_code = CRM_EX_OK; } break; case 'n': case 'N': value = crm_element_value(data, XML_ATTR_UNAME); if (value == NULL) { fprintf(stderr, "Node is not known to cluster\n"); exit_code = CRM_EX_NOHOST; } else { printf("%s\n", value); exit_code = CRM_EX_OK; } break; case 'q': value = crm_element_value(data, XML_ATTR_HAVE_QUORUM); if (value == NULL) { fprintf(stderr, "error: Controller reply did not contain quorum status\n"); } else { bool quorum = crm_is_true(value); printf("%d\n", quorum); exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM; } break; default: fprintf(stderr, "internal error: Controller reply not expected\n"); exit_code = CRM_EX_SOFTWARE; break; } done: free_xml(message); crm_node_exit(exit_code); return 0; } static void run_controller_mainloop(uint32_t nodeid) { crm_ipc_t *controller = NULL; int rc; controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller); rc = send_controller_hello(controller); if (rc < 0) { fprintf(stderr, "error: Could not register with controller: %s\n", pcmk_strerror(rc)); crm_node_exit(crm_errno2exit(rc)); } rc = send_node_info_request(controller, nodeid); if (rc < 0) { fprintf(stderr, "error: Could not ping controller: %s\n", pcmk_strerror(rc)); crm_node_exit(crm_errno2exit(rc)); } // Run main loop to get controller reply via dispatch_controller() run_mainloop_and_exit(); } static void print_node_name() { // Check environment first (i.e. when called by resource agent) const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET); if (name != NULL) { printf("%s\n", name); crm_node_exit(CRM_EX_OK); } else { // Otherwise ask the controller run_controller_mainloop(0); } } static int cib_remove_node(long id, const char *name) { int rc; cib_t *cib = NULL; xmlNode *node = NULL; xmlNode *node_state = NULL; crm_trace("Removing %s from the CIB", name); if(name == NULL && id == 0) { return -ENOTUNIQ; } node = create_xml_node(NULL, XML_CIB_TAG_NODE); node_state = create_xml_node(NULL, XML_CIB_TAG_STATE); crm_xml_add(node, XML_ATTR_UNAME, name); crm_xml_add(node_state, XML_ATTR_UNAME, name); if (id > 0) { crm_xml_set_id(node, "%ld", id); crm_xml_add(node_state, XML_ATTR_ID, ID(node)); } cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call); if (rc != pcmk_ok) { printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s", name, id, pcmk_strerror(rc)); } rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call); if (rc != pcmk_ok) { printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s", name, id, pcmk_strerror(rc)); } cib->cmds->signoff(cib); cib_delete(cib); return rc; } static int tools_remove_node_cache(const char *node_name, long nodeid, const char *target) { int rc = -1; crm_ipc_t *conn = crm_ipc_new(target, 0); xmlNode *cmd = NULL; if (!conn) { return -ENOTCONN; } if (!crm_ipc_connect(conn)) { crm_perror(LOG_ERR, "Connection to %s failed", target); crm_ipc_destroy(conn); return -ENOTCONN; } if(safe_str_eq(target, CRM_SYSTEM_CRMD)) { // The controller requires a hello message before sending a request rc = send_controller_hello(conn); if (rc < 0) { fprintf(stderr, "error: Could not register with controller: %s\n", pcmk_strerror(rc)); return rc; } } crm_trace("Removing %s[%ld] from the %s membership cache", node_name, nodeid, target); if(safe_str_eq(target, T_ATTRD)) { cmd = create_xml_node(NULL, __FUNCTION__); crm_xml_add(cmd, F_TYPE, T_ATTRD); crm_xml_add(cmd, F_ORIG, crm_system_name); crm_xml_add(cmd, F_ATTRD_TASK, ATTRD_OP_PEER_REMOVE); crm_xml_add(cmd, F_ATTRD_HOST, node_name); if (nodeid > 0) { crm_xml_add_int(cmd, F_ATTRD_HOST_ID, (int) nodeid); } } else { cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target, crm_system_name, pid_s); if (nodeid > 0) { crm_xml_set_id(cmd, "%ld", nodeid); } crm_xml_add(cmd, XML_ATTR_UNAME, node_name); } rc = crm_ipc_send(conn, cmd, 0, 0, NULL); crm_debug("%s peer cache cleanup for %s (%ld): %d", target, node_name, nodeid, rc); if (rc > 0) { rc = cib_remove_node(nodeid, node_name); } if (conn) { crm_ipc_close(conn); crm_ipc_destroy(conn); } free_xml(cmd); return rc > 0 ? 0 : rc; } static void remove_node(const char *target_uname) { int d = 0; long nodeid = 0; const char *node_name = NULL; char *endptr = NULL; const char *daemons[] = { CRM_SYSTEM_CRMD, "stonith-ng", T_ATTRD, CRM_SYSTEM_MCP, }; // Check whether node was specified by name or numeric ID errno = 0; nodeid = strtol(target_uname, &endptr, 10); if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0') || (nodeid <= 0)) { // It's not a positive integer, so assume it's a node name nodeid = 0; node_name = target_uname; } for (d = 0; d < DIMOF(daemons); d++) { if (tools_remove_node_cache(node_name, nodeid, daemons[d])) { crm_err("Failed to connect to %s to remove node '%s'", daemons[d], target_uname); crm_node_exit(CRM_EX_ERROR); return; } } crm_node_exit(CRM_EX_OK); } static gint compare_node_xml(gconstpointer a, gconstpointer b) { const char *a_name = crm_element_value((xmlNode*) a, "uname"); const char *b_name = crm_element_value((xmlNode*) b, "uname"); return strcmp((a_name? a_name : ""), (b_name? b_name : "")); } static int node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata) { GList *nodes = NULL; xmlNode *node = NULL; xmlNode *msg = string2xml(buffer); const char *uname; const char *state; if (msg == NULL) { fprintf(stderr, "error: Could not understand pacemakerd response\n"); crm_node_exit(CRM_EX_PROTOCOL); return 0; } crm_log_xml_trace(msg, "message"); for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) { nodes = g_list_insert_sorted(nodes, node, compare_node_xml); } for (GList *iter = nodes; iter; iter = iter->next) { node = (xmlNode*) iter->data; uname = crm_element_value(node, "uname"); state = crm_element_value(node, "state"); - if (command == 'l') { + if (options.command == 'l') { int id = 0; crm_element_value_int(node, "id", &id); printf("%d %s %s\n", id, (uname? uname : ""), (state? state : "")); // This is CRM_NODE_MEMBER but we don't want to include cluster header - } else if ((command == 'p') && safe_str_eq(state, "member")) { + } else if ((options.command == 'p') && safe_str_eq(state, "member")) { printf("%s ", (uname? uname : "")); } } - if (command == 'p') { + if (options.command == 'p') { fprintf(stdout, "\n"); } free_xml(msg); crm_node_exit(CRM_EX_OK); return 0; } static void run_pacemakerd_mainloop() { crm_ipc_t *ipc = NULL; xmlNode *poke = NULL; ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch); // Sending anything will get us a list of nodes poke = create_xml_node(NULL, "poke"); crm_ipc_send(ipc, poke, 0, 0, NULL); free_xml(poke); // Handle reply via node_mcp_dispatch() run_mainloop_and_exit(); } +static GOptionContext * +build_arg_context(pcmk__common_args_t *args) { + GOptionContext *context = NULL; + + GOptionEntry extra_prog_entries[] = { + { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), + "Be less descriptive in output.", + NULL }, + + { NULL } + }; + + context = pcmk__build_arg_context(args, NULL); + + /* 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, "commands", "Commands:", + "Show command help", command_entries); + pcmk__add_arg_group(context, "additional", "Additional Options:", + "Show additional options", addl_entries); + return context; +} + int main(int argc, char **argv) { - int flag = 0; - int argerr = 0; - uint32_t nodeid = 0; - gboolean force_flag = FALSE; - gboolean dangerous_cmd = FALSE; - int option_index = 0; - const char *target_uname = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + + GError *error = NULL; + GOptionContext *context = NULL; + gchar **processed_args = NULL; + + context = build_arg_context(args); crm_log_cli_init("crm_node"); - crm_set_options(NULL, "command [options]", long_options, - "Tool for displaying low-level node information"); - - while (flag >= 0) { - flag = crm_get_option(argc, argv, &option_index); - switch (flag) { - case -1: - break; - case 'V': - crm_bump_log_level(argc, argv); - break; - case '$': - case '?': - crm_help(flag, CRM_EX_OK); - break; - case 'Q': - // currently unused - break; - case 'C': - // unused and deprecated - break; - case 'f': - force_flag = TRUE; - break; - case 'R': - command = flag; - dangerous_cmd = TRUE; - target_uname = optarg; - if (optarg == NULL) { - ++argerr; - } - break; - case 'N': - command = flag; - nodeid = crm_parse_int(optarg, NULL); - break; - case 'p': - case 'q': - case 'i': - case 'l': - case 'n': - command = flag; - break; - default: - ++argerr; - break; - } + + processed_args = pcmk__cmdline_preproc(argc, argv, "NR"); + + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + } + + for (int i = 0; i < args->verbosity; i++) { + crm_bump_log_level(argc, argv); } - if (optind > argc) { - ++argerr; + if (args->version) { + /* FIXME: When crm_node is converted to use formatted output, this can go. */ + crm_help('v', CRM_EX_USAGE); } - if (argerr) { - crm_help('?', CRM_EX_USAGE); + if (optind > argc || options.command == 0) { + fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL)); + exit_code = CRM_EX_USAGE; + goto done; } - if (dangerous_cmd && force_flag == FALSE) { + if (options.dangerous_cmd && options.force_flag == FALSE) { fprintf(stderr, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n"); - crm_node_exit(CRM_EX_USAGE); + exit_code = CRM_EX_USAGE; + goto done; } - switch (command) { + switch (options.command) { case 'n': print_node_name(); break; case 'R': - remove_node(target_uname); + remove_node(options.target_uname); break; case 'i': case 'q': case 'N': - run_controller_mainloop(nodeid); + run_controller_mainloop(options.nodeid); break; case 'l': case 'p': run_pacemakerd_mainloop(); break; default: break; } - fprintf(stderr, "error: Must specify a command option\n"); - crm_node_exit(CRM_EX_USAGE); - return CRM_EX_USAGE; +done: + g_strfreev(processed_args); + g_option_context_free(context); + crm_node_exit(exit_code); + return exit_code; }