diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h index 37399e73a8..2e75d0976b 100644 --- a/include/pacemaker-internal.h +++ b/include/pacemaker-internal.h @@ -1,22 +1,23 @@ /* * Copyright 2019 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 PACEMAKER_INTERNAL__H # define PACEMAKER_INTERNAL__H # include +# include # include # include # include # include # include # include # include #endif diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am index 647f2dca6c..7aa64c7c3b 100644 --- a/include/pcmki/Makefile.am +++ b/include/pcmki/Makefile.am @@ -1,21 +1,22 @@ # # Copyright 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. # MAINTAINERCLEANFILES = Makefile.in noinst_HEADERS = pcmki_error.h \ + pcmki_cluster_queries.h \ pcmki_fence.h \ pcmki_output.h \ pcmki_sched_allocate.h \ pcmki_sched_notif.h \ pcmki_sched_utils.h \ pcmki_scheduler.h \ pcmki_transition.h .PHONY: $(ARCHIVE_VERSION) diff --git a/include/pcmki/pcmki_cluster_queries.h b/include/pcmki/pcmki_cluster_queries.h new file mode 100644 index 0000000000..eb3b51ccc5 --- /dev/null +++ b/include/pcmki/pcmki_cluster_queries.h @@ -0,0 +1,15 @@ +#include // gboolean, GMainLoop, etc. + +#include +#include +#include +#include + +int pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms); +int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms); +int pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms); +int pcmk__list_nodes(pcmk__output_t *out, gboolean BASH_EXPORT); + +// remove when parameters removed from tools/crmadmin.c +int pcmk__shutdown_controller(pcmk__output_t *out, char *dest_node); +int pcmk__start_election(pcmk__output_t *out); diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am index 51a811aa31..4129ade666 100644 --- a/lib/pacemaker/Makefile.am +++ b/lib/pacemaker/Makefile.am @@ -1,48 +1,49 @@ # # 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 AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) ## libraries lib_LTLIBRARIES = libpacemaker.la ## SOURCES libpacemaker_la_LDFLAGS = -version-info 2:2:1 libpacemaker_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libpacemaker_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libpacemaker_la_LIBADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/common/libcrmcommon.la # -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version # Use += rather than backlashed continuation lines for parsing by bumplibs.sh libpacemaker_la_SOURCES = +libpacemaker_la_SOURCES += pcmk_cluster_queries.c libpacemaker_la_SOURCES += pcmk_fence.c libpacemaker_la_SOURCES += pcmk_output.c libpacemaker_la_SOURCES += pcmk_sched_allocate.c libpacemaker_la_SOURCES += pcmk_sched_bundle.c libpacemaker_la_SOURCES += pcmk_sched_clone.c libpacemaker_la_SOURCES += pcmk_sched_constraints.c libpacemaker_la_SOURCES += pcmk_sched_graph.c libpacemaker_la_SOURCES += pcmk_sched_group.c libpacemaker_la_SOURCES += pcmk_sched_messages.c libpacemaker_la_SOURCES += pcmk_sched_native.c libpacemaker_la_SOURCES += pcmk_sched_notif.c libpacemaker_la_SOURCES += pcmk_sched_promotable.c libpacemaker_la_SOURCES += pcmk_sched_transition.c libpacemaker_la_SOURCES += pcmk_sched_utilization.c libpacemaker_la_SOURCES += pcmk_sched_utils.c libpacemaker_la_SOURCES += pcmk_trans_graph.c libpacemaker_la_SOURCES += pcmk_trans_unpack.c libpacemaker_la_SOURCES += pcmk_trans_utils.c diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c new file mode 100644 index 0000000000..8d729ebe29 --- /dev/null +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -0,0 +1,408 @@ +#include // gboolean, GMainLoop, etc. +#include // xmlNode + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_MESSAGE_TIMEOUT_MS 30000 + + +typedef struct { + pcmk__output_t *out; + GMainLoop *mainloop; + int rc; + guint message_timer_id; + guint message_timeout_ms; +} data_t; + +static void +quit_main_loop(data_t *data) +{ + if (data->mainloop != NULL) { + GMainLoop *mloop = data->mainloop; + + data->mainloop = NULL; // Don't re-enter this block + pcmk_quit_main_loop(mloop, 10); + g_main_loop_unref(mloop); + } +} + +static gboolean +admin_message_timeout(gpointer user_data) +{ + data_t *data = user_data; + pcmk__output_t *out = data->out; + + out->err(out, "error: No reply received from controller before timeout (%dms)", + data->message_timeout_ms); + data->message_timer_id = 0; + data->rc = ETIMEDOUT; + quit_main_loop(data); + return FALSE; // Tells glib to remove source +} + +static void +start_main_loop(data_t *data) +{ + if (data->message_timeout_ms < 1) { + data->message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; + } + + data->rc = ECONNRESET; // For unexpected disconnects + data->mainloop = g_main_loop_new(NULL, FALSE); + data->message_timer_id = g_timeout_add(data->message_timeout_ms, + admin_message_timeout, + data); + g_main_loop_run(data->mainloop); +} + +static void +event_done(data_t *data, pcmk_ipc_api_t *api) +{ + pcmk_disconnect_ipc(api); + quit_main_loop(data); +} + +static pcmk_controld_api_reply_t * +controld_event_reply(data_t *data, pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data) +{ + pcmk__output_t *out = data->out; + pcmk_controld_api_reply_t *reply = event_data; + + switch (event_type) { + case pcmk_ipc_event_disconnect: + if (data->rc == ECONNRESET) { // Unexpected + out->err(out, "error: Lost connection to controller"); + } + event_done(data, controld_api); + return NULL; + + case pcmk_ipc_event_reply: + break; + + default: + return NULL; + } + + if (data->message_timer_id != 0) { + g_source_remove(data->message_timer_id); + data->message_timer_id = 0; + } + + if (status != CRM_EX_OK) { + out->err(out, "error: Bad reply from controller: %s", + crm_exit_str(status)); + data->rc = EBADMSG; + event_done(data, controld_api); + return NULL; + } + + if (reply->reply_type != pcmk_controld_reply_ping) { + out->err(out, "error: Unknown reply type %d from controller", + reply->reply_type); + data->rc = EBADMSG; + event_done(data, controld_api); + return NULL; + } + + return reply; +} + +static void +controller_status_event_cb(pcmk_ipc_api_t *controld_api, + enum pcmk_ipc_event event_type, crm_exit_t status, + void *event_data, void *user_data) +{ + data_t *data = user_data; + pcmk__output_t *out = data->out; + pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, + event_type, status, event_data); + + if (reply != NULL) { + out->message(out, "health", + reply->data.ping.sys_from, + reply->host_from, + reply->data.ping.fsa_state, + reply->data.ping.result); + data->rc = pcmk_rc_ok; + } + + event_done(data, controld_api); +} + +static void +designated_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) +{ + data_t *data = user_data; + pcmk__output_t *out = data->out; + pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, + event_type, status, event_data); + + if (reply != NULL) { + out->message(out, "dc", reply->host_from); + data->rc = pcmk_rc_ok; + } + + event_done(data, controld_api); +} + +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) +{ + data_t *data = user_data; + pcmk__output_t *out = data->out; + pcmk_pacemakerd_api_reply_t *reply = event_data; + + crm_time_t *crm_when; + char *pinged_buf = NULL; + + switch (event_type) { + case pcmk_ipc_event_disconnect: + if (data->rc == ECONNRESET) { // Unexpected + out->err(out, "error: Lost connection to pacemakerd"); + } + event_done(data, pacemakerd_api); + return; + + case pcmk_ipc_event_reply: + break; + + default: + return; + } + + if (data->message_timer_id != 0) { + g_source_remove(data->message_timer_id); + data->message_timer_id = 0; + } + + if (status != CRM_EX_OK) { + out->err(out, "error: Bad reply from pacemakerd: %s", + crm_exit_str(status)); + event_done(data, pacemakerd_api); + return; + } + + if (reply->reply_type != pcmk_pacemakerd_reply_ping) { + out->err(out, "error: Unknown reply type %d from pacemakerd", + reply->reply_type); + event_done(data, pacemakerd_api); + return; + } + + // Parse desired information from reply + crm_when = crm_time_new(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); + + 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)?pinged_buf:""); + data->rc = pcmk_rc_ok; + crm_time_free(crm_when); + free(pinged_buf); + + event_done(data, pacemakerd_api); +} + +static pcmk_ipc_api_t * +ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb) +{ + int rc; + pcmk__output_t *out = data->out; + pcmk_ipc_api_t *api = NULL; + + + rc = pcmk_new_ipc_api(&api, server); + if (api == NULL) { + out->err(out, "error: Could not connect to %s: %s", + pcmk_ipc_name(api, true), + pcmk_rc_str(rc)); + data->rc = rc; + return NULL; + } + if (cb != NULL) { + pcmk_register_ipc_callback(api, cb, data); + } + rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Could not connect to %s: %s", + pcmk_ipc_name(api, true), + pcmk_rc_str(rc)); + data->rc = rc; + return NULL; + } + + return api; +} + +int +pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms) +{ + data_t data = { + .out = out, + .mainloop = NULL, + .rc = pcmk_rc_ok, + .message_timer_id = 0, + .message_timeout_ms = message_timeout_ms + }; + pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb); + + if (controld_api != NULL) { + int rc = pcmk_controld_api_ping(controld_api, dest_node); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + data.rc = rc; + } + + start_main_loop(&data); + + pcmk_free_ipc_api(controld_api); + } + + return data.rc; +} + +int +pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms) +{ + data_t data = { + .out = out, + .mainloop = NULL, + .rc = pcmk_rc_ok, + .message_timer_id = 0, + .message_timeout_ms = message_timeout_ms + }; + pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb); + + if (controld_api != NULL) { + int rc = pcmk_controld_api_ping(controld_api, NULL); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + data.rc = rc; + } + + start_main_loop(&data); + + pcmk_free_ipc_api(controld_api); + } + + return data.rc; +} + +int +pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms) +{ + data_t data = { + .out = out, + .mainloop = NULL, + .rc = pcmk_rc_ok, + .message_timer_id = 0, + .message_timeout_ms = message_timeout_ms + }; + pcmk_ipc_api_t *pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb); + + if (pacemakerd_api != NULL) { + int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + data.rc = rc; + } + + start_main_loop(&data); + + pcmk_free_ipc_api(pacemakerd_api); + } + + return data.rc; +} + +// \return Standard Pacemaker return code +int +pcmk__list_nodes(pcmk__output_t *out, gboolean BASH_EXPORT) +{ + 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) { + out->message(out, "crmadmin-node-list", output, BASH_EXPORT); + free_xml(output); + } + the_cib->cmds->signoff(the_cib); + return pcmk_legacy2rc(rc); +} + +// remove when parameters removed from tools/crmadmin.c +int +pcmk__shutdown_controller(pcmk__output_t *out, char *dest_node) +{ + data_t data = { + .out = out, + .mainloop = NULL, + .rc = pcmk_rc_ok, + }; + pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, NULL); + + if (controld_api != NULL) { + int rc = pcmk_controld_api_shutdown(controld_api, dest_node); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + data.rc = rc; + } + pcmk_free_ipc_api(controld_api); + } + + return data.rc; +} + +int +pcmk__start_election(pcmk__output_t *out) +{ + data_t data = { + .out = out, + .mainloop = NULL, + .rc = pcmk_rc_ok, + }; + pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, NULL); + + if (controld_api != NULL) { + int rc = pcmk_controld_api_start_election(controld_api); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + data.rc = rc; + } + + pcmk_free_ipc_api(controld_api); + } + + return data.rc; +} diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 306e561db2..fd577c6925 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -1,374 +1,551 @@ /* * Copyright 2019-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 #include #include #include pcmk__supported_format_t pcmk__out_formats[] = { PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; int pcmk__out_prologue(pcmk__output_t **out, xmlNodePtr *xml) { int rc = pcmk_rc_ok; if (*xml != NULL) { xmlFreeNode(*xml); } pcmk__register_formats(NULL, pcmk__out_formats); rc = pcmk__output_new(out, "xml", NULL, NULL); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(*out); return rc; } void pcmk__out_epilogue(pcmk__output_t *out, xmlNodePtr *xml, int retval) { if (retval == pcmk_rc_ok) { out->finish(out, 0, FALSE, (void **) xml); } pcmk__output_free(out); } PCMK__OUTPUT_ARGS("colocations-list", "pe_resource_t *", "gboolean", "gboolean") static int colocations_list(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); gboolean dependents = va_arg(args, gboolean); gboolean recursive = va_arg(args, gboolean); GListPtr lpc = NULL; GListPtr list = rsc->rsc_cons; bool printed_header = false; if (dependents) { list = rsc->rsc_cons_lhs; } if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { return pcmk_rc_no_output; } pe__set_resource_flags(rsc, pe_rsc_allocating); for (lpc = list; lpc != NULL; lpc = lpc->next) { rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; char *score = NULL; pe_resource_t *peer = cons->rsc_rh; if (dependents) { peer = cons->rsc_lh; } if (pcmk_is_set(peer->flags, pe_rsc_allocating)) { if (dependents == FALSE) { if (!printed_header) { out->begin_list(out, NULL, NULL, "Colocations"); printed_header = true; } out->list_item(out, NULL, "%s (id=%s - loop)", peer->id, cons->id); } continue; } if (dependents && recursive) { if (!printed_header) { out->begin_list(out, NULL, NULL, "Colocations"); printed_header = true; } out->message(out, "colocations-list", rsc, dependents, recursive); } if (!printed_header) { out->begin_list(out, NULL, NULL, "Colocations"); printed_header = true; } score = score2char(cons->score); if (cons->role_rh > RSC_ROLE_STARTED) { out->list_item(out, NULL, "%s (score=%s, %s role=%s, id=%s", peer->id, score, dependents ? "needs" : "with", role2text(cons->role_rh), cons->id); } else { out->list_item(out, NULL, "%s (score=%s, id=%s", peer->id, score, cons->id); } free(score); out->message(out, "locations-list", peer); if (!dependents && recursive) { out->message(out, "colocations-list", rsc, dependents, recursive); } } if (printed_header) { out->end_list(out); } return pcmk_rc_no_output; } PCMK__OUTPUT_ARGS("colocations-list", "pe_resource_t *", "gboolean", "gboolean") static int colocations_list_xml(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); gboolean dependents = va_arg(args, gboolean); gboolean recursive = va_arg(args, gboolean); GListPtr lpc = NULL; GListPtr list = rsc->rsc_cons; bool printed_header = false; if (dependents) { list = rsc->rsc_cons_lhs; } if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { return pcmk_rc_ok; } pe__set_resource_flags(rsc, pe_rsc_allocating); for (lpc = list; lpc != NULL; lpc = lpc->next) { rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; pe_resource_t *peer = cons->rsc_rh; char *score = NULL; if (dependents) { peer = cons->rsc_lh; } if (pcmk_is_set(peer->flags, pe_rsc_allocating)) { if (dependents == FALSE) { xmlNodePtr node; if (!printed_header) { pcmk__output_xml_create_parent(out, "colocations"); printed_header = true; } node = pcmk__output_create_xml_node(out, "colocation"); xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); } continue; } if (dependents && recursive) { if (!printed_header) { pcmk__output_xml_create_parent(out, "colocations"); printed_header = true; } out->message(out, "colocations-list", rsc, dependents, recursive); } if (!printed_header) { pcmk__output_xml_create_parent(out, "colocations"); printed_header = true; } score = score2char(cons->score); if (cons->role_rh > RSC_ROLE_STARTED) { xmlNodePtr node = pcmk__output_create_xml_node(out, "colocation"); xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); xmlSetProp(node, (pcmkXmlStr) "score", (pcmkXmlStr) score); xmlSetProp(node, (pcmkXmlStr) "dependents", (pcmkXmlStr) (dependents ? "needs" : "with")); xmlSetProp(node, (pcmkXmlStr) "role", (pcmkXmlStr) role2text(cons->role_rh)); } else { xmlNodePtr node = pcmk__output_create_xml_node(out, "colocation"); xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); xmlSetProp(node, (pcmkXmlStr) "score", (pcmkXmlStr) score); } free(score); out->message(out, "locations-list", peer); if (!dependents && recursive) { out->message(out, "colocations-list", rsc, dependents, recursive); } } if (printed_header) { pcmk__output_xml_pop_parent(out); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *") static int locations_list(pcmk__output_t *out, va_list args) { pe_resource_t *rsc G_GNUC_UNUSED = va_arg(args, pe_resource_t *); GListPtr lpc = NULL; GListPtr list = rsc->rsc_location; out->begin_list(out, NULL, NULL, "Locations"); for (lpc = list; lpc != NULL; lpc = lpc->next) { pe__location_t *cons = lpc->data; GListPtr lpc2 = NULL; for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) { pe_node_t *node = (pe_node_t *) lpc2->data; char *score = score2char(node->weight); out->list_item(out, NULL, "Node %s (score=%s, id=%s)", node->details->uname, score, cons->id); free(score); } } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *") static int locations_list_xml(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); GListPtr lpc = NULL; GListPtr list = rsc->rsc_location; pcmk__output_xml_create_parent(out, "locations"); for (lpc = list; lpc != NULL; lpc = lpc->next) { pe__location_t *cons = lpc->data; GListPtr lpc2 = NULL; for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) { pe_node_t *node = (pe_node_t *) lpc2->data; char *score = score2char(node->weight); xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "location"); xmlSetProp(xml_node, (pcmkXmlStr) "host", (pcmkXmlStr) node->details->uname); xmlSetProp(xml_node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); xmlSetProp(xml_node, (pcmkXmlStr) "score", (pcmkXmlStr) score); free(score); } } pcmk__output_xml_pop_parent(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean") static int stacks_and_constraints(pcmk__output_t *out, va_list args) { pe_resource_t *rsc G_GNUC_UNUSED = va_arg(args, pe_resource_t *); pe_working_set_t *data_set G_GNUC_UNUSED = va_arg(args, pe_working_set_t *); gboolean recursive G_GNUC_UNUSED = va_arg(args, gboolean); GListPtr lpc = NULL; xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); unpack_constraints(cib_constraints, data_set); // Constraints apply to group/clone, not member/instance rsc = uber_parent(rsc); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags(r, pe_rsc_allocating); } out->message(out, "colocations-list", rsc, TRUE, recursive); out->begin_list(out, NULL, NULL, "%s", rsc->id); out->message(out, "locations-list", rsc); out->end_list(out); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags(r, pe_rsc_allocating); } out->message(out, "colocations-list", rsc, FALSE, recursive); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean") static int stacks_and_constraints_xml(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); gboolean recursive = va_arg(args, gboolean); GListPtr lpc = NULL; xmlNodePtr node = NULL; xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); unpack_constraints(cib_constraints, data_set); // Constraints apply to group/clone, not member/instance rsc = uber_parent(rsc); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags(r, pe_rsc_allocating); } pcmk__output_xml_create_parent(out, "constraints"); out->message(out, "colocations-list", rsc, TRUE, recursive); node = pcmk__output_xml_create_parent(out, "resource"); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc->id); out->message(out, "locations-list", rsc); pcmk__output_xml_pop_parent(out); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags(r, pe_rsc_allocating); } out->message(out, "colocations-list", rsc, FALSE, recursive); return pcmk_rc_ok; } +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", "pcmk__output_t *", "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); + gboolean BASH_EXPORT = va_arg(args, gboolean); + + 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)), + BASH_EXPORT); + + 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 *", "gboolean") +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 *); + gboolean BASH_EXPORT = va_arg(args, gboolean); + + 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 *", "gboolean") +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[] = { { "colocations-list", "default", colocations_list }, { "colocations-list", "xml", colocations_list_xml }, { "locations-list", "default", locations_list }, { "locations-list", "xml", locations_list_xml }, { "stacks-constraints", "default", stacks_and_constraints }, { "stacks-constraints", "xml", stacks_and_constraints_xml }, + { "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 } }; void pcmk__register_lib_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } diff --git a/tools/Makefile.am b/tools/Makefile.am index a278fa352f..de64c93c2f 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,169 +1,170 @@ # # 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 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_diff.8.inc \ crm_error.8.inc \ crm_mon.sysconfig \ crm_mon.8.inc \ crm_node.8.inc \ crm_resource.8.inc \ crm_rule.8.inc \ crm_simulate.8.inc \ crmadmin.8.inc \ fix-manpages \ 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 mk/common.mk). 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 + $(top_builddir)/lib/common/libcrmcommon.la \ + $(top_builddir)/lib/pacemaker/libpacemaker.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_print.c \ crm_resource_runtime.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/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/common/libcrmcommon.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/crmadmin.c b/tools/crmadmin.c index e61dbf47f3..2d9d663a5b 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,772 +1,305 @@ /* * 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 -#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); -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 char *dest_node = NULL; -static crm_exit_t exit_code = CRM_EX_OK; -pcmk__output_t *out = NULL; - - struct { gboolean health; gint timeout; -} options; + char *dest_node; + char *ipc_name; + gboolean BASH_EXPORT; +} options = { + .dest_node = NULL, + .ipc_name = NULL, + .BASH_EXPORT = FALSE +}; 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, + { "bash-export", 'B', 0, G_OPTION_ARG_NONE, &options.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, + { "ipc-name", 'i', 0, G_OPTION_ARG_STRING, &options.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); + if (options.dest_node != NULL) { + free(options.dest_node); } - dest_node = strdup(optarg); + options.dest_node = strdup(optarg); } return TRUE; } -PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *") -static int -health_text(pcmk__output_t *out, va_list args) -{ - const char *sys_from = va_arg(args, const char *); - const char *host_from = va_arg(args, const char *); - const char *fsa_state = va_arg(args, const char *); - const char *result = va_arg(args, const 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", "const char *", "const char *", "const char *", "const char *") -static int -health_xml(pcmk__output_t *out, va_list args) -{ - const char *sys_from = va_arg(args, const char *); - const char *host_from = va_arg(args, const char *); - const char *fsa_state = va_arg(args, const char *); - const char *result = va_arg(args, const 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", "const char *", "const char *", "const char *") -static int -pacemakerd_health_text(pcmk__output_t *out, va_list args) -{ - const char *sys_from = va_arg(args, const char *); - const char *state = va_arg(args, const char *); - const char *last_updated = va_arg(args, const 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", "const char *", "const char *", "const char *") -static int -pacemakerd_health_xml(pcmk__output_t *out, va_list args) -{ - const char *sys_from = va_arg(args, const char *); - const char *state = va_arg(args, const char *); - const char *last_updated = va_arg(args, const 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", "const char *") -static int -dc_text(pcmk__output_t *out, va_list args) -{ - const char *dc = va_arg(args, const 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", "const char *") -static int -dc_xml(pcmk__output_t *out, va_list args) -{ - const char *dc = va_arg(args, const 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", "struct 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", "const char *", "const char *", "const char *") -static int -crmadmin_node_text(pcmk__output_t *out, va_list args) -{ - const char *type = va_arg(args, const char *); - const char *name = va_arg(args, const char *); - const char *id = va_arg(args, const 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", "const char *", "const char *", "const char *") -static int -crmadmin_node_xml(pcmk__output_t *out, va_list args) -{ - const char *type = va_arg(args, const char *); - const char *name = va_arg(args, const char *); - const char *id = va_arg(args, const 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 - 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) { - 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) { - 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: - out->message(out, "health", - reply->data.ping.sys_from, - reply->host_from, - reply->data.ping.fsa_state, - reply->data.ping.result); - exit_code = CRM_EX_OK; - break; - - case cmd_whois_dc: - 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 - 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) { - 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) { - 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); - - 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)?pinged_buf:""); - 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) { - 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, 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, &(args->quiet), "Display only the essential query information", 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; + pcmk__output_t *out = NULL; + crm_exit_t exit_code = CRM_EX_OK; int rc; + int argerr = 0; 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, &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); + pcmk__register_lib_messages(out); 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; } - if (options.timeout) { - message_timeout_ms = (guint) options.timeout; - if (message_timeout_ms < 1) { - message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; - } - } - if (options.health) { out->err(out, "Cluster-wide health option not supported"); ++argerr; } if (optind > argc) { ++argerr; } if (command == cmd_none) { out->err(out, "error: Must specify a command option"); ++argerr; } if (argerr) { char *help = g_option_context_get_help(context, TRUE, NULL); 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) { - 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) { - 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) { - 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) { - out->err(out, "error: Could not connect to pacemakerd: %s", - pcmk_rc_str(rc)); - exit_code = pcmk_rc2exitc(rc); - goto done; - } + switch (command) { + case cmd_health: + rc = pcmk__controller_status(out, options.dest_node, options.timeout); + break; + case cmd_pacemakerd_health: + rc = pcmk__pacemakerd_status(out, options.ipc_name, options.timeout); + break; + case cmd_list_nodes: + rc = pcmk__list_nodes(out, options.BASH_EXPORT); + break; + case cmd_whois_dc: + rc = pcmk__designated_controller(out, options.timeout); + break; + case cmd_shutdown: + rc = pcmk__shutdown_controller(out, options.dest_node); + break; + case cmd_elect_dc: + rc = pcmk__start_election(out); + break; + case cmd_none: + rc = pcmk_rc_error; + break; } - 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); + if (rc != pcmk_rc_ok) { + out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); } 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) { - 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) -{ - 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 -}