diff --git a/include/pcmki/pcmki_cluster_queries.h b/include/pcmki/pcmki_cluster_queries.h index 30a4bd2c6a..d08675634f 100644 --- a/include/pcmki/pcmki_cluster_queries.h +++ b/include/pcmki/pcmki_cluster_queries.h @@ -1,28 +1,29 @@ /* * Copyright 2020-2022 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 PCMK__PCMKI_PCMKI_CLUSTER_QUERIES__H # define PCMK__PCMKI_PCMKI_CLUSTER_QUERIES__H #include // gboolean, GMainLoop, etc. #include #include #include #include int pcmk__controller_status(pcmk__output_t *out, const char *node_name, - guint message_timeout_ms); -int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms); + unsigned int message_timeout_ms); +int pcmk__designated_controller(pcmk__output_t *out, + unsigned int message_timeout_ms); int pcmk__pacemakerd_status(pcmk__output_t *out, const char *ipc_name, - guint message_timeout_ms, + unsigned int message_timeout_ms, enum pcmk_pacemakerd_state *state); int pcmk__list_nodes(pcmk__output_t *out, char *node_types, gboolean bash_export); #endif diff --git a/include/pcmki/pcmki_status.h b/include/pcmki/pcmki_status.h index 0dde21c6cb..9f442e7aaa 100644 --- a/include/pcmki/pcmki_status.h +++ b/include/pcmki/pcmki_status.h @@ -1,59 +1,59 @@ /* * Copyright 2022 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 PCMK__PCMKI_PCMKI_STATUS__H #define PCMK__PCMKI_PCMKI_STATUS__H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*! * \internal * \brief Print one-line status suitable for use with monitoring software * * \param[in] data_set Working set of CIB state * * \return Standard Pacemaker return code * * \note This function's output (and the return code when the program exits) * should conform to https://www.monitoring-plugins.org/doc/guidelines.html * * \note This function is planned to be deprecated and then removed in the * future. It should only be called from crm_mon, and no additional * callers should be added. */ int pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set); int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib, xmlNode *current_cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output); int pcmk__status(pcmk__output_t *out, cib_t *cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, - bool simple_output, guint timeout_ms); + bool simple_output, unsigned int timeout_ms); #ifdef __cplusplus } #endif #endif diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index d920100f82..73cd4f804c 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,666 +1,641 @@ /* * Copyright 2020-2022 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. */ #include #include // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include #include #include #include #include #include #include #include #include -#include - -#define DEFAULT_MESSAGE_TIMEOUT_MS 30000 - +//! Object to store API results, a timeout, and an output object typedef struct { pcmk__output_t *out; - GMainLoop *mainloop; int rc; - guint message_timer_id; - guint message_timeout_ms; + bool reply_received; + unsigned int message_timeout_ms; enum pcmk_pacemakerd_state pcmkd_state; } 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); -} - /*! * \internal * \brief Process a reply from the controller IPC API * * \param[in,out] data API results and options * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in] event_data \p pcmk_controld_api_reply_t object containing * event-specific 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, const 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; } /*! * \internal * \brief Process a controller status IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ 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 = (data_t *) 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; + data->reply_received = true; } - - event_done(data, controld_api); } /*! * \internal * \brief Process a designated controller IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ 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 = (data_t *) 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; + data->reply_received = true; } - - event_done(data, controld_api); } /*! * \internal * \brief Process a \p pacemakerd status IPC event * * \param[in,out] pacemakerd_api \p pacemakerd connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_pacemakerd_api_reply_t object * containing event-specific data * \param[in,out] user_data \p data_t object for API results and options */ 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; 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); data->rc = EBADMSG; 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); data->rc = EBADMSG; return; } + data->reply_received = true; + // Parse desired information from reply data->pcmkd_state = reply->data.ping.state; if (reply->data.ping.status == pcmk_rc_ok) { crm_time_t *when = crm_time_new(NULL); char *when_s = NULL; crm_time_set_timet(when, &reply->data.ping.last_good); when_s = crm_time_as_string(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.state, NULL, when_s); crm_time_free(when); free(when_s); } else { out->message(out, "pacemakerd-health", reply->data.ping.sys_from, reply->data.ping.state, "query failed", NULL); } data->rc = pcmk_rc_ok; - 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, enum pcmk_ipc_dispatch dispatch_type, bool eremoteio_ok) { 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, dispatch_type); if (rc != pcmk_rc_ok) { if ((rc == EREMOTEIO) && eremoteio_ok) { /* EREMOTEIO may be expected and acceptable for some callers. * Preserve the return code in case callers need to handle it * specially. */ } else { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); } data->rc = rc; pcmk_free_ipc_api(api); return NULL; } return api; } +/*! + * \internal + * \brief Poll an IPC API connection until timeout or a reply is received + * + * \param[in,out] data API results and options + * \param[in,out] api IPC API connection + * \param[in] on_node If not \p NULL, name of the node to poll (used only + * for logging) + * + * \note Sets the \p rc member of \p data on error + */ +static void +poll_until_reply(data_t *data, pcmk_ipc_api_t *api, const char *on_node) +{ + pcmk__output_t *out = data->out; + + uint64_t start_nsec = qb_util_nano_current_get(); + uint64_t end_nsec = start_nsec; + uint64_t elapsed_ms = 0; + uint64_t remaining_ms = data->message_timeout_ms; + + while (remaining_ms > 0) { + int rc = pcmk_poll_ipc(api, remaining_ms); + + if (rc == EAGAIN) { + // Poll timed out + break; + } + + if (rc != pcmk_rc_ok) { + out->err(out, "error: Failed to poll %s API%s%s: %s", + pcmk_ipc_name(api, true), (on_node != NULL)? " on " : "", + pcmk__s(on_node, ""), pcmk_rc_str(rc)); + data->rc = rc; + return; + } + + pcmk_dispatch_ipc(api); + + if (data->reply_received) { + return; + } + end_nsec = qb_util_nano_current_get(); + elapsed_ms = (end_nsec - start_nsec) / QB_TIME_NS_IN_MSEC; + remaining_ms = data->message_timeout_ms - elapsed_ms; + } + + out->err(out, + "error: Timed out after %ums waiting for reply from %s API%s%s", + data->message_timeout_ms, pcmk_ipc_name(api, true), + (on_node != NULL)? " on " : "", pcmk__s(on_node, "")); + data->rc = EAGAIN; +} + /*! * \internal * \brief Get and output controller status * * \param[in,out] out Output object * \param[in] node_name Name of node whose status is desired * (\p NULL for DC) * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld API. If 0, * \p pcmk_ipc_dispatch_sync will be used. - * Otherwise, \p pcmk_ipc_dispatch_main will - * be used, and a new mainloop will be - * created for this purpose (freed before - * return). + * Otherwise, \p pcmk_ipc_dispatch_poll will + * be used. * * \return Standard Pacemaker return code */ int pcmk__controller_status(pcmk__output_t *out, const char *node_name, - guint message_timeout_ms) + unsigned int message_timeout_ms) { data_t data = { .out = out, - .mainloop = NULL, .rc = pcmk_rc_ok, - .message_timer_id = 0, + .reply_received = false, .message_timeout_ms = message_timeout_ms, .pcmkd_state = pcmk_pacemakerd_state_invalid, }; - enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_main; + enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, node_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on %s: %s", pcmk__s(node_name, "DC"), pcmk_rc_str(rc)); data.rc = rc; } - if (dispatch_type == pcmk_ipc_dispatch_main) { - start_main_loop(&data); + if (dispatch_type == pcmk_ipc_dispatch_poll) { + poll_until_reply(&data, controld_api, pcmk__s(node_name, "DC")); } - pcmk_free_ipc_api(controld_api); } return data.rc; } + // Documented in header int pcmk_controller_status(xmlNodePtr *xml, const char *node_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); - rc = pcmk__controller_status(out, node_name, (guint) message_timeout_ms); + rc = pcmk__controller_status(out, node_name, message_timeout_ms); pcmk__xml_output_finish(out, xml); return rc; } /*! * \internal * \brief Get and output designated controller node name * * \param[in,out] out Output object * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld API. If 0, * \p pcmk_ipc_dispatch_sync will be used. - * Otherwise, \p pcmk_ipc_dispatch_main will - * be used, and a new mainloop will be - * created for this purpose (freed before - * return). + * Otherwise, \p pcmk_ipc_dispatch_poll will + * be used. * * \return Standard Pacemaker return code */ int -pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms) +pcmk__designated_controller(pcmk__output_t *out, + unsigned int message_timeout_ms) { data_t data = { .out = out, - .mainloop = NULL, .rc = pcmk_rc_ok, - .message_timer_id = 0, + .reply_received = false, .message_timeout_ms = message_timeout_ms, .pcmkd_state = pcmk_pacemakerd_state_invalid, }; - enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_main; + enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on DC: %s", pcmk_rc_str(rc)); data.rc = rc; } - if (dispatch_type == pcmk_ipc_dispatch_main) { - start_main_loop(&data); + if (dispatch_type == pcmk_ipc_dispatch_poll) { + poll_until_reply(&data, controld_api, "DC"); } - pcmk_free_ipc_api(controld_api); } return data.rc; } // Documented in header int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); - rc = pcmk__designated_controller(out, (guint) message_timeout_ms); + rc = pcmk__designated_controller(out, message_timeout_ms); pcmk__xml_output_finish(out, xml); return rc; } /*! * \internal * \brief Get and output \p pacemakerd status * * \param[in,out] out Output object * \param[in] ipc_name IPC name for request * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. - * Otherwise, \p pcmk_ipc_dispatch_main will - * be used, and a new mainloop will be - * created for this purpose (freed before - * return). + * Otherwise, \p pcmk_ipc_dispatch_poll will + * be used. * \param[out] state Where to store the \p pacemakerd state, if * not \p NULL * * \return Standard Pacemaker return code * * \note This function returns \p EREMOTEIO if run on a Pacemaker Remote node * with \p pacemaker-remoted running, since \p pacemakerd is not proxied * to remote nodes. The fencer and CIB may still be accessible, but * \p state will be \p pcmk_pacemakerd_state_invalid. */ int pcmk__pacemakerd_status(pcmk__output_t *out, const char *ipc_name, - guint message_timeout_ms, + unsigned int message_timeout_ms, enum pcmk_pacemakerd_state *state) { data_t data = { .out = out, - .mainloop = NULL, - .rc = pcmk_rc_ipc_unresponsive, - .message_timer_id = 0, + .rc = pcmk_rc_ok, + .reply_received = false, .message_timeout_ms = message_timeout_ms, .pcmkd_state = pcmk_pacemakerd_state_invalid, }; - enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_main; + enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *pacemakerd_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb, dispatch_type, true); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping launcher API: %s", pcmk_rc_str(rc)); data.rc = rc; } - if (dispatch_type == pcmk_ipc_dispatch_main) { - start_main_loop(&data); + if (dispatch_type == pcmk_ipc_dispatch_poll) { + poll_until_reply(&data, pacemakerd_api, NULL); } pcmk_free_ipc_api(pacemakerd_api); } if (state != NULL) { *state = data.pcmkd_state; } return data.rc; } // Documented in header int pcmk_pacemakerd_status(xmlNodePtr *xml, const char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); - rc = pcmk__pacemakerd_status(out, ipc_name, (guint) message_timeout_ms, - NULL); + rc = pcmk__pacemakerd_status(out, ipc_name, message_timeout_ms, NULL); pcmk__xml_output_finish(out, xml); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; gboolean bash_export; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, XML_ATTR_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, name ? name : id, id, data->bash_export); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, char *node_types, gboolean bash_export) { xmlNode *xml_node = NULL; int rc; rc = cib__signon_query(NULL, &xml_node); if (rc == pcmk_rc_ok) { struct node_data data = { .out = out, .found = 0, .bash_export = bash_export }; out->begin_list(out, NULL, NULL, "nodes"); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = "id"; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = "value"; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || !pcmk__strcmp(node_types, ",|^remote", pcmk__str_regex)) { data.field = "id"; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } return rc; } int pcmk_list_nodes(xmlNodePtr *xml, char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__list_nodes(out, node_types, FALSE); pcmk__xml_output_finish(out, xml); return rc; } diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c index 605068e645..4963f09d9d 100644 --- a/lib/pacemaker/pcmk_status.c +++ b/lib/pacemaker/pcmk_status.c @@ -1,392 +1,392 @@ /* * Copyright 2004-2022 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 cib_connect(pcmk__output_t *out, cib_t *cib, xmlNode **current_cib) { int rc = pcmk_rc_ok; CRM_CHECK(cib != NULL, return EINVAL); if (cib->state == cib_connected_query || cib->state == cib_connected_command) { return rc; } crm_trace("Connecting to the CIB"); rc = cib->cmds->signon(cib, crm_system_name, cib_query); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { out->err(out, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); return rc; } rc = cib->cmds->query(cib, NULL, current_cib, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); return rc; } static stonith_t * fencing_connect(void) { stonith_t *st = stonith_api_new(); int rc = pcmk_rc_ok; if (st == NULL) { return NULL; } rc = st->cmds->connect(st, crm_system_name, NULL); if (rc == pcmk_rc_ok) { return st; } else { stonith_api_delete(st); return NULL; } } /*! * \internal * \brief Output the cluster status given a fencer and CIB connection * * \param[in,out] out Output object * \param[in,out] stonith Fencer connection * \param[in,out] cib CIB connection * \param[in] current_cib Current CIB XML * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * * \return Standard Pacemaker return code */ int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib, xmlNode *current_cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output) { xmlNode *cib_copy = copy_xml(current_cib); stonith_history_t *stonith_history = NULL; int history_rc = 0; pe_working_set_t *data_set = NULL; GList *unames = NULL; GList *resources = NULL; int rc = pcmk_rc_ok; if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) { cib__clean_up_connection(&cib); free_xml(cib_copy); rc = pcmk_rc_schema_validation; out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc)); return rc; } /* get the stonith-history if there is evidence we need it */ if (fence_history != pcmk__fence_history_none) { history_rc = pcmk__get_fencing_history(stonith, &stonith_history, fence_history); } data_set = pe_new_working_set(); CRM_ASSERT(data_set != NULL); pe__set_working_set_flags(data_set, pe_flag_no_compat); data_set->input = cib_copy; data_set->priv = out; cluster_status(data_set); /* Unpack constraints if any section will need them * (tickets may be referenced in constraints but not granted yet, * and bans need negative location constraints) */ if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) { pcmk__unpack_constraints(data_set); } unames = pe__build_node_name_list(data_set, only_node); resources = pe__build_rsc_list(data_set, only_rsc); /* Always print DC if NULL. */ if (data_set->dc_node == NULL) { show |= pcmk_section_dc; } if (simple_output) { rc = pcmk__output_simple_status(out, data_set); } else { out->message(out, "cluster-status", data_set, pcmk_rc2exitc(history_rc), stonith_history, fence_history, show, show_opts, neg_location_prefix, unames, resources); } g_list_free_full(unames, free); g_list_free_full(resources, free); stonith_history_free(stonith_history); stonith_history = NULL; pe_free_working_set(data_set); return rc; } int pcmk_status(xmlNodePtr *xml) { cib_t *cib = NULL; pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; uint32_t show_opts = pcmk_show_pending | pcmk_show_inactive_rscs | pcmk_show_timing; cib = cib_new(); if (cib == NULL) { return pcmk_rc_cib_corrupt; } rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { cib_delete(cib); return rc; } pcmk__register_lib_messages(out); pe__register_messages(out); stonith__register_messages(out); rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all, show_opts, NULL, NULL, NULL, false, 0); pcmk__xml_output_finish(out, xml); cib_delete(cib); return rc; } /*! * \internal * \brief Query and output the cluster status * * The operation is considered a success if we're able to get the \p pacemakerd * state. If possible, we'll also try to connect to the fencer and CIB and * output their respective status information. * * \param[in,out] out Output object * \param[in,out] cib CIB connection * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * \param[in] timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * If positive, \p pcmk_ipc_dispatch_main * will be used, and a new mainloop will be * created for this purpose (freed before * return). * * \return Standard Pacemaker return code */ int pcmk__status(pcmk__output_t *out, cib_t *cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output, - guint timeout_ms) + unsigned int timeout_ms) { xmlNode *current_cib = NULL; int rc = pcmk_rc_ok; stonith_t *stonith = NULL; enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid; if (cib == NULL) { return ENOTCONN; } if ((cib->variant == cib_native) && (cib->state != cib_connected_query) && (cib->state != cib_connected_command)) { rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, &state); switch (rc) { case pcmk_rc_ok: switch (state) { case pcmk_pacemakerd_state_running: case pcmk_pacemakerd_state_shutting_down: // CIB may still be available while shutting down break; default: return rc; } break; case EREMOTEIO: /* We'll always get EREMOTEIO if we run this on a Pacemaker * Remote node. The fencer and CIB might be available. */ rc = pcmk_rc_ok; break; default: return rc; } } if (fence_history != pcmk__fence_history_none && cib->variant == cib_native) { stonith = fencing_connect(); } rc = cib_connect(out, cib, ¤t_cib); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__output_cluster_status(out, stonith, cib, current_cib, fence_history, show, show_opts, only_node, only_rsc, neg_location_prefix, simple_output); if (rc != pcmk_rc_ok) { out->err(out, "Error outputting status info from the fencer or CIB"); } done: if (stonith != NULL) { if (stonith->state != stonith_disconnected) { stonith->cmds->remove_notification(stonith, NULL); stonith->cmds->disconnect(stonith); } stonith_api_delete(stonith); } if (current_cib != NULL) { free_xml(current_cib); } return pcmk_rc_ok; } /* This is an internal-only function that is planned to be deprecated and removed. * It should only ever be called from crm_mon. */ int pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set) { int nodes_online = 0; int nodes_standby = 0; int nodes_maintenance = 0; GString *offline_nodes = NULL; bool no_dc = false; bool offline = false; bool has_warnings = false; if (data_set->dc_node == NULL) { has_warnings = true; no_dc = true; } for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; if (node->details->standby && node->details->online) { nodes_standby++; } else if (node->details->maintenance && node->details->online) { nodes_maintenance++; } else if (node->details->online) { nodes_online++; } else { pcmk__add_word(&offline_nodes, 1024, "offline node:"); pcmk__add_word(&offline_nodes, 0, pe__node_name(node)); has_warnings = true; offline = true; } } if (has_warnings) { out->info(out, "CLUSTER WARN: %s%s%s", no_dc ? "No DC" : "", no_dc && offline ? ", " : "", (offline? (const char *) offline_nodes->str : "")); if (offline_nodes != NULL) { g_string_free(offline_nodes, TRUE); } } else { char *nodes_standby_s = NULL; char *nodes_maint_s = NULL; if (nodes_standby > 0) { nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby, pcmk__plural_s(nodes_standby)); } if (nodes_maintenance > 0) { nodes_maint_s = crm_strdup_printf(", %d maintenance node%s", nodes_maintenance, pcmk__plural_s(nodes_maintenance)); } out->info(out, "CLUSTER OK: %d node%s online%s%s, " "%d resource instance%s configured", nodes_online, pcmk__plural_s(nodes_online), nodes_standby_s != NULL ? nodes_standby_s : "", nodes_maint_s != NULL ? nodes_maint_s : "", data_set->ninstances, pcmk__plural_s(data_set->ninstances)); free(nodes_standby_s); free(nodes_maint_s); } if (has_warnings) { return pcmk_rc_error; } else { return pcmk_rc_ok; } /* coverity[leaked_storage] False positive */ } diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 269f80521e..b467a2014f 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,273 +1,275 @@ /* * Copyright 2004-2022 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 #define SUMMARY "query and manage the Pacemaker controller" static enum { cmd_none, cmd_health, cmd_whois_dc, cmd_list_nodes, cmd_pacemakerd_health, } command = cmd_none; struct { gboolean health; gint timeout; char *optarg; char *ipc_name; gboolean bash_export; } options = { .optarg = 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", "NODE" }, { "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_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the uname of all member nodes [optionally filtered by type (comma-separated)]" "\n Types: all (default), cluster, guest, remote", "TYPE" }, { "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_CALLBACK, command_cb, "Time to wait before declaring the operation" "\n failed", "TIMESPEC" }, { "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, &options.ipc_name, "Name to use for ipc instead of 'crmadmin' (with -P/--pacemakerd).", "NAME" }, { 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; } 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; } if (!strcmp(option_name, "--timeout") || !strcmp(option_name, "-t")) { options.timeout = crm_parse_interval_spec(optarg); if (errno == EINVAL) { return FALSE; } return TRUE; } pcmk__str_update(&options.optarg, optarg); return TRUE; } static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; const char *description = "Notes:\n\n" "Time Specification:\n\n" "The TIMESPEC in any command line option can be specified in many different\n" "formats. It can be just an integer number of seconds, a number plus units\n" "(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n" "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) { crm_exit_t exit_code = CRM_EX_OK; int rc; int argerr = 0; GError *error = NULL; pcmk__output_t *out = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "itKNS"); GOptionContext *context = build_arg_context(args, &output_group); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crmadmin", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } pcmk__register_lib_messages(out); out->quiet = args->quiet; 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.health) { out->err(out, "Cluster-wide health option not supported"); ++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; } switch (command) { case cmd_health: - rc = pcmk__controller_status(out, options.optarg, options.timeout); + rc = pcmk__controller_status(out, options.optarg, + (unsigned int) options.timeout); break; case cmd_pacemakerd_health: - rc = pcmk__pacemakerd_status(out, options.ipc_name, options.timeout, - NULL); + rc = pcmk__pacemakerd_status(out, options.ipc_name, + (unsigned int) options.timeout, NULL); break; case cmd_list_nodes: rc = pcmk__list_nodes(out, options.optarg, options.bash_export); break; case cmd_whois_dc: - rc = pcmk__designated_controller(out, options.timeout); + rc = pcmk__designated_controller(out, + (unsigned int) options.timeout); break; case cmd_none: rc = pcmk_rc_error; break; } if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(error, out); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } return crm_exit(exit_code); }