diff options
Diffstat (limited to 'daemons/execd/remoted_proxy.c')
-rw-r--r-- | daemons/execd/remoted_proxy.c | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/daemons/execd/remoted_proxy.c b/daemons/execd/remoted_proxy.c new file mode 100644 index 0000000..62c8c3a --- /dev/null +++ b/daemons/execd/remoted_proxy.c @@ -0,0 +1,470 @@ +/* + * Copyright 2012-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <glib.h> +#include <unistd.h> + +#include "pacemaker-execd.h" +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/services.h> +#include <crm/common/mainloop.h> +#include <crm/common/ipc.h> +#include <crm/common/ipc_internal.h> +#include <crm/cib/internal.h> +#include <crm/fencing/internal.h> + +static qb_ipcs_service_t *cib_ro = NULL; +static qb_ipcs_service_t *cib_rw = NULL; +static qb_ipcs_service_t *cib_shm = NULL; + +static qb_ipcs_service_t *attrd_ipcs = NULL; +static qb_ipcs_service_t *crmd_ipcs = NULL; +static qb_ipcs_service_t *stonith_ipcs = NULL; +static qb_ipcs_service_t *pacemakerd_ipcs = NULL; + +// An IPC provider is a cluster node controller connecting as a client +static GList *ipc_providers = NULL; +/* ipc clients == things like cibadmin, crm_resource, connecting locally */ +static GHashTable *ipc_clients = NULL; + +/*! + * \internal + * \brief Get an IPC proxy provider + * + * \return Pointer to a provider if one exists, NULL otherwise + * + * \note Grab the first provider, which is the most recent connection. That way, + * if we haven't yet timed out an old, failed connection, we don't try to + * use it. + */ +pcmk__client_t * +ipc_proxy_get_provider(void) +{ + return ipc_providers? (pcmk__client_t *) (ipc_providers->data) : NULL; +} + +/*! + * \internal + * \brief Accept a client connection on a proxy IPC server + * + * \param[in] c Client's IPC connection + * \param[in] uid Client's user ID + * \param[in] gid Client's group ID + * \param[in] ipc_channel Name of IPC server to proxy + * + * \return pcmk_ok on success, -errno on error + */ +static int32_t +ipc_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid, const char *ipc_channel) +{ + pcmk__client_t *client; + pcmk__client_t *ipc_proxy = ipc_proxy_get_provider(); + xmlNode *msg; + + if (ipc_proxy == NULL) { + crm_warn("Cannot proxy IPC connection from uid %d gid %d to %s " + "because not connected to cluster", uid, gid, ipc_channel); + return -EREMOTEIO; + } + + /* This new client is a local IPC client on a Pacemaker Remote controlled + * node, needing to access cluster node IPC services. + */ + client = pcmk__new_client(c, uid, gid); + if (client == NULL) { + return -EREMOTEIO; + } + + /* This ipc client is bound to a single ipc provider. If the + * provider goes away, this client is disconnected */ + client->userdata = strdup(ipc_proxy->id); + client->name = crm_strdup_printf("proxy-%s-%d-%.8s", ipc_channel, client->pid, client->id); + + /* Allow remote executor to distinguish between proxied local clients and + * actual executor API clients + */ + pcmk__set_client_flags(client, pcmk__client_to_proxy); + + g_hash_table_insert(ipc_clients, client->id, client); + + msg = create_xml_node(NULL, T_LRMD_IPC_PROXY); + crm_xml_add(msg, F_LRMD_IPC_OP, LRMD_IPC_OP_NEW); + crm_xml_add(msg, F_LRMD_IPC_IPC_SERVER, ipc_channel); + crm_xml_add(msg, F_LRMD_IPC_SESSION, client->id); + lrmd_server_send_notify(ipc_proxy, msg); + free_xml(msg); + crm_debug("Accepted IPC proxy connection (session ID %s) " + "from uid %d gid %d on channel %s", + client->id, uid, gid, ipc_channel); + return 0; +} + +static int32_t +crmd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return ipc_proxy_accept(c, uid, gid, CRM_SYSTEM_CRMD); +} + +static int32_t +attrd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return ipc_proxy_accept(c, uid, gid, T_ATTRD); +} + +static int32_t +stonith_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return ipc_proxy_accept(c, uid, gid, "stonith-ng"); +} + +static int32_t +pacemakerd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return -EREMOTEIO; +} + +static int32_t +cib_proxy_accept_rw(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RW); +} + +static int32_t +cib_proxy_accept_ro(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RO); +} + +void +ipc_proxy_forward_client(pcmk__client_t *ipc_proxy, xmlNode *xml) +{ + const char *session = crm_element_value(xml, F_LRMD_IPC_SESSION); + const char *msg_type = crm_element_value(xml, F_LRMD_IPC_OP); + xmlNode *msg = get_message_xml(xml, F_LRMD_IPC_MSG); + pcmk__client_t *ipc_client; + int rc = pcmk_rc_ok; + + /* If the IPC provider is acknowledging our shutdown request, + * defuse the short exit timer to give the cluster time to + * stop any resources we're running. + */ + if (pcmk__str_eq(msg_type, LRMD_IPC_OP_SHUTDOWN_ACK, pcmk__str_casei)) { + handle_shutdown_ack(); + return; + } + + if (pcmk__str_eq(msg_type, LRMD_IPC_OP_SHUTDOWN_NACK, pcmk__str_casei)) { + handle_shutdown_nack(); + return; + } + + ipc_client = pcmk__find_client_by_id(session); + if (ipc_client == NULL) { + xmlNode *msg = create_xml_node(NULL, T_LRMD_IPC_PROXY); + crm_xml_add(msg, F_LRMD_IPC_OP, LRMD_IPC_OP_DESTROY); + crm_xml_add(msg, F_LRMD_IPC_SESSION, session); + lrmd_server_send_notify(ipc_proxy, msg); + free_xml(msg); + return; + } + + /* This is an event or response from the ipc provider + * going to the local ipc client. + * + * Looking at the chain of events. + * + * -----remote node----------------|---- cluster node ------ + * ipc_client <--1--> this code + * <--2--> pacemaker-controld:remote_proxy_cb/remote_proxy_relay_event() + * <--3--> ipc server + * + * This function is receiving a msg from connection 2 + * and forwarding it to connection 1. + */ + + if (pcmk__str_eq(msg_type, LRMD_IPC_OP_EVENT, pcmk__str_casei)) { + crm_trace("Sending event to %s", ipc_client->id); + rc = pcmk__ipc_send_xml(ipc_client, 0, msg, crm_ipc_server_event); + + } else if (pcmk__str_eq(msg_type, LRMD_IPC_OP_RESPONSE, pcmk__str_casei)) { + int msg_id = 0; + + crm_element_value_int(xml, F_LRMD_IPC_MSG_ID, &msg_id); + crm_trace("Sending response to %d - %s", ipc_client->request_id, ipc_client->id); + rc = pcmk__ipc_send_xml(ipc_client, msg_id, msg, FALSE); + + CRM_LOG_ASSERT(msg_id == ipc_client->request_id); + ipc_client->request_id = 0; + + } else if (pcmk__str_eq(msg_type, LRMD_IPC_OP_DESTROY, pcmk__str_casei)) { + qb_ipcs_disconnect(ipc_client->ipcs); + + } else { + crm_err("Unknown ipc proxy msg type %s" , msg_type); + } + + if (rc != pcmk_rc_ok) { + crm_warn("Could not proxy IPC to client %s: %s " CRM_XS " rc=%d", + ipc_client->id, pcmk_rc_str(rc), rc); + } +} + +static int32_t +ipc_proxy_dispatch(qb_ipcs_connection_t * c, void *data, size_t size) +{ + uint32_t id = 0; + uint32_t flags = 0; + pcmk__client_t *client = pcmk__find_client(c); + pcmk__client_t *ipc_proxy = pcmk__find_client_by_id(client->userdata); + xmlNode *request = NULL; + xmlNode *msg = NULL; + + if (!ipc_proxy) { + qb_ipcs_disconnect(client->ipcs); + return 0; + } + + /* This is a request from the local ipc client going + * to the ipc provider. + * + * Looking at the chain of events. + * + * -----remote node----------------|---- cluster node ------ + * ipc_client <--1--> this code + * <--2--> pacemaker-controld:remote_proxy_dispatch_internal() + * <--3--> ipc server + * + * This function is receiving a request from connection + * 1 and forwarding it to connection 2. + */ + request = pcmk__client_data2xml(client, data, &id, &flags); + + if (!request) { + return 0; + } + + CRM_CHECK(client != NULL, crm_err("Invalid client"); + free_xml(request); return FALSE); + CRM_CHECK(client->id != NULL, crm_err("Invalid client: %p", client); + free_xml(request); return FALSE); + + /* This ensures that synced request/responses happen over the event channel + * in the controller, allowing the controller to process the messages async. + */ + pcmk__set_ipc_flags(flags, pcmk__client_name(client), crm_ipc_proxied); + client->request_id = id; + + msg = create_xml_node(NULL, T_LRMD_IPC_PROXY); + crm_xml_add(msg, F_LRMD_IPC_OP, LRMD_IPC_OP_REQUEST); + crm_xml_add(msg, F_LRMD_IPC_SESSION, client->id); + crm_xml_add(msg, F_LRMD_IPC_CLIENT, pcmk__client_name(client)); + crm_xml_add(msg, F_LRMD_IPC_USER, client->user); + crm_xml_add_int(msg, F_LRMD_IPC_MSG_ID, id); + crm_xml_add_int(msg, F_LRMD_IPC_MSG_FLAGS, flags); + add_message_xml(msg, F_LRMD_IPC_MSG, request); + lrmd_server_send_notify(ipc_proxy, msg); + free_xml(request); + free_xml(msg); + + return 0; +} + +/*! + * \internal + * \brief Notify a proxy provider that we wish to shut down + * + * \param[in,out] ipc_proxy IPC client connection to proxy provider + * + * \return 0 on success, -1 on error + */ +int +ipc_proxy_shutdown_req(pcmk__client_t *ipc_proxy) +{ + xmlNode *msg = create_xml_node(NULL, T_LRMD_IPC_PROXY); + int rc; + + crm_xml_add(msg, F_LRMD_IPC_OP, LRMD_IPC_OP_SHUTDOWN_REQ); + + /* We don't really have a session, but the controller needs this attribute + * to recognize this as proxy communication. + */ + crm_xml_add(msg, F_LRMD_IPC_SESSION, "0"); + + rc = (lrmd_server_send_notify(ipc_proxy, msg) != pcmk_rc_ok)? -1 : 0; + free_xml(msg); + return rc; +} + +static int32_t +ipc_proxy_closed(qb_ipcs_connection_t * c) +{ + pcmk__client_t *client = pcmk__find_client(c); + pcmk__client_t *ipc_proxy; + + if (client == NULL) { + return 0; + } + + ipc_proxy = pcmk__find_client_by_id(client->userdata); + + crm_trace("Connection %p", c); + + if (ipc_proxy) { + xmlNode *msg = create_xml_node(NULL, T_LRMD_IPC_PROXY); + crm_xml_add(msg, F_LRMD_IPC_OP, LRMD_IPC_OP_DESTROY); + crm_xml_add(msg, F_LRMD_IPC_SESSION, client->id); + lrmd_server_send_notify(ipc_proxy, msg); + free_xml(msg); + } + + g_hash_table_remove(ipc_clients, client->id); + + free(client->userdata); + client->userdata = NULL; + pcmk__free_client(client); + return 0; +} + +static void +ipc_proxy_destroy(qb_ipcs_connection_t * c) +{ + crm_trace("Connection %p", c); + ipc_proxy_closed(c); +} + +static struct qb_ipcs_service_handlers crmd_proxy_callbacks = { + .connection_accept = crmd_proxy_accept, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + +static struct qb_ipcs_service_handlers attrd_proxy_callbacks = { + .connection_accept = attrd_proxy_accept, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + +static struct qb_ipcs_service_handlers stonith_proxy_callbacks = { + .connection_accept = stonith_proxy_accept, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + +static struct qb_ipcs_service_handlers pacemakerd_proxy_callbacks = { + .connection_accept = pacemakerd_proxy_accept, + .connection_created = NULL, + .msg_process = NULL, + .connection_closed = NULL, + .connection_destroyed = NULL +}; + +static struct qb_ipcs_service_handlers cib_proxy_callbacks_ro = { + .connection_accept = cib_proxy_accept_ro, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + +static struct qb_ipcs_service_handlers cib_proxy_callbacks_rw = { + .connection_accept = cib_proxy_accept_rw, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + +void +ipc_proxy_add_provider(pcmk__client_t *ipc_proxy) +{ + // Prepending ensures the most recent connection is always first + ipc_providers = g_list_prepend(ipc_providers, ipc_proxy); +} + +void +ipc_proxy_remove_provider(pcmk__client_t *ipc_proxy) +{ + GHashTableIter iter; + pcmk__client_t *ipc_client = NULL; + char *key = NULL; + GList *remove_these = NULL; + GList *gIter = NULL; + + ipc_providers = g_list_remove(ipc_providers, ipc_proxy); + + g_hash_table_iter_init(&iter, ipc_clients); + while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & ipc_client)) { + const char *proxy_id = ipc_client->userdata; + if (pcmk__str_eq(proxy_id, ipc_proxy->id, pcmk__str_casei)) { + crm_info("ipc proxy connection for client %s pid %d destroyed because cluster node disconnected.", + ipc_client->id, ipc_client->pid); + /* we can't remove during the iteration, so copy items + * to a list we can destroy later */ + remove_these = g_list_append(remove_these, ipc_client); + } + } + + for (gIter = remove_these; gIter != NULL; gIter = gIter->next) { + ipc_client = gIter->data; + + // Disconnection callback will free the client here + qb_ipcs_disconnect(ipc_client->ipcs); + } + + /* just frees the list, not the elements in the list */ + g_list_free(remove_these); +} + +void +ipc_proxy_init(void) +{ + ipc_clients = pcmk__strkey_table(NULL, NULL); + + pcmk__serve_based_ipc(&cib_ro, &cib_rw, &cib_shm, &cib_proxy_callbacks_ro, + &cib_proxy_callbacks_rw); + pcmk__serve_attrd_ipc(&attrd_ipcs, &attrd_proxy_callbacks); + pcmk__serve_fenced_ipc(&stonith_ipcs, &stonith_proxy_callbacks); + pcmk__serve_pacemakerd_ipc(&pacemakerd_ipcs, &pacemakerd_proxy_callbacks); + crmd_ipcs = pcmk__serve_controld_ipc(&crmd_proxy_callbacks); + if (crmd_ipcs == NULL) { + crm_err("Failed to create controller: exiting and inhibiting respawn"); + crm_warn("Verify pacemaker and pacemaker_remote are not both enabled"); + crm_exit(CRM_EX_FATAL); + } +} + +void +ipc_proxy_cleanup(void) +{ + if (ipc_providers) { + g_list_free(ipc_providers); + ipc_providers = NULL; + } + if (ipc_clients) { + g_hash_table_destroy(ipc_clients); + ipc_clients = NULL; + } + pcmk__stop_based_ipc(cib_ro, cib_rw, cib_shm); + qb_ipcs_destroy(attrd_ipcs); + qb_ipcs_destroy(stonith_ipcs); + qb_ipcs_destroy(pacemakerd_ipcs); + qb_ipcs_destroy(crmd_ipcs); + cib_ro = NULL; + cib_rw = NULL; + cib_shm = NULL; +} |