diff --git a/include/crm/common/messages_internal.h b/include/crm/common/messages_internal.h index d385ca13bb..c7447591cb 100644 --- a/include/crm/common/messages_internal.h +++ b/include/crm/common/messages_internal.h @@ -1,78 +1,87 @@ /* * Copyright 2018-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_MESSAGES_INTERNAL__H #define PCMK__CRM_COMMON_MESSAGES_INTERNAL__H #include // uint32_t #include // xmlNode #include // pcmk__client_t #include // pcmk__action_result_t // Server request (whether from an IPC client or cluster peer) typedef struct { // If request is from an IPC client pcmk__client_t *client; // IPC client (NULL if not via IPC) uint32_t id; // IPC message ID uint32_t flags; // IPC message flags // If message is from a cluster peer const char *peer; // Peer name (NULL if not via cluster) // Common information regardless of origin xmlNode *xml; // Request XML int call_options; // Call options set on request pcmk__action_result_t result; // Where to store operation result } pcmk__request_t; +// Type for mapping a server command to a handler +typedef struct { + const char *command; + xmlNode *(*handler)(pcmk__request_t *request); +} pcmk__server_command_t; + const char *pcmk__message_name(const char *name); +GHashTable *pcmk__register_handlers(pcmk__server_command_t handlers[]); +xmlNode *pcmk__process_request(pcmk__request_t *request, const char *op, + bool sync, GHashTable *handlers); /*! * \internal * \brief Get a loggable description of a request's origin * * \param[in] request * * \return "peer" if request was via CPG, "client" if via IPC, or "originator" * if unknown */ static inline const char * pcmk__request_origin_type(pcmk__request_t *request) { if ((request != NULL) && (request->client != NULL)) { return "client"; } else if ((request != NULL) && (request->peer != NULL)) { return "peer"; } else { return "originator"; } } /*! * \internal * \brief Get a loggable name for a request's origin * * \param[in] request * * \return Peer name if request was via CPG, client name if via IPC, or * "(unspecified)" if unknown */ static inline const char * pcmk__request_origin(pcmk__request_t *request) { if ((request != NULL) && (request->client != NULL)) { return pcmk__client_name(request->client); } else if ((request != NULL) && (request->peer != NULL)) { return request->peer; } else { return "(unspecified)"; } } #endif // PCMK__CRM_COMMON_MESSAGES_INTERNAL__H diff --git a/lib/common/messages.c b/lib/common/messages.c index 27e19d4dee..1e056244a7 100644 --- a/lib/common/messages.c +++ b/lib/common/messages.c @@ -1,208 +1,282 @@ /* - * Copyright 2004-2020 the Pacemaker project contributors + * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include /*! * \brief Create a Pacemaker request (for IPC or cluster layer) * * \param[in] task What to set as the request's task * \param[in] msg_data What to add as the request's data contents * \param[in] host_to What to set as the request's destination host * \param[in] sys_to What to set as the request's destination system * \param[in] sys_from If not NULL, set as request's origin system * \param[in] uuid_from If not NULL, use in request's origin system * \param[in] origin Name of function that called this one * * \return XML of new request * * \note One of sys_from or uuid_from must be non-NULL * \note This function should not be called directly, but via the * create_request() wrapper. * \note The caller is responsible for freeing the result using free_xml(). */ xmlNode * create_request_adv(const char *task, xmlNode * msg_data, const char *host_to, const char *sys_to, const char *sys_from, const char *uuid_from, const char *origin) { static uint ref_counter = 0; char *true_from = NULL; xmlNode *request = NULL; char *reference = crm_strdup_printf("%s-%s-%lld-%u", (task? task : "_empty_"), (sys_from? sys_from : "_empty_"), (long long) time(NULL), ref_counter++); if (uuid_from != NULL) { true_from = crm_strdup_printf("%s_%s", uuid_from, (sys_from? sys_from : "none")); } else if (sys_from != NULL) { true_from = strdup(sys_from); } else { crm_err("Cannot create IPC request: No originating system specified"); } // host_from will get set for us if necessary by the controller when routed request = create_xml_node(NULL, __func__); crm_xml_add(request, F_CRM_ORIGIN, origin); crm_xml_add(request, F_TYPE, T_CRM); crm_xml_add(request, F_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(request, F_CRM_MSG_TYPE, XML_ATTR_REQUEST); crm_xml_add(request, F_CRM_REFERENCE, reference); crm_xml_add(request, F_CRM_TASK, task); crm_xml_add(request, F_CRM_SYS_TO, sys_to); crm_xml_add(request, F_CRM_SYS_FROM, true_from); /* HOSTTO will be ignored if it is to the DC anyway. */ if (host_to != NULL && strlen(host_to) > 0) { crm_xml_add(request, F_CRM_HOST_TO, host_to); } if (msg_data != NULL) { add_message_xml(request, F_CRM_DATA, msg_data); } free(reference); free(true_from); return request; } /*! * \brief Create a Pacemaker reply (for IPC or cluster layer) * * \param[in] original_request XML of request this is a reply to * \param[in] xml_response_data XML to copy as data section of reply * \param[in] origin Name of function that called this one * * \return XML of new reply * * \note This function should not be called directly, but via the * create_reply() wrapper. * \note The caller is responsible for freeing the result using free_xml(). */ xmlNode * create_reply_adv(xmlNode *original_request, xmlNode *xml_response_data, const char *origin) { xmlNode *reply = NULL; const char *host_from = crm_element_value(original_request, F_CRM_HOST_FROM); const char *sys_from = crm_element_value(original_request, F_CRM_SYS_FROM); const char *sys_to = crm_element_value(original_request, F_CRM_SYS_TO); const char *type = crm_element_value(original_request, F_CRM_MSG_TYPE); const char *operation = crm_element_value(original_request, F_CRM_TASK); const char *crm_msg_reference = crm_element_value(original_request, F_CRM_REFERENCE); if (type == NULL) { crm_err("Cannot create new_message, no message type in original message"); CRM_ASSERT(type != NULL); return NULL; #if 0 } else if (strcasecmp(XML_ATTR_REQUEST, type) != 0) { crm_err("Cannot create new_message, original message was not a request"); return NULL; #endif } reply = create_xml_node(NULL, __func__); if (reply == NULL) { crm_err("Cannot create new_message, malloc failed"); return NULL; } crm_xml_add(reply, F_CRM_ORIGIN, origin); crm_xml_add(reply, F_TYPE, T_CRM); crm_xml_add(reply, F_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(reply, F_CRM_MSG_TYPE, XML_ATTR_RESPONSE); crm_xml_add(reply, F_CRM_REFERENCE, crm_msg_reference); crm_xml_add(reply, F_CRM_TASK, operation); /* since this is a reply, we reverse the from and to */ crm_xml_add(reply, F_CRM_SYS_TO, sys_from); crm_xml_add(reply, F_CRM_SYS_FROM, sys_to); /* HOSTTO will be ignored if it is to the DC anyway. */ if (host_from != NULL && strlen(host_from) > 0) { crm_xml_add(reply, F_CRM_HOST_TO, host_from); } if (xml_response_data != NULL) { add_message_xml(reply, F_CRM_DATA, xml_response_data); } return reply; } xmlNode * get_message_xml(xmlNode *msg, const char *field) { xmlNode *tmp = first_named_child(msg, field); return pcmk__xml_first_child(tmp); } gboolean add_message_xml(xmlNode *msg, const char *field, xmlNode *xml) { xmlNode *holder = create_xml_node(msg, field); add_node_copy(holder, xml); return TRUE; } /*! * \brief Get name to be used as identifier for cluster messages * * \param[in] name Actual system name to check * * \return Non-NULL cluster message identifier corresponding to name * * \note The Pacemaker daemons were renamed in version 2.0.0, but the old names * must continue to be used as the identifier for cluster messages, so * that mixed-version clusters are possible during a rolling upgrade. */ const char * pcmk__message_name(const char *name) { if (name == NULL) { return "unknown"; } else if (!strcmp(name, "pacemaker-attrd")) { return "attrd"; } else if (!strcmp(name, "pacemaker-based")) { return CRM_SYSTEM_CIB; } else if (!strcmp(name, "pacemaker-controld")) { return CRM_SYSTEM_CRMD; } else if (!strcmp(name, "pacemaker-execd")) { return CRM_SYSTEM_LRMD; } else if (!strcmp(name, "pacemaker-fenced")) { return "stonith-ng"; } else if (!strcmp(name, "pacemaker-schedulerd")) { return CRM_SYSTEM_PENGINE; } else { return name; } } + +/*! + * \internal + * \brief Register handlers for server commands + * + * \param[in] handlers Array of handler functions for supported server commands + * (the final entry must have a NULL command name, and if + * it has a handler it will be used as the default handler + * for unrecognized commands) + * + * \return Newly created hash table with commands and handlers + * \note The caller is responsible for freeing the return value with + * g_hash_table_destroy(). + */ +GHashTable * +pcmk__register_handlers(pcmk__server_command_t *handlers) +{ + GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal); + + if (handlers != NULL) { + int i; + + for (i = 0; handlers[i].command != NULL; ++i) { + g_hash_table_insert(commands, (gpointer) handlers[i].command, + handlers[i].handler); + } + if (handlers[i].handler != NULL) { + // g_str_hash() can't handle NULL, so use empty string for default + g_hash_table_insert(commands, (gpointer) "", handlers[i].handler); + } + } + return commands; +} + +/*! + * \internal + * \brief Process an incoming request + * + * \param[in] request Request to process + * \param[in] op Operation type of request + * \param[in] sync Whether request is synchronous + * \param[in] handlers Command table created by pcmk__register_handlers() + * + * \return XML to send as reply (or NULL if no reply is needed) + * \todo It would be nice if we could pull \p op from \p request->xml and + * \p sync from \p request->call_options, but the relevant identifiers are + * not currently standardized across daemons. + */ +xmlNode * +pcmk__process_request(pcmk__request_t *request, const char *op, + bool sync, GHashTable *handlers) +{ + xmlNode *(*handler)(pcmk__request_t *request) = NULL; + + CRM_CHECK((request != NULL) && (op != NULL) && (handlers != NULL), + return NULL); + + if (sync && (request->client != NULL)) { + CRM_CHECK(request->client->request_id == request->id, return NULL); + } + + handler = g_hash_table_lookup(handlers, op); + if (handler == NULL) { + handler = g_hash_table_lookup(handlers, ""); // Default handler + if (handler == NULL) { + crm_info("Ignoring %s request from %s %s with no handler", + op, pcmk__request_origin_type(request), + pcmk__request_origin(request)); + return NULL; + } + } + + return (*handler)(request); +}