summaryrefslogtreecommitdiffstats
path: root/lib/pacemaker/pcmk_cluster_queries.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pacemaker/pcmk_cluster_queries.c')
-rw-r--r--lib/pacemaker/pcmk_cluster_queries.c900
1 files changed, 900 insertions, 0 deletions
diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c
new file mode 100644
index 0000000..6002cd4
--- /dev/null
+++ b/lib/pacemaker/pcmk_cluster_queries.c
@@ -0,0 +1,900 @@
+/*
+ * 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 <crm_internal.h>
+
+#include <glib.h> // gboolean, GMainLoop, etc.
+#include <libxml/tree.h> // xmlNode
+
+#include <pacemaker.h>
+#include <pacemaker-internal.h>
+
+#include <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+#include <crm/common/iso8601.h>
+#include <crm/common/ipc_controld.h>
+#include <crm/common/ipc_pacemakerd.h>
+
+//! Object to store node info from the controller API
+typedef struct {
+ /* Adapted from pcmk_controld_api_reply_t:data:node_info.
+ * (char **) are convenient here for use within callbacks: we can skip
+ * copying strings unless the caller passes a non-NULL value.
+ */
+ uint32_t id;
+ char **node_name;
+ char **uuid;
+ char **state;
+ bool have_quorum;
+ bool is_remote;
+} node_info_t;
+
+//! Object to store API results, a timeout, and an output object
+typedef struct {
+ pcmk__output_t *out;
+ bool show_output;
+ int rc;
+ unsigned int message_timeout_ms;
+ enum pcmk_pacemakerd_state pcmkd_state;
+ node_info_t node_info;
+} data_t;
+
+/*!
+ * \internal
+ * \brief Validate that an IPC API event is a good reply
+ *
+ * \param[in,out] data API results and options
+ * \param[in] api IPC API connection
+ * \param[in] event_type Type of event that occurred
+ * \param[in] status Event status
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+validate_reply_event(data_t *data, const pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type, crm_exit_t status)
+{
+ pcmk__output_t *out = data->out;
+
+ switch (event_type) {
+ case pcmk_ipc_event_reply:
+ break;
+
+ case pcmk_ipc_event_disconnect:
+ if (data->rc == ECONNRESET) { // Unexpected
+ out->err(out, "error: Lost connection to %s",
+ pcmk_ipc_name(api, true));
+ }
+ // Nothing bad but not the reply we're looking for
+ return ENOTSUP;
+
+ default:
+ // Ditto
+ return ENOTSUP;
+ }
+
+ if (status != CRM_EX_OK) {
+ out->err(out, "error: Bad reply from %s: %s",
+ pcmk_ipc_name(api, true), crm_exit_str(status));
+ data->rc = EBADMSG;
+ return data->rc;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Validate that a controller API event is a good reply of expected type
+ *
+ * \param[in,out] data API results and options
+ * \param[in] api Controller connection
+ * \param[in] event_type Type of event that occurred
+ * \param[in] status Event status
+ * \param[in] event_data Event-specific data
+ * \param[in] expected_type Expected reply type
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+validate_controld_reply(data_t *data, const pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type, crm_exit_t status,
+ const void *event_data,
+ enum pcmk_controld_api_reply expected_type)
+{
+ pcmk__output_t *out = data->out;
+ int rc = pcmk_rc_ok;
+ const pcmk_controld_api_reply_t *reply = NULL;
+
+ rc = validate_reply_event(data, api, event_type, status);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+
+ reply = (const pcmk_controld_api_reply_t *) event_data;
+
+ if (reply->reply_type != expected_type) {
+ out->err(out, "error: Unexpected reply type '%s' from controller",
+ pcmk__controld_api_reply2str(reply->reply_type));
+ data->rc = EBADMSG;
+ return data->rc;
+ }
+
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Validate that a \p pacemakerd API event is a good reply of expected
+ * type
+ *
+ * \param[in,out] data API results and options
+ * \param[in] api \p pacemakerd connection
+ * \param[in] event_type Type of event that occurred
+ * \param[in] status Event status
+ * \param[in] event_data Event-specific data
+ * \param[in] expected_type Expected reply type
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+validate_pcmkd_reply(data_t *data, const pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type, crm_exit_t status,
+ const void *event_data,
+ enum pcmk_pacemakerd_api_reply expected_type)
+{
+ pcmk__output_t *out = data->out;
+ const pcmk_pacemakerd_api_reply_t *reply = NULL;
+ int rc = validate_reply_event(data, api, event_type, status);
+
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+
+ reply = (const pcmk_pacemakerd_api_reply_t *) event_data;
+
+ if (reply->reply_type != expected_type) {
+ out->err(out, "error: Unexpected reply type '%s' from pacemakerd",
+ pcmk__pcmkd_api_reply2str(reply->reply_type));
+ data->rc = EBADMSG;
+ return data->rc;
+ }
+
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \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;
+ const pcmk_controld_api_reply_t *reply = NULL;
+
+ int rc = validate_controld_reply(data, controld_api, event_type, status,
+ event_data, pcmk_controld_reply_ping);
+
+ if (rc != pcmk_rc_ok) {
+ return;
+ }
+
+ reply = (const pcmk_controld_api_reply_t *) event_data;
+ 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;
+}
+
+/*!
+ * \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;
+ const pcmk_controld_api_reply_t *reply = NULL;
+
+ int rc = validate_controld_reply(data, controld_api, event_type, status,
+ event_data, pcmk_controld_reply_ping);
+
+ if (rc != pcmk_rc_ok) {
+ return;
+ }
+
+ reply = (const pcmk_controld_api_reply_t *) event_data;
+ out->message(out, "dc", reply->host_from);
+ data->rc = pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Process a node info 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
+node_info_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;
+
+ const pcmk_controld_api_reply_t *reply = NULL;
+
+ int rc = validate_controld_reply(data, controld_api, event_type, status,
+ event_data, pcmk_controld_reply_info);
+
+ if (rc != pcmk_rc_ok) {
+ return;
+ }
+
+ reply = (const pcmk_controld_api_reply_t *) event_data;
+
+ if (reply->data.node_info.uname == NULL) {
+ out->err(out, "Node is not known to cluster");
+ data->rc = pcmk_rc_node_unknown;
+ return;
+ }
+
+ data->node_info.have_quorum = reply->data.node_info.have_quorum;
+ data->node_info.is_remote = reply->data.node_info.is_remote;
+ data->node_info.id = (uint32_t) reply->data.node_info.id;
+
+ pcmk__str_update(data->node_info.node_name, reply->data.node_info.uname);
+ pcmk__str_update(data->node_info.uuid, reply->data.node_info.uuid);
+ pcmk__str_update(data->node_info.state, reply->data.node_info.state);
+
+ if (data->show_output) {
+ out->message(out, "node-info",
+ reply->data.node_info.id, reply->data.node_info.uname,
+ reply->data.node_info.uuid, reply->data.node_info.state,
+ reply->data.node_info.have_quorum,
+ reply->data.node_info.is_remote);
+ }
+
+ data->rc = pcmk_rc_ok;
+}
+
+/*!
+ * \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;
+ const pcmk_pacemakerd_api_reply_t *reply = NULL;
+
+ int rc = validate_pcmkd_reply(data, pacemakerd_api, event_type, status,
+ event_data, pcmk_pacemakerd_reply_ping);
+
+ if (rc != pcmk_rc_ok) {
+ return;
+ }
+
+ // Parse desired information from reply
+ reply = (const pcmk_pacemakerd_api_reply_t *) event_data;
+
+ data->pcmkd_state = reply->data.ping.state;
+ data->rc = pcmk_rc_ok;
+
+ if (!data->show_output) {
+ return;
+ }
+
+ if (reply->data.ping.status == pcmk_rc_ok) {
+ out->message(out, "pacemakerd-health",
+ reply->data.ping.sys_from, reply->data.ping.state, NULL,
+ reply->data.ping.last_good);
+ } else {
+ out->message(out, "pacemakerd-health",
+ reply->data.ping.sys_from, reply->data.ping.state,
+ "query failed", time(NULL));
+ }
+}
+
+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) {
+ data->pcmkd_state = pcmk_pacemakerd_state_remote;
+ if (eremoteio_ok) {
+ /* EREMOTEIO may be expected and acceptable for some callers
+ * on a Pacemaker Remote node
+ */
+ rc = pcmk_rc_ok;
+ } 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->rc != EAGAIN) {
+ // Received a reply
+ 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_poll will
+ * be used.
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk__controller_status(pcmk__output_t *out, const char *node_name,
+ unsigned int message_timeout_ms)
+{
+ data_t data = {
+ .out = out,
+ .rc = EAGAIN,
+ .message_timeout_ms = message_timeout_ms,
+ };
+ 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_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, 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_poll will
+ * be used.
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk__designated_controller(pcmk__output_t *out,
+ unsigned int message_timeout_ms)
+{
+ data_t data = {
+ .out = out,
+ .rc = EAGAIN,
+ .message_timeout_ms = message_timeout_ms,
+ };
+ 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_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, message_timeout_ms);
+ pcmk__xml_output_finish(out, xml);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Get and optionally output node info corresponding to a node ID from
+ * the controller
+ *
+ * \param[in,out] out Output object
+ * \param[in,out] node_id ID of node whose name to get. If \p NULL
+ * or 0, get the local node name. If not
+ * \p NULL, store the true node ID here on
+ * success.
+ * \param[out] node_name If not \p NULL, where to store the node
+ * name
+ * \param[out] uuid If not \p NULL, where to store the node
+ * UUID
+ * \param[out] state If not \p NULL, where to store the
+ * membership state
+ * \param[out] is_remote If not \p NULL, where to store whether the
+ * node is a Pacemaker Remote node
+ * \param[out] have_quorum If not \p NULL, where to store whether the
+ * node has quorum
+ * \param[in] show_output Whether to show the node info
+ * \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_poll will
+ * be used.
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note The caller is responsible for freeing \p *node_name, \p *uuid, and
+ * \p *state using \p free().
+ */
+int
+pcmk__query_node_info(pcmk__output_t *out, uint32_t *node_id, char **node_name,
+ char **uuid, char **state, bool *have_quorum,
+ bool *is_remote, bool show_output,
+ unsigned int message_timeout_ms)
+{
+ data_t data = {
+ .out = out,
+ .show_output = show_output,
+ .rc = EAGAIN,
+ .message_timeout_ms = message_timeout_ms,
+ .node_info = {
+ .id = (node_id == NULL)? 0 : *node_id,
+ .node_name = node_name,
+ .uuid = uuid,
+ .state = state,
+ },
+ };
+ enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll;
+ pcmk_ipc_api_t *controld_api = NULL;
+
+ if (node_name != NULL) {
+ *node_name = NULL;
+ }
+ if (uuid != NULL) {
+ *uuid = NULL;
+ }
+ if (state != NULL) {
+ *state = NULL;
+ }
+
+ if (message_timeout_ms == 0) {
+ dispatch_type = pcmk_ipc_dispatch_sync;
+ }
+ controld_api = ipc_connect(&data, pcmk_ipc_controld, node_info_event_cb,
+ dispatch_type, false);
+
+ if (controld_api != NULL) {
+ int rc = pcmk_controld_api_node_info(controld_api,
+ (node_id != NULL)? *node_id : 0);
+
+ if (rc != pcmk_rc_ok) {
+ out->err(out,
+ "error: Could not send request to controller API on local "
+ "node: %s", pcmk_rc_str(rc));
+ data.rc = rc;
+ }
+
+ if (dispatch_type == pcmk_ipc_dispatch_poll) {
+ poll_until_reply(&data, controld_api, "local node");
+ }
+ pcmk_free_ipc_api(controld_api);
+ }
+
+ if (data.rc != pcmk_rc_ok) {
+ return data.rc;
+ }
+
+ // String outputs are set in callback
+ if (node_id != NULL) {
+ *node_id = data.node_info.id;
+ }
+ if (have_quorum != NULL) {
+ *have_quorum = data.node_info.have_quorum;
+ }
+ if (is_remote != NULL) {
+ *is_remote = data.node_info.is_remote;
+ }
+
+ return data.rc;
+}
+
+// Documented in header
+int
+pcmk_query_node_info(xmlNodePtr *xml, uint32_t *node_id, char **node_name,
+ char **uuid, char **state, bool *have_quorum,
+ bool *is_remote, bool show_output,
+ unsigned int message_timeout_ms)
+{
+ pcmk__output_t *out = NULL;
+ int rc = pcmk_rc_ok;
+
+ CRM_ASSERT(node_name != NULL);
+
+ rc = pcmk__xml_output_new(&out, xml);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+
+ pcmk__register_lib_messages(out);
+
+ rc = pcmk__query_node_info(out, node_id, node_name, uuid, state,
+ have_quorum, is_remote, show_output,
+ message_timeout_ms);
+ pcmk__xml_output_finish(out, xml);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Get and optionally 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_poll will
+ * be used.
+ * \param[in] show_output Whether to output the \p pacemakerd state
+ * \param[out] state Where to store the \p pacemakerd state, if
+ * not \p NULL
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This function sets \p state to \p pcmk_pacemakerd_state_remote and
+ * returns \p pcmk_rc_ok if the IPC connection attempt returns
+ * \p EREMOTEIO. That code indicates that this is a Pacemaker Remote node
+ * with \p pacemaker-remoted running. The node may be connected to the
+ * cluster.
+ */
+int
+pcmk__pacemakerd_status(pcmk__output_t *out, const char *ipc_name,
+ unsigned int message_timeout_ms, bool show_output,
+ enum pcmk_pacemakerd_state *state)
+{
+ data_t data = {
+ .out = out,
+ .show_output = show_output,
+ .rc = EAGAIN,
+ .message_timeout_ms = message_timeout_ms,
+ .pcmkd_state = pcmk_pacemakerd_state_invalid,
+ };
+ 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_poll) {
+ poll_until_reply(&data, pacemakerd_api, NULL);
+ }
+ pcmk_free_ipc_api(pacemakerd_api);
+
+ } else if ((data.pcmkd_state == pcmk_pacemakerd_state_remote)
+ && show_output) {
+ // No API connection so the callback wasn't run
+ out->message(out, "pacemakerd-health",
+ NULL, data.pcmkd_state, NULL, time(NULL));
+ }
+
+ 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, message_timeout_ms, true, 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, const char *node_types,
+ gboolean bash_export)
+{
+ xmlNode *xml_node = NULL;
+ int rc;
+
+ rc = cib__signon_query(out, 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, const 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;
+}