c6b88556ad
# Conflicts: # .gitignore # .pacemaker.metadata # SPECS/pacemaker.spec
4147 lines
135 KiB
Diff
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
|
|
|