pacemaker/SOURCES/007-ipc_model.patch

4147 lines
135 KiB
Diff

From e34421b2608f235c6a77eb6de4596d93a2128be1 Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Fri, 3 Apr 2020 11:13:52 -0500
Subject: [PATCH 1/6] Refactor: libcrmcommon: new model for daemon IPC API
This is based on tools/crm_resource_controller.[ch] and the node removal code
in tools/crm_node.c, but generalized for any daemon. As of this commit, no
specific daemon is supported.
---
include/crm/common/internal.h | 8 +
include/crm/common/ipc.h | 95 +++++-
lib/common/crmcommon_private.h | 86 +++++
lib/common/ipc_client.c | 703 ++++++++++++++++++++++++++++++++++++++++-
lib/common/mainloop.c | 70 ++--
5 files changed, 940 insertions(+), 22 deletions(-)
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
index 28b20b4..13c29ea 100644
--- a/include/crm/common/internal.h
+++ b/include/crm/common/internal.h
@@ -19,6 +19,7 @@
#include <libxml/tree.h> // xmlNode
#include <crm/common/util.h> // crm_strdup_printf()
+#include <crm/common/mainloop.h> // mainloop_io_t, struct ipc_client_callbacks
// Internal ACL-related utilities (from acl.c)
@@ -103,6 +104,13 @@ pcmk__open_devnull(int flags)
} while (0)
+/* internal main loop utilities (from mainloop.c) */
+
+int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata,
+ struct ipc_client_callbacks *callbacks,
+ mainloop_io_t **source);
+
+
/* internal procfs utilities (from procfs.c) */
pid_t pcmk__procfs_pid_of(const char *name);
diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h
index a0df956..8dee1b1 100644
--- a/include/crm/common/ipc.h
+++ b/include/crm/common/ipc.h
@@ -16,7 +16,8 @@ extern "C" {
/**
* \file
- * \brief Wrappers for and extensions to libqb IPC
+ * \brief IPC interface to Pacemaker daemons
+ *
* \ingroup core
*/
@@ -48,6 +49,96 @@ xmlNode *create_request_adv(const char *task, xmlNode *xml_data,
const char *origin);
+/*
+ * The library supports two methods of creating IPC connections. The older code
+ * allows connecting to any arbitrary IPC name. The newer code only allows
+ * connecting to one of the Pacemaker daemons.
+ *
+ * As daemons are converted to use the new model, the old functions should be
+ * considered deprecated for use with those daemons. Once all daemons are
+ * converted, the old functions should be officially deprecated as public API
+ * and eventually made internal API.
+ */
+
+/*
+ * Pacemaker daemon IPC
+ */
+
+//! Available IPC interfaces
+enum pcmk_ipc_server {
+ pcmk_ipc_attrd, //!< Attribute manager
+ pcmk_ipc_based, //!< CIB manager
+ pcmk_ipc_controld, //!< Controller
+ pcmk_ipc_execd, //!< Executor
+ pcmk_ipc_fenced, //!< Fencer
+ pcmk_ipc_pacemakerd, //!< Launcher
+ pcmk_ipc_schedulerd, //!< Scheduler
+};
+
+//! Possible event types that an IPC event callback can be called for
+enum pcmk_ipc_event {
+ pcmk_ipc_event_connect, //!< Result of asynchronous connection attempt
+ pcmk_ipc_event_disconnect, //!< Termination of IPC connection
+ pcmk_ipc_event_reply, //!< Daemon's reply to client IPC request
+ pcmk_ipc_event_notify, //!< Notification from daemon
+};
+
+//! How IPC replies should be dispatched
+enum pcmk_ipc_dispatch {
+ pcmk_ipc_dispatch_main, //!< Attach IPC to GMainLoop for dispatch
+ pcmk_ipc_dispatch_poll, //!< Caller will poll and dispatch IPC
+ pcmk_ipc_dispatch_sync, //!< Sending a command will wait for any reply
+};
+
+//! Client connection to Pacemaker IPC
+typedef struct pcmk_ipc_api_s pcmk_ipc_api_t;
+
+/*!
+ * \brief Callback function type for Pacemaker daemon IPC APIs
+ *
+ * \param[in] api IPC API connection
+ * \param[in] event_type The type of event that occurred
+ * \param[in] status Event status
+ * \param[in] event_data Event-specific data
+ * \param[in] user_data Caller data provided when callback was registered
+ *
+ * \note For connection and disconnection events, event_data may be NULL (for
+ * local IPC) or the name of the connected node (for remote IPC, for
+ * daemons that support that). For reply and notify events, event_data is
+ * defined by the specific daemon API.
+ */
+typedef void (*pcmk_ipc_callback_t)(pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type,
+ crm_exit_t status,
+ void *event_data, void *user_data);
+
+int pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server);
+
+void pcmk_free_ipc_api(pcmk_ipc_api_t *api);
+
+int pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type);
+
+void pcmk_disconnect_ipc(pcmk_ipc_api_t *api);
+
+int pcmk_poll_ipc(pcmk_ipc_api_t *api, int timeout_ms);
+
+void pcmk_dispatch_ipc(pcmk_ipc_api_t *api);
+
+void pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb,
+ void *user_data);
+
+const char *pcmk_ipc_name(pcmk_ipc_api_t *api, bool for_log);
+
+bool pcmk_ipc_is_connected(pcmk_ipc_api_t *api);
+
+int pcmk_ipc_purge_node(pcmk_ipc_api_t *api, const char *node_name,
+ uint32_t nodeid);
+
+
+/*
+ * Generic IPC API (to eventually be deprecated as public API and made internal)
+ */
+
/* *INDENT-OFF* */
enum crm_ipc_flags
{
@@ -58,7 +149,7 @@ enum crm_ipc_flags
crm_ipc_proxied = 0x00000100, /* _ALL_ replies to proxied connections need to be sent as events */
crm_ipc_client_response = 0x00000200, /* A Response is expected in reply */
- // These are options only for pcmk__ipc_send_iov()
+ // These are options for Pacemaker's internal use only (pcmk__ipc_send_*())
crm_ipc_server_event = 0x00010000, /* Send an Event instead of a Response */
crm_ipc_server_free = 0x00020000, /* Free the iovec after sending */
crm_ipc_proxied_relay_response = 0x00040000, /* all replies to proxied connections are sent as events, this flag preserves whether the event should be treated as an actual event, or a response.*/
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
index d06fa20..f9df27d 100644
--- a/lib/common/crmcommon_private.h
+++ b/lib/common/crmcommon_private.h
@@ -103,6 +103,84 @@ pcmk__xml_attr_value(const xmlAttr *attr)
#define PCMK__IPC_VERSION 1
+// IPC behavior that varies by daemon
+typedef struct pcmk__ipc_methods_s {
+ /*!
+ * \internal
+ * \brief Allocate any private data needed by daemon IPC
+ *
+ * \param[in] api IPC API connection
+ *
+ * \return Standard Pacemaker return code
+ */
+ int (*new_data)(pcmk_ipc_api_t *api);
+
+ /*!
+ * \internal
+ * \brief Free any private data used by daemon IPC
+ *
+ * \param[in] api_data Data allocated by new_data() method
+ */
+ void (*free_data)(void *api_data);
+
+ /*!
+ * \internal
+ * \brief Perform daemon-specific handling after successful connection
+ *
+ * Some daemons require clients to register before sending any other
+ * commands. The controller requires a CRM_OP_HELLO (with no reply), and
+ * the CIB manager, executor, and fencer require a CRM_OP_REGISTER (with a
+ * reply). Ideally this would be consistent across all daemons, but for now
+ * this allows each to do its own authorization.
+ *
+ * \param[in] api IPC API connection
+ *
+ * \return Standard Pacemaker return code
+ */
+ int (*post_connect)(pcmk_ipc_api_t *api);
+
+ /*!
+ * \internal
+ * \brief Check whether an IPC request results in a reply
+ *
+ * \parma[in] api IPC API connection
+ * \param[in] request IPC request XML
+ *
+ * \return true if request would result in an IPC reply, false otherwise
+ */
+ bool (*reply_expected)(pcmk_ipc_api_t *api, xmlNode *request);
+
+ /*!
+ * \internal
+ * \brief Perform daemon-specific handling of an IPC message
+ *
+ * \param[in] api IPC API connection
+ * \param[in] msg Message read from IPC connection
+ */
+ void (*dispatch)(pcmk_ipc_api_t *api, xmlNode *msg);
+
+ /*!
+ * \internal
+ * \brief Perform daemon-specific handling of an IPC disconnect
+ *
+ * \param[in] api IPC API connection
+ */
+ void (*post_disconnect)(pcmk_ipc_api_t *api);
+} pcmk__ipc_methods_t;
+
+// Implementation of pcmk_ipc_api_t
+struct pcmk_ipc_api_s {
+ enum pcmk_ipc_server server; // Daemon this IPC API instance is for
+ enum pcmk_ipc_dispatch dispatch_type; // How replies should be dispatched
+ crm_ipc_t *ipc; // IPC connection
+ mainloop_io_t *mainloop_io; // If using mainloop, I/O source for IPC
+ bool free_on_disconnect; // Whether disconnect should free object
+ pcmk_ipc_callback_t cb; // Caller-registered callback (if any)
+ void *user_data; // Caller-registered data (if any)
+ void *api_data; // For daemon-specific use
+ pcmk__ipc_methods_t *cmds; // Behavior that varies by daemon
+};
+
typedef struct pcmk__ipc_header_s {
struct qb_ipc_response_header qb;
uint32_t size_uncompressed;
@@ -112,6 +190,14 @@ typedef struct pcmk__ipc_header_s {
} pcmk__ipc_header_t;
G_GNUC_INTERNAL
+int pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request);
+
+G_GNUC_INTERNAL
+void pcmk__call_ipc_callback(pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type,
+ crm_exit_t status, void *event_data);
+
+G_GNUC_INTERNAL
unsigned int pcmk__ipc_buffer_size(unsigned int max);
G_GNUC_INTERNAL
diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c
index 7737588..16dc9b5 100644
--- a/lib/common/ipc_client.c
+++ b/lib/common/ipc_client.c
@@ -31,6 +31,679 @@
#include <crm/common/ipc_internal.h>
#include "crmcommon_private.h"
+/*!
+ * \brief Create a new object for using Pacemaker daemon IPC
+ *
+ * \param[out] api Where to store new IPC object
+ * \param[in] server Which Pacemaker daemon the object is for
+ *
+ * \return Standard Pacemaker result code
+ *
+ * \note The caller is responsible for freeing *api using pcmk_free_ipc_api().
+ * \note This is intended to supersede crm_ipc_new() but is not yet usable.
+ */
+int
+pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server)
+{
+ size_t max_size = 0;
+
+ if (api == NULL) {
+ return EINVAL;
+ }
+
+ *api = calloc(1, sizeof(pcmk_ipc_api_t));
+ if (*api == NULL) {
+ return errno;
+ }
+
+ (*api)->server = server;
+ if (pcmk_ipc_name(*api, false) == NULL) {
+ pcmk_free_ipc_api(*api);
+ *api = NULL;
+ return EOPNOTSUPP;
+ }
+
+ // Set server methods and max_size (if not default)
+ switch (server) {
+ case pcmk_ipc_attrd:
+ break;
+
+ case pcmk_ipc_based:
+ max_size = 512 * 1024; // 512KB
+ break;
+
+ case pcmk_ipc_controld:
+ break;
+
+ case pcmk_ipc_execd:
+ break;
+
+ case pcmk_ipc_fenced:
+ break;
+
+ case pcmk_ipc_pacemakerd:
+ break;
+
+ case pcmk_ipc_schedulerd:
+ // @TODO max_size could vary by client, maybe take as argument?
+ max_size = 5 * 1024 * 1024; // 5MB
+ break;
+ }
+ if ((*api)->cmds == NULL) {
+ pcmk_free_ipc_api(*api);
+ *api = NULL;
+ return ENOMEM;
+ }
+
+ (*api)->ipc = crm_ipc_new(pcmk_ipc_name(*api, false), max_size);
+ if ((*api)->ipc == NULL) {
+ pcmk_free_ipc_api(*api);
+ *api = NULL;
+ return ENOMEM;
+ }
+
+ // If daemon API has its own data to track, allocate it
+ if ((*api)->cmds->new_data != NULL) {
+ if ((*api)->cmds->new_data(*api) != pcmk_rc_ok) {
+ pcmk_free_ipc_api(*api);
+ *api = NULL;
+ return ENOMEM;
+ }
+ }
+ crm_trace("Created %s API IPC object", pcmk_ipc_name(*api, true));
+ return pcmk_rc_ok;
+}
+
+static void
+free_daemon_specific_data(pcmk_ipc_api_t *api)
+{
+ if ((api != NULL) && (api->cmds != NULL)) {
+ if ((api->cmds->free_data != NULL) && (api->api_data != NULL)) {
+ api->cmds->free_data(api->api_data);
+ api->api_data = NULL;
+ }
+ free(api->cmds);
+ api->cmds = NULL;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Call an IPC API event callback, if one is registed
+ *
+ * \param[in] api IPC API connection
+ * \param[in] event_type The type of event that occurred
+ * \param[in] status Event status
+ * \param[in] event_data Event-specific data
+ */
+void
+pcmk__call_ipc_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type,
+ crm_exit_t status, void *event_data)
+{
+ if ((api != NULL) && (api->cb != NULL)) {
+ api->cb(api, event_type, status, event_data, api->user_data);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Clean up after an IPC disconnect
+ *
+ * \param[in] user_data IPC API connection that disconnected
+ *
+ * \note This function can be used as a main loop IPC destroy callback.
+ */
+static void
+ipc_post_disconnect(gpointer user_data)
+{
+ pcmk_ipc_api_t *api = user_data;
+
+ crm_info("Disconnected from %s IPC API", pcmk_ipc_name(api, true));
+
+ // Perform any daemon-specific handling needed
+ if ((api->cmds != NULL) && (api->cmds->post_disconnect != NULL)) {
+ api->cmds->post_disconnect(api);
+ }
+
+ // Call client's registered event callback
+ pcmk__call_ipc_callback(api, pcmk_ipc_event_disconnect, CRM_EX_DISCONNECT,
+ NULL);
+
+ /* If this is being called from a running main loop, mainloop_gio_destroy()
+ * will free ipc and mainloop_io immediately after calling this function.
+ * If this is called from a stopped main loop, these will leak, so the best
+ * practice is to close the connection before stopping the main loop.
+ */
+ api->ipc = NULL;
+ api->mainloop_io = NULL;
+
+ if (api->free_on_disconnect) {
+ /* pcmk_free_ipc_api() has already been called, but did not free api
+ * or api->cmds because this function needed them. Do that now.
+ */
+ free_daemon_specific_data(api);
+ crm_trace("Freeing IPC API object after disconnect");
+ free(api);
+ }
+}
+
+/*!
+ * \brief Free the contents of an IPC API object
+ *
+ * \param[in] api IPC API object to free
+ */
+void
+pcmk_free_ipc_api(pcmk_ipc_api_t *api)
+{
+ bool free_on_disconnect = false;
+
+ if (api == NULL) {
+ return;
+ }
+ crm_debug("Releasing %s IPC API", pcmk_ipc_name(api, true));
+
+ if (api->ipc != NULL) {
+ if (api->mainloop_io != NULL) {
+ /* We need to keep the api pointer itself around, because it is the
+ * user data for the IPC client destroy callback. That will be
+ * triggered by the pcmk_disconnect_ipc() call below, but it might
+ * happen later in the main loop (if still running).
+ *
+ * This flag tells the destroy callback to free the object. It can't
+ * do that unconditionally, because the application might call this
+ * function after a disconnect that happened by other means.
+ */
+ free_on_disconnect = api->free_on_disconnect = true;
+ }
+ pcmk_disconnect_ipc(api); // Frees api if free_on_disconnect is true
+ }
+ if (!free_on_disconnect) {
+ free_daemon_specific_data(api);
+ crm_trace("Freeing IPC API object");
+ free(api);
+ }
+}
+
+/*!
+ * \brief Get the IPC name used with an IPC API connection
+ *
+ * \param[in] api IPC API connection
+ * \param[in] for_log If true, return human-friendly name instead of IPC name
+ *
+ * \return IPC API's human-friendly or connection name, or if none is available,
+ * "Pacemaker" if for_log is true and NULL if for_log is false
+ */
+const char *
+pcmk_ipc_name(pcmk_ipc_api_t *api, bool for_log)
+{
+ if (api == NULL) {
+ return for_log? "Pacemaker" : NULL;
+ }
+ switch (api->server) {
+ case pcmk_ipc_attrd:
+ return for_log? "attribute manager" : NULL /* T_ATTRD */;
+
+ case pcmk_ipc_based:
+ return for_log? "CIB manager" : NULL /* PCMK__SERVER_BASED_RW */;
+
+ case pcmk_ipc_controld:
+ return for_log? "controller" : NULL /* CRM_SYSTEM_CRMD */;
+
+ case pcmk_ipc_execd:
+ return for_log? "executor" : NULL /* CRM_SYSTEM_LRMD */;
+
+ case pcmk_ipc_fenced:
+ return for_log? "fencer" : NULL /* "stonith-ng" */;
+
+ case pcmk_ipc_pacemakerd:
+ return for_log? "launcher" : NULL /* CRM_SYSTEM_MCP */;
+
+ case pcmk_ipc_schedulerd:
+ return for_log? "scheduler" : NULL /* CRM_SYSTEM_PENGINE */;
+
+ default:
+ return for_log? "Pacemaker" : NULL;
+ }
+}
+
+/*!
+ * \brief Check whether an IPC API connection is active
+ *
+ * \param[in] api IPC API connection
+ *
+ * \return true if IPC is connected, false otherwise
+ */
+bool
+pcmk_ipc_is_connected(pcmk_ipc_api_t *api)
+{
+ return (api != NULL) && crm_ipc_connected(api->ipc);
+}
+
+/*!
+ * \internal
+ * \brief Call the daemon-specific API's dispatch function
+ *
+ * Perform daemon-specific handling of IPC reply dispatch. It is the daemon
+ * method's responsibility to call the client's registered event callback, as
+ * well as allocate and free any event data.
+ *
+ * \param[in] api IPC API connection
+ */
+static void
+call_api_dispatch(pcmk_ipc_api_t *api, xmlNode *message)
+{
+ crm_log_xml_trace(message, "ipc-received");
+ if ((api->cmds != NULL) && (api->cmds->dispatch != NULL)) {
+ api->cmds->dispatch(api, message);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Dispatch data read from IPC source
+ *
+ * \param[in] buffer Data read from IPC
+ * \param[in] length Number of bytes of data in buffer (ignored)
+ * \param[in] user_data IPC object
+ *
+ * \return Always 0 (meaning connection is still required)
+ *
+ * \note This function can be used as a main loop IPC dispatch callback.
+ */
+static int
+dispatch_ipc_data(const char *buffer, ssize_t length, gpointer user_data)
+{
+ pcmk_ipc_api_t *api = user_data;
+ xmlNode *msg;
+
+ CRM_CHECK(api != NULL, return 0);
+
+ if (buffer == NULL) {
+ crm_warn("Empty message received from %s IPC",
+ pcmk_ipc_name(api, true));
+ return 0;
+ }
+
+ msg = string2xml(buffer);
+ if (msg == NULL) {
+ crm_warn("Malformed message received from %s IPC",
+ pcmk_ipc_name(api, true));
+ return 0;
+ }
+ call_api_dispatch(api, msg);
+ free_xml(msg);
+ return 0;
+}
+
+/*!
+ * \brief Check whether an IPC connection has data available (without main loop)
+ *
+ * \param[in] api IPC API connection
+ * \param[in] timeout_ms If less than 0, poll indefinitely; if 0, poll once
+ * and return immediately; otherwise, poll for up to
+ * this many milliseconds
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note Callers of pcmk_connect_ipc() using pcmk_ipc_dispatch_poll should call
+ * this function to check whether IPC data is available. Return values of
+ * interest include pcmk_rc_ok meaning data is available, and EAGAIN
+ * meaning no data is available; all other values indicate errors.
+ * \todo This does not allow the caller to poll multiple file descriptors at
+ * once. If there is demand for that, we could add a wrapper for
+ * crm_ipc_get_fd(api->ipc), so the caller can call poll() themselves.
+ */
+int
+pcmk_poll_ipc(pcmk_ipc_api_t *api, int timeout_ms)
+{
+ int rc;
+ struct pollfd pollfd = { 0, };
+
+ if ((api == NULL) || (api->dispatch_type != pcmk_ipc_dispatch_poll)) {
+ return EINVAL;
+ }
+ pollfd.fd = crm_ipc_get_fd(api->ipc);
+ pollfd.events = POLLIN;
+ rc = poll(&pollfd, 1, timeout_ms);
+ if (rc < 0) {
+ return errno;
+ } else if (rc == 0) {
+ return EAGAIN;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \brief Dispatch available messages on an IPC connection (without main loop)
+ *
+ * \param[in] api IPC API connection
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note Callers of pcmk_connect_ipc() using pcmk_ipc_dispatch_poll should call
+ * this function when IPC data is available.
+ */
+void
+pcmk_dispatch_ipc(pcmk_ipc_api_t *api)
+{
+ if (api == NULL) {
+ return;
+ }
+ while (crm_ipc_ready(api->ipc)) {
+ if (crm_ipc_read(api->ipc) > 0) {
+ dispatch_ipc_data(crm_ipc_buffer(api->ipc), 0, api);
+ }
+ }
+}
+
+// \return Standard Pacemaker return code
+static int
+connect_with_main_loop(pcmk_ipc_api_t *api)
+{
+ int rc;
+
+ struct ipc_client_callbacks callbacks = {
+ .dispatch = dispatch_ipc_data,
+ .destroy = ipc_post_disconnect,
+ };
+
+ rc = pcmk__add_mainloop_ipc(api->ipc, G_PRIORITY_DEFAULT, api,
+ &callbacks, &(api->mainloop_io));
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+ crm_debug("Connected to %s IPC (attached to main loop)",
+ pcmk_ipc_name(api, true));
+ /* After this point, api->mainloop_io owns api->ipc, so api->ipc
+ * should not be explicitly freed.
+ */
+ return pcmk_rc_ok;
+}
+
+// \return Standard Pacemaker return code
+static int
+connect_without_main_loop(pcmk_ipc_api_t *api)
+{
+ int rc;
+
+ if (!crm_ipc_connect(api->ipc)) {
+ rc = errno;
+ crm_ipc_close(api->ipc);
+ return rc;
+ }
+ crm_debug("Connected to %s IPC (without main loop)",
+ pcmk_ipc_name(api, true));
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \brief Connect to a Pacemaker daemon via IPC
+ *
+ * \param[in] api IPC API instance
+ * \param[out] dispatch_type How IPC replies should be dispatched
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type)
+{
+ int rc = pcmk_rc_ok;
+
+ if ((api == NULL) || (api->ipc == NULL)) {
+ crm_err("Cannot connect to uninitialized API object");
+ return EINVAL;
+ }
+
+ if (crm_ipc_connected(api->ipc)) {
+ crm_trace("Already connected to %s IPC API", pcmk_ipc_name(api, true));
+ return pcmk_rc_ok;
+ }
+
+ api->dispatch_type = dispatch_type;
+ switch (dispatch_type) {
+ case pcmk_ipc_dispatch_main:
+ rc = connect_with_main_loop(api);
+ break;
+
+ case pcmk_ipc_dispatch_sync:
+ case pcmk_ipc_dispatch_poll:
+ rc = connect_without_main_loop(api);
+ break;
+ }
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+
+ if ((api->cmds != NULL) && (api->cmds->post_connect != NULL)) {
+ rc = api->cmds->post_connect(api);
+ if (rc != pcmk_rc_ok) {
+ crm_ipc_close(api->ipc);
+ }
+ }
+ return rc;
+}
+
+/*!
+ * \brief Disconnect an IPC API instance
+ *
+ * \param[in] api IPC API connection
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note If the connection is attached to a main loop, this function should be
+ * called before quitting the main loop, to ensure that all memory is
+ * freed.
+ */
+void
+pcmk_disconnect_ipc(pcmk_ipc_api_t *api)
+{
+ if ((api == NULL) || (api->ipc == NULL)) {
+ return;
+ }
+ switch (api->dispatch_type) {
+ case pcmk_ipc_dispatch_main:
+ {
+ mainloop_io_t *mainloop_io = api->mainloop_io;
+
+ // Make sure no code with access to api can use these again
+ api->mainloop_io = NULL;
+ api->ipc = NULL;
+
+ mainloop_del_ipc_client(mainloop_io);
+ // After this point api might have already been freed
+ }
+ break;
+
+ case pcmk_ipc_dispatch_poll:
+ case pcmk_ipc_dispatch_sync:
+ {
+ crm_ipc_t *ipc = api->ipc;
+
+ // Make sure no code with access to api can use ipc again
+ api->ipc = NULL;
+
+ // This should always be the case already, but to be safe
+ api->free_on_disconnect = false;
+
+ crm_ipc_destroy(ipc);
+ ipc_post_disconnect(api);
+ }
+ break;
+ }
+}
+
+/*!
+ * \brief Register a callback for IPC API events
+ *
+ * \param[in] api IPC API connection
+ * \param[in] callback Callback to register
+ * \param[in] userdata Caller data to pass to callback
+ *
+ * \note This function may be called multiple times to update the callback
+ * and/or user data. The caller remains responsible for freeing
+ * userdata in any case (after the IPC is disconnected, if the
+ * user data is still registered with the IPC).
+ */
+void
+pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb,
+ void *user_data)
+{
+ if (api == NULL) {
+ return;
+ }
+ api->cb = cb;
+ api->user_data = user_data;
+}
+
+/*!
+ * \internal
+ * \brief Send an XML request across an IPC API connection
+ *
+ * \param[in] api IPC API connection
+ * \param[in] request XML request to send
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note Daemon-specific IPC API functions should call this function to send
+ * requests, because it handles different dispatch types appropriately.
+ */
+int
+pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request)
+{
+ int rc;
+ xmlNode *reply = NULL;
+ enum crm_ipc_flags flags = crm_ipc_flags_none;
+
+ if ((api == NULL) || (api->ipc == NULL) || (request == NULL)) {
+ return EINVAL;
+ }
+ crm_log_xml_trace(request, "ipc-sent");
+
+ // Synchronous dispatch requires waiting for a reply
+ if ((api->dispatch_type == pcmk_ipc_dispatch_sync)
+ && (api->cmds != NULL)
+ && (api->cmds->reply_expected != NULL)
+ && (api->cmds->reply_expected(api, request))) {
+ flags = crm_ipc_client_response;
+ }
+
+ // The 0 here means a default timeout of 5 seconds
+ rc = crm_ipc_send(api->ipc, request, flags, 0, &reply);
+
+ if (rc < 0) {
+ return pcmk_legacy2rc(rc);
+ } else if (rc == 0) {
+ return ENODATA;
+ }
+
+ // With synchronous dispatch, we dispatch any reply now
+ if (reply != NULL) {
+ call_api_dispatch(api, reply);
+ free_xml(reply);
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Create the XML for an IPC request to purge a node from the peer cache
+ *
+ * \param[in] api IPC API connection
+ * \param[in] node_name If not NULL, name of node to purge
+ * \param[in] nodeid If not 0, node ID of node to purge
+ *
+ * \return Newly allocated IPC request XML
+ *
+ * \note The controller, fencer, and pacemakerd use the same request syntax, but
+ * the attribute manager uses a different one. The CIB manager doesn't
+ * have any syntax for it. The executor and scheduler don't connect to the
+ * cluster layer and thus don't have or need any syntax for it.
+ *
+ * \todo Modify the attribute manager to accept the common syntax (as well
+ * as its current one, for compatibility with older clients). Modify
+ * the CIB manager to accept and honor the common syntax. Modify the
+ * executor and scheduler to accept the syntax (immediately returning
+ * success), just for consistency. Modify this function to use the
+ * common syntax with all daemons if their version supports it.
+ */
+static xmlNode *
+create_purge_node_request(pcmk_ipc_api_t *api, const char *node_name,
+ uint32_t nodeid)
+{
+ xmlNode *request = NULL;
+ const char *client = crm_system_name? crm_system_name : "client";
+
+ switch (api->server) {
+ case pcmk_ipc_attrd:
+ request = create_xml_node(NULL, __FUNCTION__);
+ crm_xml_add(request, F_TYPE, T_ATTRD);
+ crm_xml_add(request, F_ORIG, crm_system_name);
+ crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
+ crm_xml_add(request, PCMK__XA_ATTR_NODE_NAME, node_name);
+ if (nodeid > 0) {
+ crm_xml_add_int(request, PCMK__XA_ATTR_NODE_ID, (int) nodeid);
+ }
+ break;
+
+ case pcmk_ipc_controld:
+ case pcmk_ipc_fenced:
+ case pcmk_ipc_pacemakerd:
+ request = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL,
+ pcmk_ipc_name(api, false), client, NULL);
+ if (nodeid > 0) {
+ crm_xml_set_id(request, "%lu", (unsigned long) nodeid);
+ }
+ crm_xml_add(request, XML_ATTR_UNAME, node_name);
+ break;
+
+ case pcmk_ipc_based:
+ case pcmk_ipc_execd:
+ case pcmk_ipc_schedulerd:
+ break;
+ }
+ return request;
+}
+
+/*!
+ * \brief Ask a Pacemaker daemon to purge a node from its peer cache
+ *
+ * \param[in] api IPC API connection
+ * \param[in] node_name If not NULL, name of node to purge
+ * \param[in] nodeid If not 0, node ID of node to purge
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note At least one of node_name or nodeid must be specified.
+ */
+int
+pcmk_ipc_purge_node(pcmk_ipc_api_t *api, const char *node_name, uint32_t nodeid)
+{
+ int rc = 0;
+ xmlNode *request = NULL;
+
+ if (api == NULL) {
+ return EINVAL;
+ }
+ if ((node_name == NULL) && (nodeid == 0)) {
+ return EINVAL;
+ }
+
+ request = create_purge_node_request(api, node_name, nodeid);
+ if (request == NULL) {
+ return EOPNOTSUPP;
+ }
+ rc = pcmk__send_ipc_request(api, request);
+ free_xml(request);
+
+ crm_debug("%s peer cache purge of node %s[%lu]: rc=%d",
+ pcmk_ipc_name(api, true), node_name, (unsigned long) nodeid, rc);
+ return rc;
+}
+
+/*
+ * Generic IPC API (to eventually be deprecated as public API and made internal)
+ */
+
struct crm_ipc_s {
struct pollfd pfd;
unsigned int max_buf_size; // maximum bytes we can send or receive over IPC
@@ -42,16 +715,44 @@ struct crm_ipc_s {
qb_ipcc_connection_t *ipc;
};
+/*!
+ * \brief Create a new (legacy) object for using Pacemaker daemon IPC
+ *
+ * \param[in] name IPC system name to connect to
+ * \param[in] max_size Use a maximum IPC buffer size of at least this size
+ *
+ * \return Newly allocated IPC object on success, NULL otherwise
+ *
+ * \note The caller is responsible for freeing the result using
+ * crm_ipc_destroy().
+ * \note This should be considered deprecated for use with daemons supported by
+ * pcmk_new_ipc_api().
+ */
crm_ipc_t *
crm_ipc_new(const char *name, size_t max_size)
{
crm_ipc_t *client = NULL;
client = calloc(1, sizeof(crm_ipc_t));
+ if (client == NULL) {
+ crm_err("Could not create IPC connection: %s", strerror(errno));
+ return NULL;
+ }
client->name = strdup(name);
+ if (client->name == NULL) {
+ crm_err("Could not create IPC connection: %s", strerror(errno));
+ free(client);
+ return NULL;
+ }
client->buf_size = pcmk__ipc_buffer_size(max_size);
client->buffer = malloc(client->buf_size);
+ if (client->buffer == NULL) {
+ crm_err("Could not create IPC connection: %s", strerror(errno));
+ free(client->name);
+ free(client);
+ return NULL;
+ }
/* Clients initiating connection pick the max buf size */
client->max_buf_size = client->buf_size;
@@ -143,8 +844,6 @@ void
crm_ipc_close(crm_ipc_t * client)
{
if (client) {
- crm_trace("Disconnecting %s IPC connection %p (%p)", client->name, client, client->ipc);
-
if (client->ipc) {
qb_ipcc_connection_t *ipc = client->ipc;
diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c
index 634eead..e942e57 100644
--- a/lib/common/mainloop.c
+++ b/lib/common/mainloop.c
@@ -834,32 +834,66 @@ mainloop_gio_destroy(gpointer c)
free(c_name);
}
-mainloop_io_t *
-mainloop_add_ipc_client(const char *name, int priority, size_t max_size, void *userdata,
- struct ipc_client_callbacks *callbacks)
+/*!
+ * \brief Connect to IPC and add it as a main loop source
+ *
+ * \param[in] ipc IPC connection to add
+ * \param[in] priority Event source priority to use for connection
+ * \param[in] userdata Data to register with callbacks
+ * \param[in] callbacks Dispatch and destroy callbacks for connection
+ * \param[out] source Newly allocated event source
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note On failure, the caller is still responsible for ipc. On success, the
+ * caller should call mainloop_del_ipc_client() when source is no longer
+ * needed, which will lead to the disconnection of the IPC later in the
+ * main loop if it is connected. However the IPC disconnects,
+ * mainloop_gio_destroy() will free ipc and source after calling the
+ * destroy callback.
+ */
+int
+pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata,
+ struct ipc_client_callbacks *callbacks,
+ mainloop_io_t **source)
{
- mainloop_io_t *client = NULL;
- crm_ipc_t *conn = crm_ipc_new(name, max_size);
+ CRM_CHECK((ipc != NULL) && (callbacks != NULL), return EINVAL);
- if (conn && crm_ipc_connect(conn)) {
- int32_t fd = crm_ipc_get_fd(conn);
+ if (!crm_ipc_connect(ipc)) {
+ return ENOTCONN;
+ }
+ *source = mainloop_add_fd(crm_ipc_name(ipc), priority, crm_ipc_get_fd(ipc),
+ userdata, NULL);
+ if (*source == NULL) {
+ int rc = errno;
- client = mainloop_add_fd(name, priority, fd, userdata, NULL);
+ crm_ipc_close(ipc);
+ return rc;
}
+ (*source)->ipc = ipc;
+ (*source)->destroy_fn = callbacks->destroy;
+ (*source)->dispatch_fn_ipc = callbacks->dispatch;
+ return pcmk_rc_ok;
+}
- if (client == NULL) {
- crm_perror(LOG_TRACE, "Connection to %s failed", name);
- if (conn) {
- crm_ipc_close(conn);
- crm_ipc_destroy(conn);
+mainloop_io_t *
+mainloop_add_ipc_client(const char *name, int priority, size_t max_size,
+ void *userdata, struct ipc_client_callbacks *callbacks)
+{
+ crm_ipc_t *ipc = crm_ipc_new(name, max_size);
+ mainloop_io_t *source = NULL;
+ int rc = pcmk__add_mainloop_ipc(ipc, priority, userdata, callbacks,
+ &source);
+
+ if (rc != pcmk_rc_ok) {
+ if (crm_log_level == LOG_STDOUT) {
+ fprintf(stderr, "Connection to %s failed: %s",
+ name, pcmk_rc_str(rc));
}
+ crm_ipc_destroy(ipc);
return NULL;
}
-
- client->ipc = conn;
- client->destroy_fn = callbacks->destroy;
- client->dispatch_fn_ipc = callbacks->dispatch;
- return client;
+ return source;
}
void
--
1.8.3.1
From b9539da27998ff5e6c8b681f39603550a923ca33 Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Mon, 6 Apr 2020 17:40:55 -0500
Subject: [PATCH 2/6] Refactor: libcrmcommon: add C API for controller IPC
Implement a C API for controller IPC using the new IPC API model.
---
include/crm/common/Makefile.am | 2 +-
include/crm/common/ipc.h | 4 +-
include/crm/common/ipc_controld.h | 99 +++++++
lib/common/Makefile.am | 1 +
lib/common/crmcommon_private.h | 6 +
lib/common/ipc_client.c | 46 +--
lib/common/ipc_controld.c | 609 ++++++++++++++++++++++++++++++++++++++
7 files changed, 723 insertions(+), 44 deletions(-)
create mode 100644 include/crm/common/ipc_controld.h
create mode 100644 lib/common/ipc_controld.c
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
index 776e4a7..f29d105 100644
--- a/include/crm/common/Makefile.am
+++ b/include/crm/common/Makefile.am
@@ -12,7 +12,7 @@ MAINTAINERCLEANFILES = Makefile.in
headerdir=$(pkgincludedir)/crm/common
header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h \
- nvpair.h acl.h
+ nvpair.h acl.h ipc_controld.h
noinst_HEADERS = internal.h alerts_internal.h \
iso8601_internal.h remote_internal.h xml_internal.h \
ipc_internal.h output.h cmdline_internal.h curses_internal.h \
diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h
index 8dee1b1..c67aaea 100644
--- a/include/crm/common/ipc.h
+++ b/include/crm/common/ipc.h
@@ -217,7 +217,9 @@ unsigned int crm_ipc_default_buffer_size(void);
int crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid,
pid_t *gotpid, uid_t *gotuid, gid_t *gotgid);
-/* Utils */
+/* This is controller-specific but is declared in this header for C API
+ * backward compatibility.
+ */
xmlNode *create_hello_message(const char *uuid, const char *client_name,
const char *major_version, const char *minor_version);
diff --git a/include/crm/common/ipc_controld.h b/include/crm/common/ipc_controld.h
new file mode 100644
index 0000000..0ebabfc
--- /dev/null
+++ b/include/crm/common/ipc_controld.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2020 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__IPC_CONTROLD__H
+# define PCMK__IPC_CONTROLD__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief IPC commands for Pacemaker controller
+ *
+ * \ingroup core
+ */
+
+#include <stdbool.h> // bool
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/ipc.h> // pcmk_ipc_api_t
+
+//! Possible types of controller replies
+enum pcmk_controld_api_reply {
+ pcmk_controld_reply_unknown,
+ pcmk_controld_reply_reprobe,
+ pcmk_controld_reply_info,
+ pcmk_controld_reply_resource,
+ pcmk_controld_reply_ping,
+};
+
+/*!
+ * Controller reply passed to event callback
+ *
+ * \note Shutdown and election calls have no reply. Reprobe calls are
+ * acknowledged but contain no data (reply_type will be the only item
+ * set). Node info and ping calls have their own reply data. Fail and
+ * refresh calls use the resource reply type and reply data.
+ * \note The pointers in the reply are only guaranteed to be meaningful for the
+ * execution of the callback; if the values are needed for later, the
+ * callback should copy them.
+ */
+typedef struct {
+ enum pcmk_controld_api_reply reply_type;
+ const char *feature_set; //!< CRM feature set advertised by controller
+ const char *host_from; //!< Name of node that sent reply
+
+ union {
+ // pcmk_controld_reply_info
+ struct {
+ bool have_quorum;
+ bool is_remote;
+ int id;
+ const char *uuid;
+ const char *uname;
+ const char *state;
+ } node_info;
+
+ // pcmk_controld_reply_resource
+ struct {
+ xmlNode *node_state; //<! Resource operation history XML
+ } resource;
+
+ // pcmk_controld_reply_ping
+ struct {
+ const char *sys_from;
+ const char *fsa_state;
+ const char *result;
+ } ping;
+ } data;
+} pcmk_controld_api_reply_t;
+
+int pcmk_controld_api_reprobe(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node);
+int pcmk_controld_api_node_info(pcmk_ipc_api_t *api, uint32_t nodeid);
+int pcmk_controld_api_fail(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node, const char *rsc_id,
+ const char *rsc_long_id, const char *standard,
+ const char *provider, const char *type);
+int pcmk_controld_api_refresh(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node, const char *rsc_id,
+ const char *rsc_long_id, const char *standard,
+ const char *provider, const char *type,
+ bool cib_only);
+int pcmk_controld_api_ping(pcmk_ipc_api_t *api, const char *node_name);
+int pcmk_controld_api_shutdown(pcmk_ipc_api_t *api, const char *node_name);
+int pcmk_controld_api_start_election(pcmk_ipc_api_t *api);
+unsigned int pcmk_controld_api_replies_expected(pcmk_ipc_api_t *api);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__IPC_CONTROLD__H
diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am
index 29404a6..db66a6e 100644
--- a/lib/common/Makefile.am
+++ b/lib/common/Makefile.am
@@ -49,6 +49,7 @@ libcrmcommon_la_SOURCES += digest.c
libcrmcommon_la_SOURCES += io.c
libcrmcommon_la_SOURCES += ipc_client.c
libcrmcommon_la_SOURCES += ipc_common.c
+libcrmcommon_la_SOURCES += ipc_controld.c
libcrmcommon_la_SOURCES += ipc_server.c
libcrmcommon_la_SOURCES += iso8601.c
libcrmcommon_la_SOURCES += logging.c
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
index f9df27d..49dae6c 100644
--- a/lib/common/crmcommon_private.h
+++ b/lib/common/crmcommon_private.h
@@ -103,6 +103,9 @@ pcmk__xml_attr_value(const xmlAttr *attr)
#define PCMK__IPC_VERSION 1
+#define PCMK__CONTROLD_API_MAJOR "1"
+#define PCMK__CONTROLD_API_MINOR "0"
+
// IPC behavior that varies by daemon
typedef struct pcmk__ipc_methods_s {
/*!
@@ -203,4 +206,7 @@ unsigned int pcmk__ipc_buffer_size(unsigned int max);
G_GNUC_INTERNAL
bool pcmk__valid_ipc_header(const pcmk__ipc_header_t *header);
+G_GNUC_INTERNAL
+pcmk__ipc_methods_t *pcmk__controld_api_methods(void);
+
#endif // CRMCOMMON_PRIVATE__H
diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c
index 16dc9b5..4077d61 100644
--- a/lib/common/ipc_client.c
+++ b/lib/common/ipc_client.c
@@ -40,7 +40,8 @@
* \return Standard Pacemaker result code
*
* \note The caller is responsible for freeing *api using pcmk_free_ipc_api().
- * \note This is intended to supersede crm_ipc_new() but is not yet usable.
+ * \note This is intended to supersede crm_ipc_new() but currently only
+ * supports the controller IPC API.
*/
int
pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server)
@@ -73,6 +74,7 @@ pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server)
break;
case pcmk_ipc_controld:
+ (*api)->cmds = pcmk__controld_api_methods();
break;
case pcmk_ipc_execd:
@@ -247,7 +249,7 @@ pcmk_ipc_name(pcmk_ipc_api_t *api, bool for_log)
return for_log? "CIB manager" : NULL /* PCMK__SERVER_BASED_RW */;
case pcmk_ipc_controld:
- return for_log? "controller" : NULL /* CRM_SYSTEM_CRMD */;
+ return for_log? "controller" : CRM_SYSTEM_CRMD;
case pcmk_ipc_execd:
return for_log? "executor" : NULL /* CRM_SYSTEM_LRMD */;
@@ -1412,43 +1414,3 @@ bail:
}
return rc;
}
-
-xmlNode *
-create_hello_message(const char *uuid,
- const char *client_name, const char *major_version, const char *minor_version)
-{
- xmlNode *hello_node = NULL;
- xmlNode *hello = NULL;
-
- if (pcmk__str_empty(uuid) || pcmk__str_empty(client_name)
- || pcmk__str_empty(major_version) || pcmk__str_empty(minor_version)) {
- crm_err("Could not create IPC hello message from %s (UUID %s): "
- "missing information",
- client_name? client_name : "unknown client",
- uuid? uuid : "unknown");
- return NULL;
- }
-
- hello_node = create_xml_node(NULL, XML_TAG_OPTIONS);
- if (hello_node == NULL) {
- crm_err("Could not create IPC hello message from %s (UUID %s): "
- "Message data creation failed", client_name, uuid);
- return NULL;
- }
-
- crm_xml_add(hello_node, "major_version", major_version);
- crm_xml_add(hello_node, "minor_version", minor_version);
- crm_xml_add(hello_node, "client_name", client_name);
- crm_xml_add(hello_node, "client_uuid", uuid);
-
- hello = create_request(CRM_OP_HELLO, hello_node, NULL, NULL, client_name, uuid);
- if (hello == NULL) {
- crm_err("Could not create IPC hello message from %s (UUID %s): "
- "Request creation failed", client_name, uuid);
- return NULL;
- }
- free_xml(hello_node);
-
- crm_trace("Created hello message from %s (UUID %s)", client_name, uuid);
- return hello;
-}
diff --git a/lib/common/ipc_controld.c b/lib/common/ipc_controld.c
new file mode 100644
index 0000000..22bb733
--- /dev/null
+++ b/lib/common/ipc_controld.c
@@ -0,0 +1,609 @@
+/*
+ * Copyright 2020 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 <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <libxml/tree.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/ipc_controld.h>
+#include "crmcommon_private.h"
+
+struct controld_api_private_s {
+ char *client_uuid;
+ unsigned int replies_expected;
+};
+
+// \return Standard Pacemaker return code
+static int
+new_data(pcmk_ipc_api_t *api)
+{
+ struct controld_api_private_s *private = NULL;
+
+ api->api_data = calloc(1, sizeof(struct controld_api_private_s));
+
+ if (api->api_data == NULL) {
+ return errno;
+ }
+
+ private = api->api_data;
+
+ /* This is set to the PID because that's how it was always done, but PIDs
+ * are not unique because clients can be remote. The value appears to be
+ * unused other than as part of F_CRM_SYS_FROM in IPC requests, which is
+ * only compared against the internal system names (CRM_SYSTEM_TENGINE,
+ * etc.), so it shouldn't be a problem.
+ */
+ private->client_uuid = pcmk__getpid_s();
+
+ /* @TODO Implement a call ID model similar to the CIB, executor, and fencer
+ * IPC APIs, so that requests and replies can be matched, and
+ * duplicate replies can be discarded.
+ */
+ return pcmk_rc_ok;
+}
+
+static void
+free_data(void *data)
+{
+ free(((struct controld_api_private_s *) data)->client_uuid);
+ free(data);
+}
+
+// \return Standard Pacemaker return code
+static int
+post_connect(pcmk_ipc_api_t *api)
+{
+ /* The controller currently requires clients to register via a hello
+ * request, but does not reply back.
+ */
+ struct controld_api_private_s *private = api->api_data;
+ const char *client_name = crm_system_name? crm_system_name : "client";
+ xmlNode *hello;
+ int rc;
+
+ hello = create_hello_message(private->client_uuid, client_name,
+ PCMK__CONTROLD_API_MAJOR,
+ PCMK__CONTROLD_API_MINOR);
+ rc = pcmk__send_ipc_request(api, hello);
+ free_xml(hello);
+ if (rc != pcmk_rc_ok) {
+ crm_info("Could not send IPC hello to %s: %s " CRM_XS " rc=%s",
+ pcmk_ipc_name(api, true), pcmk_rc_str(rc), rc);
+ } else {
+ crm_debug("Sent IPC hello to %s", pcmk_ipc_name(api, true));
+ }
+ return rc;
+}
+
+#define xml_true(xml, field) crm_is_true(crm_element_value(xml, field))
+
+static void
+set_node_info_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data)
+{
+ data->reply_type = pcmk_controld_reply_info;
+ if (msg_data == NULL) {
+ return;
+ }
+ data->data.node_info.have_quorum = xml_true(msg_data, XML_ATTR_HAVE_QUORUM);
+ data->data.node_info.is_remote = xml_true(msg_data, XML_NODE_IS_REMOTE);
+ crm_element_value_int(msg_data, XML_ATTR_ID, &(data->data.node_info.id));
+ data->data.node_info.uuid = crm_element_value(msg_data, XML_ATTR_UUID);
+ data->data.node_info.uname = crm_element_value(msg_data, XML_ATTR_UNAME);
+ data->data.node_info.state = crm_element_value(msg_data, XML_NODE_IS_PEER);
+}
+
+static void
+set_ping_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data)
+{
+ data->reply_type = pcmk_controld_reply_ping;
+ if (msg_data == NULL) {
+ return;
+ }
+ data->data.ping.sys_from = crm_element_value(msg_data,
+ XML_PING_ATTR_SYSFROM);
+ data->data.ping.fsa_state = crm_element_value(msg_data,
+ XML_PING_ATTR_CRMDSTATE);
+ data->data.ping.result = crm_element_value(msg_data, XML_PING_ATTR_STATUS);
+}
+
+static bool
+reply_expected(pcmk_ipc_api_t *api, xmlNode *request)
+{
+ const char *command = crm_element_value(request, F_CRM_TASK);
+
+ if (command == NULL) {
+ return false;
+ }
+
+ // We only need to handle commands that functions in this file can send
+ return !strcmp(command, CRM_OP_REPROBE)
+ || !strcmp(command, CRM_OP_NODE_INFO)
+ || !strcmp(command, CRM_OP_PING)
+ || !strcmp(command, CRM_OP_LRM_FAIL)
+ || !strcmp(command, CRM_OP_LRM_DELETE);
+}
+
+static void
+dispatch(pcmk_ipc_api_t *api, xmlNode *reply)
+{
+ struct controld_api_private_s *private = api->api_data;
+ crm_exit_t status = CRM_EX_OK;
+ xmlNode *msg_data = NULL;
+ const char *value = NULL;
+ pcmk_controld_api_reply_t reply_data = {
+ pcmk_controld_reply_unknown, NULL, NULL,
+ };
+
+ if (private->replies_expected > 0) {
+ private->replies_expected--;
+ }
+
+ // Do some basic validation of the reply
+
+ /* @TODO We should be able to verify that value is always a response, but
+ * currently the controller doesn't always properly set the type. Even
+ * if we fix the controller, we'll still need to handle replies from
+ * old versions (feature set could be used to differentiate).
+ */
+ value = crm_element_value(reply, F_CRM_MSG_TYPE);
+ if ((value == NULL) || (strcmp(value, XML_ATTR_REQUEST)
+ && strcmp(value, XML_ATTR_RESPONSE))) {
+ crm_debug("Unrecognizable controller message: invalid message type '%s'",
+ crm_str(value));
+ status = CRM_EX_PROTOCOL;
+ reply = NULL;
+ }
+
+ if (crm_element_value(reply, XML_ATTR_REFERENCE) == NULL) {
+ crm_debug("Unrecognizable controller message: no reference");
+ status = CRM_EX_PROTOCOL;
+ reply = NULL;
+ }
+
+ value = crm_element_value(reply, F_CRM_TASK);
+ if (value == NULL) {
+ crm_debug("Unrecognizable controller message: no command name");
+ status = CRM_EX_PROTOCOL;
+ reply = NULL;
+ }
+
+ // Parse useful info from reply
+
+ if (reply != NULL) {
+ reply_data.feature_set = crm_element_value(reply, XML_ATTR_VERSION);
+ reply_data.host_from = crm_element_value(reply, F_CRM_HOST_FROM);
+ msg_data = get_message_xml(reply, F_CRM_DATA);
+
+ if (!strcmp(value, CRM_OP_REPROBE)) {
+ reply_data.reply_type = pcmk_controld_reply_reprobe;
+
+ } else if (!strcmp(value, CRM_OP_NODE_INFO)) {
+ set_node_info_data(&reply_data, msg_data);
+
+ } else if (!strcmp(value, CRM_OP_INVOKE_LRM)) {
+ reply_data.reply_type = pcmk_controld_reply_resource;
+ reply_data.data.resource.node_state = msg_data;
+
+ } else if (!strcmp(value, CRM_OP_PING)) {
+ set_ping_data(&reply_data, msg_data);
+
+ } else {
+ crm_debug("Unrecognizable controller message: unknown command '%s'",
+ value);
+ status = CRM_EX_PROTOCOL;
+ reply = NULL;
+ }
+ }
+
+ pcmk__call_ipc_callback(api, pcmk_ipc_event_reply, status, &reply_data);
+}
+
+pcmk__ipc_methods_t *
+pcmk__controld_api_methods()
+{
+ pcmk__ipc_methods_t *cmds = calloc(1, sizeof(pcmk__ipc_methods_t));
+
+ if (cmds != NULL) {
+ cmds->new_data = new_data;
+ cmds->free_data = free_data;
+ cmds->post_connect = post_connect;
+ cmds->reply_expected = reply_expected;
+ cmds->dispatch = dispatch;
+ }
+ return cmds;
+}
+
+/*!
+ * \internal
+ * \brief Create XML for a controller IPC request
+ *
+ * \param[in] api Controller connection
+ * \param[in] op Controller IPC command name
+ * \param[in] node Node name to set as destination host
+ * \param[in] msg_data XML to attach to request as message data
+ *
+ * \return Newly allocated XML for request
+ */
+static xmlNode *
+create_controller_request(pcmk_ipc_api_t *api, const char *op,
+ const char *node, xmlNode *msg_data)
+{
+ struct controld_api_private_s *private = api->api_data;
+ const char *sys_to = NULL;
+
+ if ((node == NULL) && !strcmp(op, CRM_OP_PING)) {
+ sys_to = CRM_SYSTEM_DC;
+ } else {
+ sys_to = CRM_SYSTEM_CRMD;
+ }
+ return create_request(op, msg_data, node, sys_to,
+ (crm_system_name? crm_system_name : "client"),
+ private->client_uuid);
+}
+
+// \return Standard Pacemaker return code
+static int
+send_controller_request(pcmk_ipc_api_t *api, xmlNode *request,
+ bool reply_is_expected)
+{
+ int rc;
+
+ if (crm_element_value(request, XML_ATTR_REFERENCE) == NULL) {
+ return EINVAL;
+ }
+ rc = pcmk__send_ipc_request(api, request);
+ if ((rc == pcmk_rc_ok) && reply_is_expected) {
+ struct controld_api_private_s *private = api->api_data;
+
+ private->replies_expected++;
+ }
+ return rc;
+}
+
+static xmlNode *
+create_reprobe_message_data(const char *target_node, const char *router_node)
+{
+ xmlNode *msg_data;
+
+ msg_data = create_xml_node(NULL, "data_for_" CRM_OP_REPROBE);
+ crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, target_node);
+ if ((router_node != NULL) && safe_str_neq(router_node, target_node)) {
+ crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
+ }
+ return msg_data;
+}
+
+/*!
+ * \brief Send a reprobe controller operation
+ *
+ * \param[in] api Controller connection
+ * \param[in] target_node Name of node to reprobe
+ * \param[in] router_node Router node for host
+ *
+ * \return Standard Pacemaker return code
+ * \note Event callback will get a reply of type pcmk_controld_reply_reprobe.
+ */
+int
+pcmk_controld_api_reprobe(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node)
+{
+ xmlNode *request;
+ xmlNode *msg_data;
+ int rc = pcmk_rc_ok;
+
+ if (api == NULL) {
+ return EINVAL;
+ }
+ if (router_node == NULL) {
+ router_node = target_node;
+ }
+ crm_debug("Sending %s IPC request to reprobe %s via %s",
+ pcmk_ipc_name(api, true), crm_str(target_node),
+ crm_str(router_node));
+ msg_data = create_reprobe_message_data(target_node, router_node);
+ request = create_controller_request(api, CRM_OP_REPROBE, router_node,
+ msg_data);
+ rc = send_controller_request(api, request, true);
+ free_xml(msg_data);
+ free_xml(request);
+ return rc;
+}
+
+/*!
+ * \brief Send a "node info" controller operation
+ *
+ * \param[in] api Controller connection
+ * \param[in] nodeid ID of node to get info for (or 0 for local node)
+ *
+ * \return Standard Pacemaker return code
+ * \note Event callback will get a reply of type pcmk_controld_reply_info.
+ */
+int
+pcmk_controld_api_node_info(pcmk_ipc_api_t *api, uint32_t nodeid)
+{
+ xmlNode *request;
+ int rc = pcmk_rc_ok;
+
+ request = create_controller_request(api, CRM_OP_NODE_INFO, NULL, NULL);
+ if (request == NULL) {
+ return EINVAL;
+ }
+ if (nodeid > 0) {
+ crm_xml_set_id(request, "%lu", (unsigned long) nodeid);
+ }
+
+ rc = send_controller_request(api, request, true);
+ free_xml(request);
+ return rc;
+}
+
+/*!
+ * \brief Ask the controller for status
+ *
+ * \param[in] api Controller connection
+ * \param[in] node_name Name of node whose status is desired (or NULL for DC)
+ *
+ * \return Standard Pacemaker return code
+ * \note Event callback will get a reply of type pcmk_controld_reply_ping.
+ */
+int
+pcmk_controld_api_ping(pcmk_ipc_api_t *api, const char *node_name)
+{
+ xmlNode *request;
+ int rc = pcmk_rc_ok;
+
+ request = create_controller_request(api, CRM_OP_PING, node_name, NULL);
+ if (request == NULL) {
+ return EINVAL;
+ }
+ rc = send_controller_request(api, request, true);
+ free_xml(request);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Ask the controller to shut down
+ *
+ * \param[in] api Controller connection
+ * \param[in] node_name Name of node whose controller should shut down
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This capability currently does not work, so the function is considered
+ * internal. It will likely be removed.
+ * \note Event callback will not get a reply.
+ */
+int
+pcmk_controld_api_shutdown(pcmk_ipc_api_t *api, const char *node_name)
+{
+ xmlNode *request;
+ int rc = pcmk_rc_ok;
+
+ request = create_controller_request(api, CRM_OP_SHUTDOWN, NULL, NULL);
+ if (request == NULL) {
+ return EINVAL;
+ }
+ rc = send_controller_request(api, request, false);
+ free_xml(request);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Ask the controller to start a DC election
+ *
+ * \param[in] api Controller connection
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This capability currently does not work, so the function is considered
+ * internal. It will likely be removed.
+ * \note Event callback will not get a reply.
+ */
+int
+pcmk_controld_api_start_election(pcmk_ipc_api_t *api)
+{
+ xmlNode *request;
+ int rc = pcmk_rc_ok;
+
+ request = create_controller_request(api, CRM_OP_VOTE, NULL, NULL);
+ if (request == NULL) {
+ return EINVAL;
+ }
+ rc = send_controller_request(api, request, false);
+ free_xml(request);
+ return rc;
+}
+
+// \return Standard Pacemaker return code
+static int
+controller_resource_op(pcmk_ipc_api_t *api, const char *op,
+ const char *target_node, const char *router_node,
+ bool cib_only, const char *rsc_id,
+ const char *rsc_long_id, const char *standard,
+ const char *provider, const char *type)
+{
+ int rc = pcmk_rc_ok;
+ char *key;
+ xmlNode *request, *msg_data, *xml_rsc, *params;
+
+ if (api == NULL) {
+ return EINVAL;
+ }
+ if (router_node == NULL) {
+ router_node = target_node;
+ }
+
+ msg_data = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
+
+ /* The controller logs the transition key from resource op requests, so we
+ * need to have *something* for it.
+ * @TODO don't use "crm-resource"
+ */
+ key = pcmk__transition_key(0, getpid(), 0,
+ "xxxxxxxx-xrsc-opxx-xcrm-resourcexxxx");
+ crm_xml_add(msg_data, XML_ATTR_TRANSITION_KEY, key);
+ free(key);
+
+ crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, target_node);
+ if (safe_str_neq(router_node, target_node)) {
+ crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
+ }
+
+ if (cib_only) {
+ // Indicate that only the CIB needs to be cleaned
+ crm_xml_add(msg_data, PCMK__XA_MODE, XML_TAG_CIB);
+ }
+
+ xml_rsc = create_xml_node(msg_data, XML_CIB_TAG_RESOURCE);
+ crm_xml_add(xml_rsc, XML_ATTR_ID, rsc_id);
+ crm_xml_add(xml_rsc, XML_ATTR_ID_LONG, rsc_long_id);
+ crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, standard);
+ crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER, provider);
+ crm_xml_add(xml_rsc, XML_ATTR_TYPE, type);
+
+ params = create_xml_node(msg_data, XML_TAG_ATTRS);
+ crm_xml_add(params, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
+
+ // The controller parses the timeout from the request
+ key = crm_meta_name(XML_ATTR_TIMEOUT);
+ crm_xml_add(params, key, "60000"); /* 1 minute */ //@TODO pass as arg
+ free(key);
+
+ request = create_controller_request(api, op, router_node, msg_data);
+ rc = send_controller_request(api, request, true);
+ free_xml(msg_data);
+ free_xml(request);
+ return rc;
+}
+
+/*!
+ * \brief Ask the controller to fail a resource
+ *
+ * \param[in] api Controller connection
+ * \param[in] target_node Name of node resource is on
+ * \param[in] router_node Router node for target
+ * \param[in] rsc_id ID of resource to fail
+ * \param[in] rsc_long_id Long ID of resource (if any)
+ * \param[in] standard Standard of resource
+ * \param[in] provider Provider of resource (if any)
+ * \param[in] type Type of resource to fail
+ *
+ * \return Standard Pacemaker return code
+ * \note Event callback will get a reply of type pcmk_controld_reply_resource.
+ */
+int
+pcmk_controld_api_fail(pcmk_ipc_api_t *api,
+ const char *target_node, const char *router_node,
+ const char *rsc_id, const char *rsc_long_id,
+ const char *standard, const char *provider,
+ const char *type)
+{
+ crm_debug("Sending %s IPC request to fail %s (a.k.a. %s) on %s via %s",
+ pcmk_ipc_name(api, true), crm_str(rsc_id), crm_str(rsc_long_id),
+ crm_str(target_node), crm_str(router_node));
+ return controller_resource_op(api, CRM_OP_LRM_FAIL, target_node,
+ router_node, false, rsc_id, rsc_long_id,
+ standard, provider, type);
+}
+
+/*!
+ * \brief Ask the controller to refresh a resource
+ *
+ * \param[in] api Controller connection
+ * \param[in] target_node Name of node resource is on
+ * \param[in] router_node Router node for target
+ * \param[in] rsc_id ID of resource to refresh
+ * \param[in] rsc_long_id Long ID of resource (if any)
+ * \param[in] standard Standard of resource
+ * \param[in] provider Provider of resource (if any)
+ * \param[in] type Type of resource
+ * \param[in] cib_only If true, clean resource from CIB only
+ *
+ * \return Standard Pacemaker return code
+ * \note Event callback will get a reply of type pcmk_controld_reply_resource.
+ */
+int
+pcmk_controld_api_refresh(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node,
+ const char *rsc_id, const char *rsc_long_id,
+ const char *standard, const char *provider,
+ const char *type, bool cib_only)
+{
+ crm_debug("Sending %s IPC request to refresh %s (a.k.a. %s) on %s via %s",
+ pcmk_ipc_name(api, true), crm_str(rsc_id), crm_str(rsc_long_id),
+ crm_str(target_node), crm_str(router_node));
+ return controller_resource_op(api, CRM_OP_LRM_DELETE, target_node,
+ router_node, cib_only, rsc_id, rsc_long_id,
+ standard, provider, type);
+}
+
+/*!
+ * \brief Get the number of IPC replies currently expected from the controller
+ *
+ * \param[in] api Controller IPC API connection
+ *
+ * \return Number of replies expected
+ */
+unsigned int
+pcmk_controld_api_replies_expected(pcmk_ipc_api_t *api)
+{
+ struct controld_api_private_s *private = api->api_data;
+
+ return private->replies_expected;
+}
+
+xmlNode *
+create_hello_message(const char *uuid, const char *client_name,
+ const char *major_version, const char *minor_version)
+{
+ xmlNode *hello_node = NULL;
+ xmlNode *hello = NULL;
+
+ if (pcmk__str_empty(uuid) || pcmk__str_empty(client_name)
+ || pcmk__str_empty(major_version) || pcmk__str_empty(minor_version)) {
+ crm_err("Could not create IPC hello message from %s (UUID %s): "
+ "missing information",
+ client_name? client_name : "unknown client",
+ uuid? uuid : "unknown");
+ return NULL;
+ }
+
+ hello_node = create_xml_node(NULL, XML_TAG_OPTIONS);
+ if (hello_node == NULL) {
+ crm_err("Could not create IPC hello message from %s (UUID %s): "
+ "Message data creation failed", client_name, uuid);
+ return NULL;
+ }
+
+ crm_xml_add(hello_node, "major_version", major_version);
+ crm_xml_add(hello_node, "minor_version", minor_version);
+ crm_xml_add(hello_node, "client_name", client_name);
+ crm_xml_add(hello_node, "client_uuid", uuid);
+
+ hello = create_request(CRM_OP_HELLO, hello_node, NULL, NULL, client_name, uuid);
+ if (hello == NULL) {
+ crm_err("Could not create IPC hello message from %s (UUID %s): "
+ "Request creation failed", client_name, uuid);
+ return NULL;
+ }
+ free_xml(hello_node);
+
+ crm_trace("Created hello message from %s (UUID %s)", client_name, uuid);
+ return hello;
+}
--
1.8.3.1
From 1d1b34664b64f6805aeaf4d8e29e77aa8f59b4fc Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Tue, 7 Apr 2020 15:43:01 -0500
Subject: [PATCH 3/6] Refactor: tools: convert crm_resource to use new
controller IPC model
---
tools/Makefile.am | 3 +-
tools/crm_resource.c | 138 +++++++------
tools/crm_resource.h | 9 +-
tools/crm_resource_controller.c | 425 ----------------------------------------
tools/crm_resource_controller.h | 198 -------------------
tools/crm_resource_runtime.c | 30 +--
6 files changed, 96 insertions(+), 707 deletions(-)
delete mode 100644 tools/crm_resource_controller.c
delete mode 100644 tools/crm_resource_controller.h
diff --git a/tools/Makefile.am b/tools/Makefile.am
index c822a8c..4609b0f 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -12,7 +12,7 @@ if BUILD_SYSTEMD
systemdsystemunit_DATA = crm_mon.service
endif
-noinst_HEADERS = crm_mon.h crm_resource.h crm_resource_controller.h
+noinst_HEADERS = crm_mon.h crm_resource.h
pcmkdir = $(datadir)/$(PACKAGE)
pcmk_DATA = report.common report.collector
@@ -115,7 +115,6 @@ crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \
crm_resource_SOURCES = crm_resource.c \
crm_resource_ban.c \
- crm_resource_controller.c \
crm_resource_print.c \
crm_resource_runtime.c
crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \
diff --git a/tools/crm_resource.c b/tools/crm_resource.c
index 6853ad5..c8c1cfa 100644
--- a/tools/crm_resource.c
+++ b/tools/crm_resource.c
@@ -11,62 +11,70 @@
#include <pacemaker-internal.h>
#include <sys/param.h>
-
-#include <crm/crm.h>
-#include <crm/stonith-ng.h>
-
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
-
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <time.h>
+#include <crm/crm.h>
+#include <crm/stonith-ng.h>
+#include <crm/common/ipc_controld.h>
+
bool BE_QUIET = FALSE;
bool scope_master = FALSE;
int cib_options = cib_sync_call;
-
-static GMainLoop *mainloop = NULL;
+static crm_exit_t exit_code = CRM_EX_OK;
// Things that should be cleaned up on exit
+static GMainLoop *mainloop = NULL;
static cib_t *cib_conn = NULL;
-static pcmk_controld_api_t *controld_api = NULL;
+static pcmk_ipc_api_t *controld_api = NULL;
static pe_working_set_t *data_set = NULL;
#define MESSAGE_TIMEOUT_S 60
// Clean up and exit
static crm_exit_t
-bye(crm_exit_t exit_code)
+bye(crm_exit_t ec)
{
- static bool crm_resource_shutdown_flag = false;
-
- if (crm_resource_shutdown_flag) {
- // Allow usage like "return bye(...);"
- return exit_code;
- }
- crm_resource_shutdown_flag = true;
-
if (cib_conn != NULL) {
cib_t *save_cib_conn = cib_conn;
- cib_conn = NULL;
+ cib_conn = NULL; // Ensure we can't free this twice
save_cib_conn->cmds->signoff(save_cib_conn);
cib_delete(save_cib_conn);
}
if (controld_api != NULL) {
- pcmk_controld_api_t *save_controld_api = controld_api;
+ pcmk_ipc_api_t *save_controld_api = controld_api;
- controld_api = NULL;
- pcmk_free_controld_api(save_controld_api);
+ controld_api = NULL; // Ensure we can't free this twice
+ pcmk_free_ipc_api(save_controld_api);
+ }
+ if (mainloop != NULL) {
+ g_main_loop_unref(mainloop);
+ mainloop = NULL;
}
pe_free_working_set(data_set);
data_set = NULL;
- crm_exit(exit_code);
- return exit_code;
+ crm_exit(ec);
+ return ec;
+}
+
+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 gboolean
@@ -76,39 +84,54 @@ resource_ipc_timeout(gpointer data)
fprintf(stderr, "\nAborting because no messages received in %d seconds\n",
MESSAGE_TIMEOUT_S);
crm_err("No messages received in %d seconds", MESSAGE_TIMEOUT_S);
- bye(CRM_EX_TIMEOUT);
+ quit_main_loop(CRM_EX_TIMEOUT);
return FALSE;
}
static void
-handle_controller_reply(pcmk_controld_api_t *capi, void *api_data,
- void *user_data)
+controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type,
+ crm_exit_t status, void *event_data, void *user_data)
{
- fprintf(stderr, ".");
- if ((capi->replies_expected(capi) == 0)
- && mainloop && g_main_loop_is_running(mainloop)) {
- fprintf(stderr, " OK\n");
- crm_debug("Got all the replies we expected");
- bye(CRM_EX_OK);
- }
-}
+ switch (event_type) {
+ case pcmk_ipc_event_disconnect:
+ if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
+ crm_info("Connection to controller was terminated");
+ }
+ quit_main_loop(exit_code);
+ break;
-static void
-handle_controller_drop(pcmk_controld_api_t *capi, void *api_data,
- void *user_data)
-{
- crm_info("Connection to controller was terminated");
- bye(CRM_EX_DISCONNECT);
+ case pcmk_ipc_event_reply:
+ if (status != CRM_EX_OK) {
+ fprintf(stderr, "\nError: bad reply from controller: %s\n",
+ crm_exit_str(status));
+ pcmk_disconnect_ipc(api);
+ quit_main_loop(status);
+ } else {
+ fprintf(stderr, ".");
+ if ((pcmk_controld_api_replies_expected(api) == 0)
+ && mainloop && g_main_loop_is_running(mainloop)) {
+ fprintf(stderr, " OK\n");
+ crm_debug("Got all the replies we expected");
+ pcmk_disconnect_ipc(api);
+ quit_main_loop(CRM_EX_OK);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
}
static void
-start_mainloop(pcmk_controld_api_t *capi)
+start_mainloop(pcmk_ipc_api_t *capi)
{
- if (capi->replies_expected(capi) > 0) {
- unsigned int count = capi->replies_expected(capi);
+ unsigned int count = pcmk_controld_api_replies_expected(capi);
+ if (count > 0) {
fprintf(stderr, "Waiting for %d %s from the controller",
count, pcmk__plural_alt(count, "reply", "replies"));
+ exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
mainloop = g_main_loop_new(NULL, FALSE);
g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL);
g_main_loop_run(mainloop);
@@ -664,7 +687,6 @@ main(int argc, char **argv)
int argerr = 0;
int flag;
int find_flags = 0; // Flags to use when searching for resource
- crm_exit_t exit_code = CRM_EX_OK;
crm_log_cli_init("crm_resource");
pcmk__set_cli_options(NULL, "<query>|<command> [options]", long_options,
@@ -1151,21 +1173,15 @@ main(int argc, char **argv)
// Establish a connection to the controller if needed
if (require_crmd) {
- char *client_uuid;
- pcmk_controld_api_cb_t dispatch_cb = {
- handle_controller_reply, NULL
- };
- pcmk_controld_api_cb_t destroy_cb = {
- handle_controller_drop, NULL
- };
-
-
- client_uuid = pcmk__getpid_s();
- controld_api = pcmk_new_controld_api(crm_system_name, client_uuid);
- free(client_uuid);
-
- rc = controld_api->connect(controld_api, true, &dispatch_cb,
- &destroy_cb);
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ CMD_ERR("Error connecting to the controller: %s", pcmk_rc_str(rc));
+ rc = pcmk_rc2legacy(rc);
+ goto bail;
+ }
+ pcmk_register_ipc_callback(controld_api, controller_event_callback,
+ NULL);
+ rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
if (rc != pcmk_rc_ok) {
CMD_ERR("Error connecting to the controller: %s", pcmk_rc_str(rc));
rc = pcmk_rc2legacy(rc);
@@ -1525,8 +1541,8 @@ main(int argc, char **argv)
NULL, NULL, NULL,
NULL, attr_options));
- if (controld_api->reprobe(controld_api, host_uname,
- router_node) == pcmk_rc_ok) {
+ if (pcmk_controld_api_reprobe(controld_api, host_uname,
+ router_node) == pcmk_rc_ok) {
start_mainloop(controld_api);
}
diff --git a/tools/crm_resource.h b/tools/crm_resource.h
index 0bf7bee..cb7f506 100644
--- a/tools/crm_resource.h
+++ b/tools/crm_resource.h
@@ -21,7 +21,6 @@
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
-#include "crm_resource_controller.h"
extern bool print_pending;
@@ -36,8 +35,6 @@ extern char *move_lifetime;
extern const char *attr_set_type;
-extern pcmk_controld_api_cb_t controld_api_cb;
-
/* ban */
int cli_resource_prefer(const char *rsc_id, const char *host, cib_t * cib_conn);
int cli_resource_ban(const char *rsc_id, const char *host, GListPtr allnodes, cib_t * cib_conn);
@@ -63,16 +60,16 @@ int cli_resource_print_operations(const char *rsc_id, const char *host_uname, bo
/* runtime */
void cli_resource_check(cib_t * cib, pe_resource_t *rsc);
-int cli_resource_fail(pcmk_controld_api_t *controld_api,
+int cli_resource_fail(pcmk_ipc_api_t *controld_api,
const char *host_uname, const char *rsc_id,
pe_working_set_t *data_set);
int cli_resource_search(pe_resource_t *rsc, const char *requested_name,
pe_working_set_t *data_set);
-int cli_resource_delete(pcmk_controld_api_t *controld_api,
+int cli_resource_delete(pcmk_ipc_api_t *controld_api,
const char *host_uname, pe_resource_t *rsc,
const char *operation, const char *interval_spec,
bool just_failures, pe_working_set_t *data_set);
-int cli_cleanup_all(pcmk_controld_api_t *controld_api, const char *node_name,
+int cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name,
const char *operation, const char *interval_spec,
pe_working_set_t *data_set);
int cli_resource_restart(pe_resource_t *rsc, const char *host, int timeout_ms,
diff --git a/tools/crm_resource_controller.c b/tools/crm_resource_controller.c
deleted file mode 100644
index 994a7be..0000000
--- a/tools/crm_resource_controller.c
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright 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 <crm_internal.h>
-#include <stdio.h>
-#include <errno.h>
-#include "crm_resource.h"
-
-// API object's private members
-struct controller_private {
- char *client_name; // Client name to use with IPC
- char *client_uuid; // Client UUID to use with IPC
- mainloop_io_t *source; // If main loop used, I/O source for IPC
- crm_ipc_t *ipc; // IPC connection to controller
- int replies_expected; // How many controller replies are expected
- pcmk_controld_api_cb_t dispatch_cb; // Caller's registered dispatch callback
- pcmk_controld_api_cb_t destroy_cb; // Caller's registered destroy callback
-};
-
-static void
-call_client_callback(pcmk_controld_api_t *api, pcmk_controld_api_cb_t *cb,
- void *api_data)
-{
- if ((cb != NULL) && (cb->callback != NULL)) {
- cb->callback(api, api_data, cb->user_data);
- }
-}
-
-/*
- * IPC callbacks when used with main loop
- */
-
-static void
-controller_ipc_destroy(gpointer user_data)
-{
- pcmk_controld_api_t *api = user_data;
- struct controller_private *private = api->private;
-
- private->ipc = NULL;
- private->source = NULL;
- call_client_callback(api, &(private->destroy_cb), NULL);
-}
-
-// \return < 0 if connection is no longer required, >= 0 if it is
-static int
-controller_ipc_dispatch(const char *buffer, ssize_t length, gpointer user_data)
-{
- xmlNode *msg = NULL;
- pcmk_controld_api_t *api = user_data;
-
- CRM_CHECK(buffer && api && api->private, return 0);
-
- msg = string2xml(buffer);
- if (msg == NULL) {
- crm_warn("Received malformed controller IPC message");
- } else {
- struct controller_private *private = api->private;
-
- crm_log_xml_trace(msg, "controller-reply");
- private->replies_expected--;
- call_client_callback(api, &(private->dispatch_cb),
- get_message_xml(msg, F_CRM_DATA));
- free_xml(msg);
- }
- return 0;
-}
-
-/*
- * IPC utilities
- */
-
-// \return Standard Pacemaker return code
-static int
-send_hello(crm_ipc_t *ipc, const char *client_name, const char *client_uuid)
-{
- xmlNode *hello = create_hello_message(client_uuid, client_name, "0", "1");
- int rc = crm_ipc_send(ipc, hello, 0, 0, NULL);
-
- free_xml(hello);
- if (rc < 0) {
- rc = pcmk_legacy2rc(rc);
- crm_info("Could not send IPC hello to %s: %s " CRM_XS " rc=%s",
- CRM_SYSTEM_CRMD /* ipc->name */,
- pcmk_rc_str(rc), rc);
- return rc;
- }
- crm_debug("Sent IPC hello to %s", CRM_SYSTEM_CRMD /* ipc->name */);
- return pcmk_rc_ok;
-}
-
-// \return Standard Pacemaker return code
-static int
-send_controller_request(pcmk_controld_api_t *api, const char *op,
- xmlNode *msg_data, const char *node)
-{
- int rc;
- struct controller_private *private = api->private;
- xmlNode *cmd = create_request(op, msg_data, node, CRM_SYSTEM_CRMD,
- private->client_name, private->client_uuid);
- const char *reference = crm_element_value(cmd, XML_ATTR_REFERENCE);
-
- if ((cmd == NULL) || (reference == NULL)) {
- return EINVAL;
- }
-
- //@TODO pass as args? 0=crm_ipc_flags, 0=timeout_ms (default 5s), NULL=reply
- crm_log_xml_trace(cmd, "controller-request");
- rc = crm_ipc_send(private->ipc, cmd, 0, 0, NULL);
- free_xml(cmd);
- if (rc < 0) {
- return pcmk_legacy2rc(rc);
- }
- private->replies_expected++;
- return pcmk_rc_ok;
-}
-
-/*
- * pcmk_controld_api_t methods
- */
-
-static int
-controller_connect_mainloop(pcmk_controld_api_t *api)
-{
- struct controller_private *private = api->private;
- struct ipc_client_callbacks callbacks = {
- .dispatch = controller_ipc_dispatch,
- .destroy = controller_ipc_destroy,
- };
-
- private->source = mainloop_add_ipc_client(CRM_SYSTEM_CRMD,
- G_PRIORITY_DEFAULT, 0, api,
- &callbacks);
- if (private->source == NULL) {
- return ENOTCONN;
- }
-
- private->ipc = mainloop_get_ipc_client(private->source);
- if (private->ipc == NULL) {
- (void) api->disconnect(api);
- return ENOTCONN;
- }
-
- crm_debug("Connected to %s IPC (attaching to main loop)", CRM_SYSTEM_CRMD);
- return pcmk_rc_ok;
-}
-
-static int
-controller_connect_no_mainloop(pcmk_controld_api_t *api)
-{
- struct controller_private *private = api->private;
-
- private->ipc = crm_ipc_new(CRM_SYSTEM_CRMD, 0);
- if (private->ipc == NULL) {
- return ENOTCONN;
- }
- if (!crm_ipc_connect(private->ipc)) {
- crm_ipc_close(private->ipc);
- crm_ipc_destroy(private->ipc);
- private->ipc = NULL;
- return errno;
- }
- /* @TODO caller needs crm_ipc_get_fd(private->ipc); either add method for
- * that, or replace use_mainloop with int *fd
- */
- crm_debug("Connected to %s IPC", CRM_SYSTEM_CRMD);
- return pcmk_rc_ok;
-}
-
-static void
-set_callback(pcmk_controld_api_cb_t *dest, pcmk_controld_api_cb_t *source)
-{
- if (source) {
- dest->callback = source->callback;
- dest->user_data = source->user_data;
- }
-}
-
-static int
-controller_api_connect(pcmk_controld_api_t *api, bool use_mainloop,
- pcmk_controld_api_cb_t *dispatch_cb,
- pcmk_controld_api_cb_t *destroy_cb)
-{
- int rc = pcmk_rc_ok;
- struct controller_private *private;
-
- if (api == NULL) {
- return EINVAL;
- }
- private = api->private;
-
- set_callback(&(private->dispatch_cb), dispatch_cb);
- set_callback(&(private->destroy_cb), destroy_cb);
-
- if (private->ipc != NULL) {
- return pcmk_rc_ok; // already connected
- }
-
- if (use_mainloop) {
- rc = controller_connect_mainloop(api);
- } else {
- rc = controller_connect_no_mainloop(api);
- }
- if (rc != pcmk_rc_ok) {
- return rc;
- }
-
- rc = send_hello(private->ipc, private->client_name, private->client_uuid);
- if (rc != pcmk_rc_ok) {
- (void) api->disconnect(api);
- }
- return rc;
-}
-
-static int
-controller_api_disconnect(pcmk_controld_api_t *api)
-{
- struct controller_private *private = api->private;
-
- if (private->source != NULL) {
- // Attached to main loop
- mainloop_del_ipc_client(private->source);
- private->source = NULL;
- private->ipc = NULL;
-
- } else if (private->ipc != NULL) {
- // Not attached to main loop
- crm_ipc_t *ipc = private->ipc;
-
- private->ipc = NULL;
- crm_ipc_close(ipc);
- crm_ipc_destroy(ipc);
- }
- crm_debug("Disconnected from %s IPC", CRM_SYSTEM_CRMD /* ipc->name */);
- return pcmk_rc_ok;
-}
-
-//@TODO dispatch function for non-mainloop a la stonith_dispatch()
-//@TODO convenience retry-connect function a la stonith_api_connect_retry()
-
-static unsigned int
-controller_api_replies_expected(pcmk_controld_api_t *api)
-{
- if (api != NULL) {
- struct controller_private *private = api->private;
-
- return private->replies_expected;
- }
- return 0;
-}
-
-static xmlNode *
-create_reprobe_message_data(const char *target_node, const char *router_node)
-{
- xmlNode *msg_data;
-
- msg_data = create_xml_node(NULL, "data_for_" CRM_OP_REPROBE);
- crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, target_node);
- if ((router_node != NULL) && safe_str_neq(router_node, target_node)) {
- crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
- }
- return msg_data;
-}
-
-static int
-controller_api_reprobe(pcmk_controld_api_t *api, const char *target_node,
- const char *router_node)
-{
- int rc = EINVAL;
-
- if (api != NULL) {
- xmlNode *msg_data;
-
- crm_debug("Sending %s IPC request to reprobe %s via %s",
- CRM_SYSTEM_CRMD, crm_str(target_node), crm_str(router_node));
- msg_data = create_reprobe_message_data(target_node, router_node);
- rc = send_controller_request(api, CRM_OP_REPROBE, msg_data,
- (router_node? router_node : target_node));
- free_xml(msg_data);
- }
- return rc;
-}
-
-// \return Standard Pacemaker return code
-static int
-controller_resource_op(pcmk_controld_api_t *api, const char *op,
- const char *target_node, const char *router_node,
- bool cib_only, const char *rsc_id,
- const char *rsc_long_id, const char *standard,
- const char *provider, const char *type)
-{
- int rc;
- char *key;
- xmlNode *msg_data, *xml_rsc, *params;
-
- if (api == NULL) {
- return EINVAL;
- }
- if (router_node == NULL) {
- router_node = target_node;
- }
-
- msg_data = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
-
- /* The controller logs the transition key from resource op requests, so we
- * need to have *something* for it.
- */
- key = pcmk__transition_key(0, getpid(), 0,
- "xxxxxxxx-xrsc-opxx-xcrm-resourcexxxx");
- crm_xml_add(msg_data, XML_ATTR_TRANSITION_KEY, key);
- free(key);
-
- crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, target_node);
- if (safe_str_neq(router_node, target_node)) {
- crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
- }
-
- if (cib_only) {
- // Indicate that only the CIB needs to be cleaned
- crm_xml_add(msg_data, PCMK__XA_MODE, XML_TAG_CIB);
- }
-
- xml_rsc = create_xml_node(msg_data, XML_CIB_TAG_RESOURCE);
- crm_xml_add(xml_rsc, XML_ATTR_ID, rsc_id);
- crm_xml_add(xml_rsc, XML_ATTR_ID_LONG, rsc_long_id);
- crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, standard);
- crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER, provider);
- crm_xml_add(xml_rsc, XML_ATTR_TYPE, type);
-
- params = create_xml_node(msg_data, XML_TAG_ATTRS);
- crm_xml_add(params, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
-
- // The controller parses the timeout from the request
- key = crm_meta_name(XML_ATTR_TIMEOUT);
- crm_xml_add(params, key, "60000"); /* 1 minute */ //@TODO pass as arg
- free(key);
-
- rc = send_controller_request(api, op, msg_data, router_node);
- free_xml(msg_data);
- return rc;
-}
-
-static int
-controller_api_fail_resource(pcmk_controld_api_t *api,
- const char *target_node, const char *router_node,
- const char *rsc_id, const char *rsc_long_id,
- const char *standard, const char *provider,
- const char *type)
-{
- crm_debug("Sending %s IPC request to fail %s (a.k.a. %s) on %s via %s",
- CRM_SYSTEM_CRMD, crm_str(rsc_id), crm_str(rsc_long_id),
- crm_str(target_node), crm_str(router_node));
- return controller_resource_op(api, CRM_OP_LRM_FAIL, target_node,
- router_node, false, rsc_id, rsc_long_id,
- standard, provider, type);
-}
-
-static int
-controller_api_refresh_resource(pcmk_controld_api_t *api,
- const char *target_node,
- const char *router_node,
- const char *rsc_id, const char *rsc_long_id,
- const char *standard, const char *provider,
- const char *type, bool cib_only)
-{
- crm_debug("Sending %s IPC request to refresh %s (a.k.a. %s) on %s via %s",
- CRM_SYSTEM_CRMD, crm_str(rsc_id), crm_str(rsc_long_id),
- crm_str(target_node), crm_str(router_node));
- return controller_resource_op(api, CRM_OP_LRM_DELETE, target_node,
- router_node, cib_only, rsc_id, rsc_long_id,
- standard, provider, type);
-}
-
-pcmk_controld_api_t *
-pcmk_new_controld_api(const char *client_name, const char *client_uuid)
-{
- struct controller_private *private;
- pcmk_controld_api_t *api = calloc(1, sizeof(pcmk_controld_api_t));
-
- CRM_ASSERT(api != NULL);
-
- api->private = calloc(1, sizeof(struct controller_private));
- CRM_ASSERT(api->private != NULL);
- private = api->private;
-
- if (client_name == NULL) {
- client_name = crm_system_name? crm_system_name : "client";
- }
- private->client_name = strdup(client_name);
- CRM_ASSERT(private->client_name != NULL);
-
- if (client_uuid == NULL) {
- private->client_uuid = crm_generate_uuid();
- } else {
- private->client_uuid = strdup(client_uuid);
- }
- CRM_ASSERT(private->client_uuid != NULL);
-
- api->connect = controller_api_connect;
- api->disconnect = controller_api_disconnect;
- api->replies_expected = controller_api_replies_expected;
- api->reprobe = controller_api_reprobe;
- api->fail_resource = controller_api_fail_resource;
- api->refresh_resource = controller_api_refresh_resource;
- return api;
-}
-
-void
-pcmk_free_controld_api(pcmk_controld_api_t *api)
-{
- if (api != NULL) {
- struct controller_private *private = api->private;
-
- api->disconnect(api);
- free(private->client_name);
- free(private->client_uuid);
- free(api->private);
- free(api);
- }
-}
diff --git a/tools/crm_resource_controller.h b/tools/crm_resource_controller.h
deleted file mode 100644
index 50e20b4..0000000
--- a/tools/crm_resource_controller.h
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright 2020 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__CONTROLD_API_H
-#define PCMK__CONTROLD_API_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdbool.h> // bool
-
-/* This is a demonstration of an abstracted controller IPC API. It is expected
- * that this will be improved and moved to libcrmcommon.
- *
- * @TODO We could consider whether it's reasonable to have a single type for
- * all daemons' IPC APIs (e.g. pcmk_ipc_api_t instead of pcmk_*_api_t). They
- * could potentially have common connect/disconnect methods and then a void* to
- * a group of API-specific methods.
- *
- * In that case, the callback type would also need to be generic, taking
- * (pcmk_ipc_api_t *api, void *api_data, void *user_data), with individual APIs
- * having functions for getting useful info from api_data. If all APIs followed
- * the call_id model, we could use int call_id instead of api_data.
- *
- * A major annoyance is that the controller IPC protocol currently does not have
- * any way to tie a particular reply to a particular request. The current
- * clients (crmadmin, crm_node, and crm_resource) simply know what kind of reply
- * to expect for the kind of request they sent. In crm_resource's case, all it
- * does is count replies, ignoring their content altogether.
- *
- * That really forces us to have a single callback for all events rather than a
- * per-request callback. That in turn implies that callers can only provide a
- * single user data pointer.
- *
- * @TODO Define protocol version constants to use in hello message.
- * @TODO Allow callers to specify timeouts.
- * @TODO Define call IDs for controller ops, while somehow maintaining backward
- * compatibility, since a client running on a Pacemaker Remote node could
- * be older or newer than the controller on the connection's cluster
- * node.
- * @TODO The controller currently does not respond to hello messages. We should
- * establish a common connection handshake protocol for all daemons that
- * involves a hello message and acknowledgement. We should support sync
- * or async connection (i.e. block until the ack is received, or return
- * after the hello is sent and call a connection callback when the hello
- * ack is received).
- */
-
-//! \internal
-typedef struct pcmk_controld_api_s pcmk_controld_api_t;
-
-//! \internal
-typedef struct pcmk_controld_api_callback_s {
- void (*callback)(pcmk_controld_api_t *api, void *api_data, void *user_data);
- void *user_data;
-} pcmk_controld_api_cb_t;
-
-//! \internal
-struct pcmk_controld_api_s {
- //! \internal
- void *private;
-
- /*!
- * \internal
- * \brief Connect to the local controller
- *
- * \param[in] api Controller API instance
- * \param[in] use_mainloop If true, attach IPC to main loop
- * \param[in] dispatch_cb If not NULL, call this when replies are received
- * \param[in] destroy_cb If not NULL, call this if connection drops
- *
- * \return Standard Pacemaker return code
- * \note Only the pointers inside the callback objects need to be
- * persistent, not the callback objects themselves. The destroy_cb
- * will be called only for unrequested disconnects.
- */
- int (*connect)(pcmk_controld_api_t *api, bool use_mainloop,
- pcmk_controld_api_cb_t *dispatch_cb,
- pcmk_controld_api_cb_t *destroy_cb);
-
- /*!
- * \internal
- * \brief Disconnect from the local controller
- *
- * \param[in] api Controller API instance
- *
- * \return Standard Pacemaker return code
- */
- int (*disconnect)(pcmk_controld_api_t *api);
-
- /*!
- * \internal
- * \brief Check number of replies still expected from controller
- *
- * \param[in] api Controller API instance
- *
- * \return Number of expected replies
- */
- unsigned int (*replies_expected)(pcmk_controld_api_t *api);
-
- /*!
- * \internal
- * \brief Send a reprobe controller operation
- *
- * \param[in] api Controller API instance
- * \param[in] target_node Name of node to reprobe
- * \param[in] router_node Router node for host
- *
- * \return Standard Pacemaker return code
- */
- int (*reprobe)(pcmk_controld_api_t *api, const char *target_node,
- const char *router_node);
-
- /* @TODO These methods have a lot of arguments. One possibility would be to
- * make a struct for agent info (standard/provider/type), which theortically
- * could be used throughout pacemaker code. However that would end up being
- * really awkward to use generically, since sometimes you need to allocate
- * those strings (char *) and other times you only have references into XML
- * (const char *). We could make some structs just for this API.
- */
-
- /*!
- * \internal
- * \brief Ask the controller to fail a resource
- *
- * \param[in] api Controller API instance
- * \param[in] target_node Name of node resource is on
- * \param[in] router_node Router node for target
- * \param[in] rsc_id ID of resource to fail
- * \param[in] rsc_long_id Long ID of resource (if any)
- * \param[in] standard Standard of resource
- * \param[in] provider Provider of resource (if any)
- * \param[in] type Type of resource to fail
- *
- * \return Standard Pacemaker return code
- */
- int (*fail_resource)(pcmk_controld_api_t *api, const char *target_node,
- const char *router_node, const char *rsc_id,
- const char *rsc_long_id, const char *standard,
- const char *provider, const char *type);
-
- /*!
- * \internal
- * \brief Ask the controller to refresh a resource
- *
- * \param[in] api Controller API instance
- * \param[in] target_node Name of node resource is on
- * \param[in] router_node Router node for target
- * \param[in] rsc_id ID of resource to refresh
- * \param[in] rsc_long_id Long ID of resource (if any)
- * \param[in] standard Standard of resource
- * \param[in] provider Provider of resource (if any)
- * \param[in] type Type of resource
- * \param[in] cib_only If true, clean resource from CIB only
- *
- * \return Standard Pacemaker return code
- */
- int (*refresh_resource)(pcmk_controld_api_t *api, const char *target_node,
- const char *router_node, const char *rsc_id,
- const char *rsc_long_id, const char *standard,
- const char *provider, const char *type,
- bool cib_only);
-};
-
-/*!
- * \internal
- * \brief Create new controller IPC API object for clients
- *
- * \param[in] client_name Client name to use with IPC
- * \param[in] client_uuid Client UUID to use with IPC
- *
- * \return Newly allocated object
- * \note This function asserts on errors, so it will never return NULL.
- * The caller is responsible for freeing the result with
- * pcmk_free_controld_api().
- */
-pcmk_controld_api_t *pcmk_new_controld_api(const char *client_name,
- const char *client_uuid);
-
-/*!
- * \internal
- * \brief Free a controller IPC API object
- *
- * \param[in] api Controller IPC API object to free
- */
-void pcmk_free_controld_api(pcmk_controld_api_t *api);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 37789d1..cc2abeb 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -8,6 +8,7 @@
*/
#include <crm_resource.h>
+#include <crm/common/ipc_controld.h>
int resource_verbose = 0;
bool do_force = FALSE;
@@ -460,7 +461,7 @@ cli_resource_delete_attribute(pe_resource_t *rsc, const char *requested_name,
// \return Standard Pacemaker return code
static int
-send_lrm_rsc_op(pcmk_controld_api_t *controld_api, bool do_fail_resource,
+send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource,
const char *host_uname, const char *rsc_id,
pe_working_set_t *data_set)
{
@@ -528,14 +529,13 @@ send_lrm_rsc_op(pcmk_controld_api_t *controld_api, bool do_fail_resource,
rsc_api_id = rsc->id;
}
if (do_fail_resource) {
- return controld_api->fail_resource(controld_api, host_uname,
- router_node, rsc_api_id, rsc_long_id,
- rsc_class, rsc_provider, rsc_type);
+ return pcmk_controld_api_fail(controld_api, host_uname, router_node,
+ rsc_api_id, rsc_long_id,
+ rsc_class, rsc_provider, rsc_type);
} else {
- return controld_api->refresh_resource(controld_api, host_uname,
- router_node, rsc_api_id,
- rsc_long_id, rsc_class,
- rsc_provider, rsc_type, cib_only);
+ return pcmk_controld_api_refresh(controld_api, host_uname, router_node,
+ rsc_api_id, rsc_long_id, rsc_class,
+ rsc_provider, rsc_type, cib_only);
}
}
@@ -558,7 +558,7 @@ rsc_fail_name(pe_resource_t *rsc)
// \return Standard Pacemaker return code
static int
-clear_rsc_history(pcmk_controld_api_t *controld_api, const char *host_uname,
+clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname,
const char *rsc_id, pe_working_set_t *data_set)
{
int rc = pcmk_ok;
@@ -574,16 +574,16 @@ clear_rsc_history(pcmk_controld_api_t *controld_api, const char *host_uname,
}
crm_trace("Processing %d mainloop inputs",
- controld_api->replies_expected(controld_api));
+ pcmk_controld_api_replies_expected(controld_api));
while (g_main_context_iteration(NULL, FALSE)) {
crm_trace("Processed mainloop input, %d still remaining",
- controld_api->replies_expected(controld_api));
+ pcmk_controld_api_replies_expected(controld_api));
}
return rc;
}
static int
-clear_rsc_failures(pcmk_controld_api_t *controld_api, const char *node_name,
+clear_rsc_failures(pcmk_ipc_api_t *controld_api, const char *node_name,
const char *rsc_id, const char *operation,
const char *interval_spec, pe_working_set_t *data_set)
{
@@ -683,7 +683,7 @@ clear_rsc_fail_attrs(pe_resource_t *rsc, const char *operation,
}
int
-cli_resource_delete(pcmk_controld_api_t *controld_api, const char *host_uname,
+cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname,
pe_resource_t *rsc, const char *operation,
const char *interval_spec, bool just_failures,
pe_working_set_t *data_set)
@@ -792,7 +792,7 @@ cli_resource_delete(pcmk_controld_api_t *controld_api, const char *host_uname,
}
int
-cli_cleanup_all(pcmk_controld_api_t *controld_api, const char *node_name,
+cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name,
const char *operation, const char *interval_spec,
pe_working_set_t *data_set)
{
@@ -905,7 +905,7 @@ cli_resource_check(cib_t * cib_conn, pe_resource_t *rsc)
// \return Standard Pacemaker return code
int
-cli_resource_fail(pcmk_controld_api_t *controld_api, const char *host_uname,
+cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
const char *rsc_id, pe_working_set_t *data_set)
{
crm_notice("Failing %s on %s", rsc_id, host_uname);
--
1.8.3.1
From ae14fa4a831e45eae0d78b0f42765fcf40c4ce56 Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Tue, 14 Apr 2020 14:06:02 -0500
Subject: [PATCH 4/6] Refactor: tools: convert crm_node to use new controller
IPC model
---
tools/crm_node.c | 281 +++++++++++++++++++++++++++----------------------------
1 file changed, 140 insertions(+), 141 deletions(-)
diff --git a/tools/crm_node.c b/tools/crm_node.c
index db31f20..1773a36 100644
--- a/tools/crm_node.c
+++ b/tools/crm_node.c
@@ -19,6 +19,7 @@
#include <crm/common/mainloop.h>
#include <crm/msg_xml.h>
#include <crm/cib.h>
+#include <crm/common/ipc_controld.h>
#include <crm/common/attrd_internal.h>
#define SUMMARY "crm_node - Tool for displaying low-level node information"
@@ -39,7 +40,6 @@ gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data
gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
-static char *pid_s = NULL;
static GMainLoop *mainloop = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
@@ -140,11 +140,6 @@ remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError *
static void
crm_node_exit(crm_exit_t value)
{
- if (pid_s) {
- free(pid_s);
- pid_s = NULL;
- }
-
exit_code = value;
if (mainloop && g_main_loop_is_running(mainloop)) {
@@ -155,173 +150,123 @@ crm_node_exit(crm_exit_t value)
}
static void
-exit_disconnect(gpointer user_data)
-{
- fprintf(stderr, "error: Lost connection to cluster\n");
- crm_node_exit(CRM_EX_DISCONNECT);
-}
-
-typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length,
- gpointer userdata);
-
-static crm_ipc_t *
-new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch)
+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)
{
- mainloop_io_t *source = NULL;
- crm_ipc_t *ipc = NULL;
-
- struct ipc_client_callbacks ipc_callbacks = {
- .dispatch = dispatch,
- .destroy = exit_disconnect
- };
+ pcmk_controld_api_reply_t *reply = event_data;
- mainloop = g_main_loop_new(NULL, FALSE);
- source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0,
- NULL, &ipc_callbacks);
- ipc = mainloop_get_ipc_client(source);
- if (ipc == NULL) {
- fprintf(stderr,
- "error: Could not connect to cluster (is it running?)\n");
- crm_node_exit(CRM_EX_DISCONNECT);
- }
- return ipc;
-}
-
-static int
-send_controller_hello(crm_ipc_t *controller)
-{
- xmlNode *hello = NULL;
- int rc;
-
- pid_s = pcmk__getpid_s();
- hello = create_hello_message(pid_s, crm_system_name, "1", "0");
- rc = crm_ipc_send(controller, hello, 0, 0, NULL);
- free_xml(hello);
- return (rc < 0)? rc : 0;
-}
-
-static int
-send_node_info_request(crm_ipc_t *controller, uint32_t nodeid)
-{
- xmlNode *ping = NULL;
- int rc;
-
- ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD,
- crm_system_name, pid_s);
- if (nodeid > 0) {
- crm_xml_add_int(ping, XML_ATTR_ID, nodeid);
- }
- rc = crm_ipc_send(controller, ping, 0, 0, NULL);
- free_xml(ping);
- return (rc < 0)? rc : 0;
-}
+ switch (event_type) {
+ case pcmk_ipc_event_disconnect:
+ if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
+ fprintf(stderr, "error: Lost connection to controller\n");
+ }
+ goto done;
+ break;
-static int
-dispatch_controller(const char *buffer, ssize_t length, gpointer userdata)
-{
- xmlNode *message = string2xml(buffer);
- xmlNode *data = NULL;
- const char *value = NULL;
+ case pcmk_ipc_event_reply:
+ break;
- if (message == NULL) {
- fprintf(stderr, "error: Could not understand reply from controller\n");
- crm_node_exit(CRM_EX_PROTOCOL);
- return 0;
+ default:
+ return;
}
- crm_log_xml_trace(message, "controller reply");
-
- exit_code = CRM_EX_PROTOCOL;
- // Validate reply
- value = crm_element_value(message, F_CRM_MSG_TYPE);
- if (safe_str_neq(value, XML_ATTR_RESPONSE)) {
- fprintf(stderr, "error: Message from controller was not a reply\n");
+ if (status != CRM_EX_OK) {
+ fprintf(stderr, "error: Bad reply from controller: %s\n",
+ crm_exit_str(status));
goto done;
}
- value = crm_element_value(message, XML_ATTR_REFERENCE);
- if (value == NULL) {
- fprintf(stderr, "error: Controller reply did not specify original message\n");
- goto done;
- }
- data = get_message_xml(message, F_CRM_DATA);
- if (data == NULL) {
- fprintf(stderr, "error: Controller reply did not contain any data\n");
+ if (reply->reply_type != pcmk_controld_reply_info) {
+ fprintf(stderr, "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
goto done;
}
+ // Parse desired info from reply and display to user
switch (options.command) {
case 'i':
- value = crm_element_value(data, XML_ATTR_ID);
- if (value == NULL) {
- fprintf(stderr, "error: Controller reply did not contain node ID\n");
- } else {
- printf("%s\n", value);
- exit_code = CRM_EX_OK;
+ if (reply->data.node_info.id == 0) {
+ fprintf(stderr,
+ "error: Controller reply did not contain node ID\n");
+ exit_code = CRM_EX_PROTOCOL;
+ goto done;
}
+ printf("%d\n", reply->data.node_info.id);
break;
case 'n':
case 'N':
- value = crm_element_value(data, XML_ATTR_UNAME);
- if (value == NULL) {
+ if (reply->data.node_info.uname == NULL) {
fprintf(stderr, "Node is not known to cluster\n");
exit_code = CRM_EX_NOHOST;
- } else {
- printf("%s\n", value);
- exit_code = CRM_EX_OK;
+ goto done;
}
+ printf("%s\n", reply->data.node_info.uname);
break;
case 'q':
- value = crm_element_value(data, XML_ATTR_HAVE_QUORUM);
- if (value == NULL) {
- fprintf(stderr, "error: Controller reply did not contain quorum status\n");
- } else {
- bool quorum = crm_is_true(value);
-
- printf("%d\n", quorum);
- exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM;
+ printf("%d\n", reply->data.node_info.have_quorum);
+ if (!(reply->data.node_info.have_quorum)) {
+ exit_code = CRM_EX_QUORUM;
+ goto done;
}
break;
default:
fprintf(stderr, "internal error: Controller reply not expected\n");
exit_code = CRM_EX_SOFTWARE;
- break;
+ goto done;
}
+ // Success
+ exit_code = CRM_EX_OK;
done:
- free_xml(message);
- crm_node_exit(exit_code);
- return 0;
+ pcmk_disconnect_ipc(controld_api);
+ pcmk_quit_main_loop(mainloop, 10);
}
static void
run_controller_mainloop(uint32_t nodeid)
{
- crm_ipc_t *controller = NULL;
+ pcmk_ipc_api_t *controld_api = NULL;
int rc;
- controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller);
+ // Set disconnect exit code to handle unexpected disconnects
+ exit_code = CRM_EX_DISCONNECT;
+
+ // Create controller IPC object
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ return;
+ }
+ pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
- rc = send_controller_hello(controller);
- if (rc < 0) {
- fprintf(stderr, "error: Could not register with controller: %s\n",
- pcmk_strerror(rc));
- crm_node_exit(crm_errno2exit(rc));
+ // Connect to controller
+ rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ exit_code = pcmk_rc2exitc(rc);
+ return;
}
- rc = send_node_info_request(controller, nodeid);
- if (rc < 0) {
+ rc = pcmk_controld_api_node_info(controld_api, nodeid);
+ if (rc != pcmk_rc_ok) {
fprintf(stderr, "error: Could not ping controller: %s\n",
- pcmk_strerror(rc));
- crm_node_exit(crm_errno2exit(rc));
+ pcmk_rc_str(rc));
+ pcmk_disconnect_ipc(controld_api);
+ exit_code = pcmk_rc2exitc(rc);
+ return;
}
- // Run main loop to get controller reply via dispatch_controller()
+ // Run main loop to get controller reply via controller_event_cb()
+ mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
mainloop = NULL;
+ pcmk_free_ipc_api(controld_api);
}
static void
@@ -385,32 +330,56 @@ cib_remove_node(long id, const char *name)
}
static int
+controller_remove_node(const char *node_name, long nodeid)
+{
+ pcmk_ipc_api_t *controld_api = NULL;
+ int rc;
+
+ // Create controller IPC object
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ return ENOTCONN;
+ }
+
+ // Connect to controller (without main loop)
+ rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ pcmk_free_ipc_api(controld_api);
+ return rc;
+ }
+
+ rc = pcmk_ipc_purge_node(controld_api, node_name, nodeid);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr,
+ "error: Could not clear node from controller's cache: %s\n",
+ pcmk_rc_str(rc));
+ }
+
+ pcmk_free_ipc_api(controld_api);
+ return pcmk_rc_ok;
+}
+
+static int
tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
{
int rc = -1;
- crm_ipc_t *conn = crm_ipc_new(target, 0);
+ crm_ipc_t *conn = NULL;
xmlNode *cmd = NULL;
+ conn = crm_ipc_new(target, 0);
if (!conn) {
return -ENOTCONN;
}
-
if (!crm_ipc_connect(conn)) {
crm_perror(LOG_ERR, "Connection to %s failed", target);
crm_ipc_destroy(conn);
return -ENOTCONN;
}
- if(safe_str_eq(target, CRM_SYSTEM_CRMD)) {
- // The controller requires a hello message before sending a request
- rc = send_controller_hello(conn);
- if (rc < 0) {
- fprintf(stderr, "error: Could not register with controller: %s\n",
- pcmk_strerror(rc));
- return rc;
- }
- }
-
crm_trace("Removing %s[%ld] from the %s membership cache",
node_name, nodeid, target);
@@ -427,9 +396,9 @@ tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
crm_xml_add_int(cmd, PCMK__XA_ATTR_NODE_ID, (int) nodeid);
}
- } else {
- cmd = create_request(CRM_OP_RM_NODE_CACHE,
- NULL, NULL, target, crm_system_name, pid_s);
+ } else { // Fencer or pacemakerd
+ cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target,
+ crm_system_name, NULL);
if (nodeid > 0) {
crm_xml_set_id(cmd, "%ld", nodeid);
}
@@ -441,6 +410,7 @@ tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
target, node_name, nodeid, rc);
if (rc > 0) {
+ // @TODO Should this be done just once after all the rest?
rc = cib_remove_node(nodeid, node_name);
}
@@ -455,12 +425,12 @@ tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
static void
remove_node(const char *target_uname)
{
+ int rc;
int d = 0;
long nodeid = 0;
const char *node_name = NULL;
char *endptr = NULL;
const char *daemons[] = {
- CRM_SYSTEM_CRMD,
"stonith-ng",
T_ATTRD,
CRM_SYSTEM_MCP,
@@ -476,6 +446,12 @@ remove_node(const char *target_uname)
node_name = target_uname;
}
+ rc = controller_remove_node(node_name, nodeid);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ return;
+ }
+
for (d = 0; d < DIMOF(daemons); d++) {
if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
crm_err("Failed to connect to %s to remove node '%s'",
@@ -545,12 +521,34 @@ node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata)
}
static void
+lost_pacemakerd(gpointer user_data)
+{
+ fprintf(stderr, "error: Lost connection to cluster\n");
+ exit_code = CRM_EX_DISCONNECT;
+ g_main_loop_quit(mainloop);
+}
+
+static void
run_pacemakerd_mainloop(void)
{
crm_ipc_t *ipc = NULL;
xmlNode *poke = NULL;
+ mainloop_io_t *source = NULL;
- ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch);
+ struct ipc_client_callbacks ipc_callbacks = {
+ .dispatch = node_mcp_dispatch,
+ .destroy = lost_pacemakerd
+ };
+
+ source = mainloop_add_ipc_client(CRM_SYSTEM_MCP, G_PRIORITY_DEFAULT, 0,
+ NULL, &ipc_callbacks);
+ ipc = mainloop_get_ipc_client(source);
+ if (ipc == NULL) {
+ fprintf(stderr,
+ "error: Could not connect to cluster (is it running?)\n");
+ exit_code = CRM_EX_DISCONNECT;
+ return;
+ }
// Sending anything will get us a list of nodes
poke = create_xml_node(NULL, "poke");
@@ -558,6 +556,7 @@ run_pacemakerd_mainloop(void)
free_xml(poke);
// Handle reply via node_mcp_dispatch()
+ mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
mainloop = NULL;
--
1.8.3.1
From a361f764cb28630d440eec0f3e04a4f3812825eb Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Tue, 14 Apr 2020 16:05:15 -0500
Subject: [PATCH 5/6] Refactor: tools: remove dead code from crm_node
---
tools/crm_node.c | 22 +---------------------
1 file changed, 1 insertion(+), 21 deletions(-)
diff --git a/tools/crm_node.c b/tools/crm_node.c
index 1773a36..57c2ee5 100644
--- a/tools/crm_node.c
+++ b/tools/crm_node.c
@@ -130,25 +130,6 @@ remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError *
return TRUE;
}
-/*!
- * \internal
- * \brief Exit crm_node
- * Clean up memory, and either quit mainloop (if running) or exit
- *
- * \param[in] value Exit status
- */
-static void
-crm_node_exit(crm_exit_t value)
-{
- exit_code = value;
-
- if (mainloop && g_main_loop_is_running(mainloop)) {
- g_main_loop_quit(mainloop);
- } else {
- crm_exit(exit_code);
- }
-}
-
static void
controller_event_cb(pcmk_ipc_api_t *controld_api,
enum pcmk_ipc_event event_type, crm_exit_t status,
@@ -660,6 +641,5 @@ done:
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
- crm_node_exit(exit_code);
- return exit_code;
+ return crm_exit(exit_code);
}
--
1.8.3.1
From 591944539259f6294450517770d95c7a02fc599c Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Mon, 20 Apr 2020 15:48:15 -0500
Subject: [PATCH 6/6] Refactor: tools: convert crmadmin to use new controller
IPC model
---
tools/crmadmin.c | 484 ++++++++++++++++++++++++-------------------------------
1 file changed, 208 insertions(+), 276 deletions(-)
diff --git a/tools/crmadmin.c b/tools/crmadmin.c
index 3e9e959..4688458 100644
--- a/tools/crmadmin.c
+++ b/tools/crmadmin.c
@@ -9,56 +9,44 @@
#include <crm_internal.h>
-#include <sys/param.h>
-
#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h> // atoi()
-#include <stdlib.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <libgen.h>
#include <glib.h> // gboolean, GMainLoop, etc.
+#include <libxml/tree.h> // xmlNode
#include <crm/crm.h>
+#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
-
+#include <crm/common/ipc_controld.h>
#include <crm/common/mainloop.h>
-#include <crm/cib.h>
-
#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;
-static crm_ipc_t *crmd_channel = NULL;
-static char *admin_uuid = NULL;
-
-gboolean do_init(void);
-int do_work(void);
-void crmadmin_ipc_connection_destroy(gpointer user_data);
-int admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata);
-int do_find_node_list(xmlNode * xml_node);
+
+bool do_work(pcmk_ipc_api_t *api);
+void do_find_node_list(xmlNode *xml_node);
gboolean admin_message_timeout(gpointer data);
+static enum {
+ cmd_none,
+ cmd_shutdown,
+ cmd_health,
+ cmd_elect_dc,
+ cmd_whois_dc,
+ cmd_list_nodes,
+} command = cmd_none;
+
static gboolean BE_VERBOSE = FALSE;
-static int expected_responses = 1;
static gboolean BASH_EXPORT = FALSE;
-static gboolean DO_HEALTH = FALSE;
-static gboolean DO_RESET = FALSE;
-static gboolean DO_RESOURCE = FALSE;
-static gboolean DO_ELECT_DC = FALSE;
-static gboolean DO_WHOIS_DC = FALSE;
-static gboolean DO_NODE_LIST = FALSE;
static gboolean BE_SILENT = FALSE;
-static gboolean DO_RESOURCE_LIST = FALSE;
-static const char *crmd_operation = NULL;
static char *dest_node = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
-static const char *sys_to = NULL;
static pcmk__cli_option_t long_options[] = {
// long option, argument type, storage, short option, description, flags
@@ -133,7 +121,8 @@ static pcmk__cli_option_t long_options[] = {
},
{
"bash-export", no_argument, NULL, 'B',
- "Create Bash export entries of the form 'export uname=uuid'\n",
+ "Display nodes as shell commands of the form 'export uname=uuid' "
+ "(valid with -N/--nodes)'\n",
pcmk__option_default
},
{
@@ -142,13 +131,98 @@ static pcmk__cli_option_t long_options[] = {
},
{
"-spacer-", no_argument, NULL, '-',
- " The -K and -E commands are rarely used and may be removed in "
- "future versions.",
+ "The -K and -E commands do not work and may be removed in a future "
+ "version.",
pcmk__option_default
},
{ 0, 0, 0, 0 }
};
+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
+ fprintf(stderr, "error: Lost connection to controller\n");
+ }
+ 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) {
+ fprintf(stderr, "error: Bad reply from controller: %s",
+ crm_exit_str(status));
+ exit_code = status;
+ goto done;
+ }
+
+ if (reply->reply_type != pcmk_controld_reply_ping) {
+ fprintf(stderr, "error: Unknown reply type %d from controller\n",
+ reply->reply_type);
+ goto done;
+ }
+
+ // Parse desired information from reply
+ switch (command) {
+ case cmd_health:
+ printf("Status of %s@%s: %s (%s)\n",
+ reply->data.ping.sys_from,
+ reply->host_from,
+ reply->data.ping.fsa_state,
+ reply->data.ping.result);
+ if (BE_SILENT && (reply->data.ping.fsa_state != NULL)) {
+ fprintf(stderr, "%s\n", reply->data.ping.fsa_state);
+ }
+ exit_code = CRM_EX_OK;
+ break;
+
+ case cmd_whois_dc:
+ printf("Designated Controller is: %s\n", reply->host_from);
+ if (BE_SILENT && (reply->host_from != NULL)) {
+ fprintf(stderr, "%s\n", 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);
+}
+
// \return Standard Pacemaker return code
static int
list_nodes()
@@ -181,6 +255,9 @@ main(int argc, char **argv)
int option_index = 0;
int argerr = 0;
int flag;
+ int rc;
+ pcmk_ipc_api_t *controld_api = NULL;
+ bool need_controld_api = true;
crm_log_cli_init("crmadmin");
pcmk__set_cli_options(NULL, "<command> [options]", long_options,
@@ -211,33 +288,40 @@ main(int argc, char **argv)
pcmk__cli_help(flag, CRM_EX_OK);
break;
case 'D':
- DO_WHOIS_DC = TRUE;
+ command = cmd_whois_dc;
break;
case 'B':
BASH_EXPORT = TRUE;
break;
case 'K':
- DO_RESET = TRUE;
+ command = cmd_shutdown;
crm_trace("Option %c => %s", flag, optarg);
+ if (dest_node != NULL) {
+ free(dest_node);
+ }
dest_node = strdup(optarg);
- crmd_operation = CRM_OP_LOCAL_SHUTDOWN;
break;
case 'q':
BE_SILENT = TRUE;
break;
case 'S':
- DO_HEALTH = TRUE;
+ command = cmd_health;
crm_trace("Option %c => %s", flag, optarg);
+ if (dest_node != NULL) {
+ free(dest_node);
+ }
dest_node = strdup(optarg);
break;
case 'E':
- DO_ELECT_DC = TRUE;
+ command = cmd_elect_dc;
break;
case 'N':
- DO_NODE_LIST = TRUE;
+ command = cmd_list_nodes;
+ need_controld_api = false;
break;
case 'H':
- DO_HEALTH = TRUE;
+ fprintf(stderr, "Cluster-wide health option not supported\n");
+ ++argerr;
break;
default:
printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag);
@@ -257,285 +341,133 @@ main(int argc, char **argv)
++argerr;
}
+ if (command == cmd_none) {
+ fprintf(stderr, "error: Must specify a command option\n\n");
+ ++argerr;
+ }
+
if (argerr) {
pcmk__cli_help('?', CRM_EX_USAGE);
}
- if (do_init()) {
- int res = 0;
-
- res = do_work();
- if (res > 0) {
- /* wait for the reply by creating a mainloop and running it until
- * the callbacks are invoked...
- */
- mainloop = g_main_loop_new(NULL, FALSE);
- crm_trace("Waiting for %d replies from the local CRM", expected_responses);
-
- message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL);
-
- g_main_loop_run(mainloop);
-
- } else if (res < 0) {
- crm_err("No message to send");
- exit_code = CRM_EX_ERROR;
+ // Connect to the controller if needed
+ if (need_controld_api) {
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (controld_api == NULL) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ exit_code = pcmk_rc2exitc(rc);
+ goto done;
}
- } else {
- crm_warn("Init failed, could not perform requested operations");
- exit_code = CRM_EX_UNAVAILABLE;
- }
-
- crm_trace("%s exiting normally", crm_system_name);
- return exit_code;
-}
-
-int
-do_work(void)
-{
- int ret = 1;
-
- /* construct the request */
- xmlNode *msg_data = NULL;
- gboolean all_is_good = TRUE;
-
- if (DO_HEALTH == TRUE) {
- crm_trace("Querying the system");
-
- sys_to = CRM_SYSTEM_DC;
-
- if (dest_node != NULL) {
- sys_to = CRM_SYSTEM_CRMD;
- crmd_operation = CRM_OP_PING;
-
- if (BE_VERBOSE) {
- expected_responses = 1;
- }
-
- } else {
- crm_info("Cluster-wide health not available yet");
- all_is_good = FALSE;
+ 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) {
+ fprintf(stderr, "error: Could not connect to controller: %s\n",
+ pcmk_rc_str(rc));
+ exit_code = pcmk_rc2exitc(rc);
+ goto done;
}
-
- } else if (DO_ELECT_DC) {
- /* tell the local node to initiate an election */
-
- dest_node = NULL;
- sys_to = CRM_SYSTEM_CRMD;
- crmd_operation = CRM_OP_VOTE;
- ret = 0; /* no return message */
-
- } else if (DO_WHOIS_DC) {
- dest_node = NULL;
- sys_to = CRM_SYSTEM_DC;
- crmd_operation = CRM_OP_PING;
-
- } else if (DO_NODE_LIST) {
- crm_exit(pcmk_rc2exitc(list_nodes()));
-
- } else if (DO_RESET) {
- /* tell dest_node to initiate the shutdown procedure
- *
- * if dest_node is NULL, the request will be sent to the
- * local node
- */
- sys_to = CRM_SYSTEM_CRMD;
- ret = 0; /* no return message */
-
- } else {
- crm_err("Unknown options");
- all_is_good = FALSE;
}
- if (all_is_good == FALSE) {
- crm_err("Creation of request failed. No message to send");
- return -1;
+ if (do_work(controld_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);
}
-/* send it */
- if (crmd_channel == NULL) {
- crm_err("The IPC connection is not valid, cannot send anything");
- return -1;
- }
-
- if (sys_to == NULL) {
- if (dest_node != NULL) {
- sys_to = CRM_SYSTEM_CRMD;
- } else {
- sys_to = CRM_SYSTEM_DC;
- }
- }
-
- {
- xmlNode *cmd = create_request(crmd_operation, msg_data, dest_node, sys_to,
- crm_system_name, admin_uuid);
-
- crm_ipc_send(crmd_channel, cmd, 0, 0, NULL);
- free_xml(cmd);
- }
-
- return ret;
-}
+done:
+ if (controld_api != NULL) {
+ pcmk_ipc_api_t *capi = controld_api;
-void
-crmadmin_ipc_connection_destroy(gpointer user_data)
-{
- crm_err("Connection to controller was terminated");
- if (mainloop) {
- g_main_loop_quit(mainloop);
- } else {
- crm_exit(CRM_EX_DISCONNECT);
+ controld_api = NULL; // Ensure we can't free this twice
+ pcmk_free_ipc_api(capi);
}
-}
-
-struct ipc_client_callbacks crm_callbacks = {
- .dispatch = admin_msg_callback,
- .destroy = crmadmin_ipc_connection_destroy
-};
-
-gboolean
-do_init(void)
-{
- mainloop_io_t *source =
- mainloop_add_ipc_client(CRM_SYSTEM_CRMD, G_PRIORITY_DEFAULT, 0, NULL, &crm_callbacks);
-
- admin_uuid = pcmk__getpid_s();
-
- crmd_channel = mainloop_get_ipc_client(source);
-
- if (DO_RESOURCE || DO_RESOURCE_LIST || DO_NODE_LIST) {
- return TRUE;
-
- } else if (crmd_channel != NULL) {
- xmlNode *xml = create_hello_message(admin_uuid, crm_system_name, "0", "1");
-
- crm_ipc_send(crmd_channel, xml, 0, 0, NULL);
- return TRUE;
+ if (mainloop != NULL) {
+ g_main_loop_unref(mainloop);
+ mainloop = NULL;
}
- return FALSE;
+ return crm_exit(exit_code);
}
-static bool
-validate_crm_message(xmlNode * msg, const char *sys, const char *uuid, const char *msg_type)
+// \return True if reply from controller is needed
+bool
+do_work(pcmk_ipc_api_t *controld_api)
{
- const char *type = NULL;
- const char *crm_msg_reference = NULL;
-
- if (msg == NULL) {
- return FALSE;
- }
+ bool need_reply = false;
+ int rc = pcmk_rc_ok;
- type = crm_element_value(msg, F_CRM_MSG_TYPE);
- crm_msg_reference = crm_element_value(msg, XML_ATTR_REFERENCE);
-
- if (type == NULL) {
- crm_info("No message type defined.");
- return FALSE;
-
- } else if (msg_type != NULL && strcasecmp(msg_type, type) != 0) {
- crm_info("Expecting a (%s) message but received a (%s).", msg_type, type);
- return FALSE;
- }
-
- if (crm_msg_reference == NULL) {
- crm_info("No message crm_msg_reference defined.");
- return FALSE;
- }
-
- return TRUE;
-}
-
-int
-admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata)
-{
- static int received_responses = 0;
- xmlNode *xml = string2xml(buffer);
-
- received_responses++;
- g_source_remove(message_timer_id);
-
- crm_log_xml_trace(xml, "ipc");
-
- if (xml == NULL) {
- crm_info("XML in IPC message was not valid... " "discarding.");
-
- } else if (validate_crm_message(xml, crm_system_name, admin_uuid, XML_ATTR_RESPONSE) == FALSE) {
- crm_trace("Message was not a CRM response. Discarding.");
-
- } else if (DO_HEALTH) {
- xmlNode *data = get_message_xml(xml, F_CRM_DATA);
- const char *state = crm_element_value(data, XML_PING_ATTR_CRMDSTATE);
+ switch (command) {
+ case cmd_shutdown:
+ rc = pcmk_controld_api_shutdown(controld_api, dest_node);
+ break;
- printf("Status of %s@%s: %s (%s)\n",
- crm_element_value(data, XML_PING_ATTR_SYSFROM),
- crm_element_value(xml, F_CRM_HOST_FROM),
- state, crm_element_value(data, XML_PING_ATTR_STATUS));
+ case cmd_health: // dest_node != NULL
+ case cmd_whois_dc: // dest_node == NULL
+ rc = pcmk_controld_api_ping(controld_api, dest_node);
+ need_reply = true;
+ break;
- if (BE_SILENT && state != NULL) {
- fprintf(stderr, "%s\n", state);
- }
+ case cmd_elect_dc:
+ rc = pcmk_controld_api_start_election(controld_api);
+ break;
- } else if (DO_WHOIS_DC) {
- const char *dc = crm_element_value(xml, F_CRM_HOST_FROM);
+ case cmd_list_nodes:
+ rc = list_nodes();
+ break;
- printf("Designated Controller is: %s\n", dc);
- if (BE_SILENT && dc != NULL) {
- fprintf(stderr, "%s\n", dc);
- }
- crm_exit(CRM_EX_OK);
+ case cmd_none: // not actually possible here
+ break;
}
-
- free_xml(xml);
-
- if (received_responses >= expected_responses) {
- crm_trace("Received expected number (%d) of replies, exiting normally",
- expected_responses);
- crm_exit(CRM_EX_OK);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "error: Command failed: %s", pcmk_rc_str(rc));
+ exit_code = pcmk_rc2exitc(rc);
}
-
- message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL);
- return 0;
+ return need_reply;
}
gboolean
admin_message_timeout(gpointer data)
{
- fprintf(stderr, "No messages received in %d seconds.. aborting\n",
- (int)message_timeout_ms / 1000);
- crm_err("No messages received in %d seconds", (int)message_timeout_ms / 1000);
- exit_code = CRM_EX_TIMEOUT;
- g_main_loop_quit(mainloop);
- return FALSE;
+ fprintf(stderr,
+ "error: No reply received from controller before timeout (%dms)\n",
+ message_timeout_ms);
+ message_timer_id = 0;
+ quit_main_loop(CRM_EX_TIMEOUT);
+ return FALSE; // Tells glib to remove source
}
-int
+void
do_find_node_list(xmlNode * xml_node)
{
int found = 0;
xmlNode *node = NULL;
xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node);
- for (node = __xml_first_child_element(nodes); node != NULL;
- node = __xml_next_element(node)) {
+ for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL;
+ node = crm_next_same_xml(node)) {
- if (crm_str_eq((const char *)node->name, XML_CIB_TAG_NODE, TRUE)) {
+ if (BASH_EXPORT) {
+ printf("export %s=%s\n",
+ crm_element_value(node, XML_ATTR_UNAME),
+ crm_element_value(node, XML_ATTR_ID));
+ } else {
+ const char *node_type = crm_element_value(node, XML_ATTR_TYPE);
- if (BASH_EXPORT) {
- printf("export %s=%s\n",
- crm_element_value(node, XML_ATTR_UNAME),
- crm_element_value(node, XML_ATTR_ID));
- } else {
- printf("%s node: %s (%s)\n",
- crm_element_value(node, XML_ATTR_TYPE),
- crm_element_value(node, XML_ATTR_UNAME),
- crm_element_value(node, XML_ATTR_ID));
+ if (node_type == NULL) {
+ node_type = "member";
}
- found++;
+ printf("%s node: %s (%s)\n", node_type,
+ crm_element_value(node, XML_ATTR_UNAME),
+ crm_element_value(node, XML_ATTR_ID));
}
+ found++;
}
+ // @TODO List Pacemaker Remote nodes that don't have a <node> entry
if (found == 0) {
- printf("NO nodes configured\n");
+ printf("No nodes configured\n");
}
-
- return found;
}
--
1.8.3.1