summaryrefslogtreecommitdiffstats
path: root/tools/crm_node.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/crm_node.c')
-rw-r--r--tools/crm_node.c601
1 files changed, 601 insertions, 0 deletions
diff --git a/tools/crm_node.c b/tools/crm_node.c
new file mode 100644
index 0000000..ac2a190
--- /dev/null
+++ b/tools/crm_node.c
@@ -0,0 +1,601 @@
+/*
+ * Copyright 2004-2023 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 <crm_internal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <crm/crm.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/mainloop.h>
+#include <crm/msg_xml.h>
+#include <crm/cib.h>
+#include <crm/cib/internal.h>
+#include <crm/common/ipc_controld.h>
+#include <crm/common/attrd_internal.h>
+
+#define SUMMARY "crm_node - Tool for displaying low-level node information"
+
+struct {
+ gboolean corosync;
+ gboolean dangerous_cmd;
+ gboolean force_flag;
+ char command;
+ int nodeid;
+ 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 GMainLoop *mainloop = NULL;
+static crm_exit_t exit_code = CRM_EX_OK;
+
+#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
+ /* 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
+
+ { NULL }
+};
+
+gboolean
+command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_eq("-i", option_name, pcmk__str_casei) || pcmk__str_eq("--cluster-id", option_name, pcmk__str_casei)) {
+ options.command = 'i';
+ } else if (pcmk__str_eq("-l", option_name, pcmk__str_casei) || pcmk__str_eq("--list", option_name, pcmk__str_casei)) {
+ options.command = 'l';
+ } else if (pcmk__str_eq("-n", option_name, pcmk__str_casei) || pcmk__str_eq("--name", option_name, pcmk__str_casei)) {
+ options.command = 'n';
+ } else if (pcmk__str_eq("-p", option_name, pcmk__str_casei) || pcmk__str_eq("--partition", option_name, pcmk__str_casei)) {
+ options.command = 'p';
+ } else if (pcmk__str_eq("-q", option_name, pcmk__str_casei) || pcmk__str_eq("--quorum", option_name, pcmk__str_casei)) {
+ options.command = 'q';
+ } else {
+ g_set_error(error, PCMK__EXITC_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';
+ pcmk__scan_min_int(optarg, &(options.nodeid), 0);
+ 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, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
+ return FALSE;
+ }
+
+ options.command = 'R';
+ options.dangerous_cmd = TRUE;
+ pcmk__str_update(&options.target_uname, optarg);
+ return TRUE;
+}
+
+static gint
+sort_node(gconstpointer a, gconstpointer b)
+{
+ const pcmk_controld_api_node_t *node_a = a;
+ const pcmk_controld_api_node_t *node_b = b;
+
+ return pcmk__numeric_strcasecmp((node_a->uname? node_a->uname : ""),
+ (node_b->uname? node_b->uname : ""));
+}
+
+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");
+ }
+ goto done;
+ break;
+
+ case pcmk_ipc_event_reply:
+ break;
+
+ default:
+ return;
+ }
+
+ if (status != CRM_EX_OK) {
+ fprintf(stderr, "error: Bad reply from controller: %s\n",
+ crm_exit_str(status));
+ goto done;
+ }
+
+ // Parse desired info from reply and display to user
+ switch (options.command) {
+ case 'i':
+ if (reply->reply_type != pcmk_controld_reply_info) {
+ fprintf(stderr,
+ "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
+ goto done;
+ }
+ if (reply->data.node_info.id == 0) {
+ fprintf(stderr,
+ "error: Controller reply did not contain node ID\n");
+ exit_code = CRM_EX_PROTOCOL;
+ goto done;
+ }
+ printf("%d\n", reply->data.node_info.id);
+ break;
+
+ case 'n':
+ case 'N':
+ if (reply->reply_type != pcmk_controld_reply_info) {
+ fprintf(stderr,
+ "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
+ goto done;
+ }
+ if (reply->data.node_info.uname == NULL) {
+ fprintf(stderr, "Node is not known to cluster\n");
+ exit_code = CRM_EX_NOHOST;
+ goto done;
+ }
+ printf("%s\n", reply->data.node_info.uname);
+ break;
+
+ case 'q':
+ if (reply->reply_type != pcmk_controld_reply_info) {
+ fprintf(stderr,
+ "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
+ goto done;
+ }
+ printf("%d\n", reply->data.node_info.have_quorum);
+ if (!(reply->data.node_info.have_quorum)) {
+ exit_code = CRM_EX_QUORUM;
+ goto done;
+ }
+ break;
+
+ case 'l':
+ case 'p':
+ if (reply->reply_type != pcmk_controld_reply_nodes) {
+ fprintf(stderr,
+ "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
+ goto done;
+ }
+ reply->data.nodes = g_list_sort(reply->data.nodes, sort_node);
+ for (GList *node_iter = reply->data.nodes;
+ node_iter != NULL; node_iter = node_iter->next) {
+
+ pcmk_controld_api_node_t *node = node_iter->data;
+ const char *uname = (node->uname? node->uname : "");
+ const char *state = (node->state? node->state : "");
+
+ if (options.command == 'l') {
+ printf("%lu %s %s\n",
+ (unsigned long) node->id, uname, state);
+
+ // i.e. CRM_NODE_MEMBER, but we don't want to include cluster.h
+ } else if (!strcmp(state, "member")) {
+ printf("%s ", uname);
+ }
+ }
+ if (options.command == 'p') {
+ printf("\n");
+ }
+ break;
+
+ default:
+ fprintf(stderr, "internal error: Controller reply not expected\n");
+ exit_code = CRM_EX_SOFTWARE;
+ goto done;
+ }
+
+ // Success
+ exit_code = CRM_EX_OK;
+done:
+ pcmk_disconnect_ipc(controld_api);
+ pcmk_quit_main_loop(mainloop, 10);
+}
+
+static void
+run_controller_mainloop(uint32_t nodeid, bool list_nodes)
+{
+ pcmk_ipc_api_t *controld_api = NULL;
+ int rc;
+
+ // Set disconnect exit code to handle unexpected disconnects
+ exit_code = CRM_EX_DISCONNECT;
+
+ // Create controller IPC object
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ return;
+ }
+ pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
+
+ // Connect to controller
+ 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",
+ pcmk_rc_str(rc));
+ exit_code = pcmk_rc2exitc(rc);
+ return;
+ }
+
+ if (list_nodes) {
+ rc = pcmk_controld_api_list_nodes(controld_api);
+ } else {
+ rc = pcmk_controld_api_node_info(controld_api, nodeid);
+ }
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not ping controller: %s\n",
+ pcmk_rc_str(rc));
+ pcmk_disconnect_ipc(controld_api);
+ exit_code = pcmk_rc2exitc(rc);
+ return;
+ }
+
+ // Run main loop to get controller reply via controller_event_cb()
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+ g_main_loop_unref(mainloop);
+ mainloop = NULL;
+ pcmk_free_ipc_api(controld_api);
+}
+
+static void
+print_node_name(void)
+{
+ // 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);
+ exit_code = CRM_EX_OK;
+ return;
+
+ } else {
+ /* Otherwise ask the controller.
+ * FIXME: Use pcmk__query_node_name() after conversion to formatted
+ * output.
+ */
+ run_controller_mainloop(0, false);
+ }
+}
+
+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__clean_up_connection(&cib);
+ return rc;
+}
+
+static int
+controller_remove_node(const char *node_name, long nodeid)
+{
+ pcmk_ipc_api_t *controld_api = NULL;
+ int rc;
+
+ // Create controller IPC object
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ return ENOTCONN;
+ }
+
+ // Connect to controller (without main loop)
+ rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ pcmk_free_ipc_api(controld_api);
+ return rc;
+ }
+
+ rc = pcmk_ipc_purge_node(controld_api, node_name, nodeid);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr,
+ "error: Could not clear node from controller's cache: %s\n",
+ pcmk_rc_str(rc));
+ }
+
+ pcmk_free_ipc_api(controld_api);
+ return pcmk_rc_ok;
+}
+
+static int
+tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
+{
+ int rc = -1;
+ crm_ipc_t *conn = NULL;
+ xmlNode *cmd = NULL;
+
+ conn = crm_ipc_new(target, 0);
+ if (!conn) {
+ return -ENOTCONN;
+ }
+ if (!crm_ipc_connect(conn)) {
+ crm_perror(LOG_ERR, "Connection to %s failed", target);
+ crm_ipc_destroy(conn);
+ return -ENOTCONN;
+ }
+
+ crm_trace("Removing %s[%ld] from the %s membership cache",
+ node_name, nodeid, target);
+
+ if(pcmk__str_eq(target, T_ATTRD, pcmk__str_casei)) {
+ cmd = create_xml_node(NULL, __func__);
+
+ crm_xml_add(cmd, F_TYPE, T_ATTRD);
+ crm_xml_add(cmd, F_ORIG, crm_system_name);
+
+ crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
+
+ pcmk__xe_add_node(cmd, node_name, nodeid);
+
+ } else { // Fencer or pacemakerd
+ cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target,
+ crm_system_name, NULL);
+ 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) {
+ // @TODO Should this be done just once after all the rest?
+ 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 rc;
+ int d = 0;
+ long nodeid = 0;
+ const char *node_name = NULL;
+ char *endptr = NULL;
+ const char *daemons[] = {
+ "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;
+ }
+
+ rc = controller_remove_node(node_name, nodeid);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ return;
+ }
+
+ for (d = 0; d < PCMK__NELEM(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);
+ exit_code = CRM_EX_ERROR;
+ return;
+ }
+ }
+ exit_code = CRM_EX_OK;
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup *group) {
+ 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, &group, 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)
+{
+ GError *error = NULL;
+
+ GOptionGroup *output_group = NULL;
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, "NR");
+ GOptionContext *context = build_arg_context(args, output_group);
+
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ pcmk__cli_init_logging("crm_node", args->verbosity);
+
+ if (args->version) {
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+ /* FIXME: When crm_node is converted to use formatted output, this can go. */
+ pcmk__cli_help('v');
+ }
+
+ if (options.command == 0) {
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ fprintf(stderr, "%s", help);
+ g_free(help);
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ 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");
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ switch (options.command) {
+ case 'n':
+ print_node_name();
+ break;
+ case 'R':
+ remove_node(options.target_uname);
+ break;
+ case 'i':
+ case 'q':
+ case 'N':
+ /* FIXME: Use pcmk__query_node_name() after conversion to formatted
+ * output
+ */
+ run_controller_mainloop(options.nodeid, false);
+ break;
+ case 'l':
+ case 'p':
+ run_controller_mainloop(0, true);
+ break;
+ default:
+ break;
+ }
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ pcmk__output_and_clear_error(&error, NULL);
+ return crm_exit(exit_code);
+}