root/lib/common/messages.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__new_message_as
  2. pcmk__new_reply_as
  3. pcmk__register_handlers
  4. pcmk__process_request
  5. pcmk__reset_request

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <time.h>                       // time()
  14 #include <sys/types.h>
  15 
  16 #include <glib.h>
  17 #include <libxml/tree.h>
  18 
  19 #include <crm/common/xml.h>
  20 #include <crm/common/xml_internal.h>
  21 
  22 /*!
  23  * \internal
  24  * \brief Create message XML (for IPC or the cluster layer)
  25  *
  26  * Create standard, generic XML that can be used as a message sent via IPC or
  27  * the cluster layer. Currently, not all IPC and cluster layer messaging uses
  28  * this, but it should (eventually, keeping backward compatibility in mind).
  29  *
  30  * \param[in] origin            Name of function that called this one (required)
  31  * \param[in] server            Server whose protocol defines message semantics
  32  * \param[in] reply_to          If NULL, create message as a request with a
  33  *                              generated message ID, otherwise create message
  34  *                              as a reply to this message ID
  35  * \param[in] sender_system     Sender's subsystem (required; this is an
  36  *                              arbitrary string that may have meaning between
  37  *                              the sender and recipient)
  38  * \param[in] recipient_node    If not NULL, add as message's recipient node
  39  *                              (NULL typically indicates a broadcast message)
  40  * \param[in] recipient_system  If not NULL, add as message's recipient
  41  *                              subsystem (this is an arbitrary string that may
  42  *                              have meaning between the sender and recipient)
  43  * \param[in] task              Add as message's task (required)
  44  * \param[in] data              If not NULL, copy as message's data (callers
  45  *                              should not add attributes to the returned
  46  *                              message element, but instead pass any desired
  47  *                              information here, though this is not always
  48  *                              honored currently)
  49  *
  50  * \return Newly created message XML
  51  *
  52  * \note This function should usually not be called directly, but via the
  53  *       pcmk__new_message() wrapper.
  54  * \note The caller is responsible for freeing the return value using
  55  *       \c pcmk__xml_free().
  56  */
  57 xmlNode *
  58 pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server,
     /* [previous][next][first][last][top][bottom][index][help] */
  59                      const char *reply_to, const char *sender_system,
  60                      const char *recipient_node, const char *recipient_system,
  61                      const char *task, xmlNode *data)
  62 {
  63     static unsigned int message_counter = 0U;
  64 
  65     xmlNode *message = NULL;
  66     char *message_id = NULL;
  67     const char *subtype = PCMK__VALUE_RESPONSE;
  68 
  69     CRM_CHECK(!pcmk__str_empty(origin)
  70               && !pcmk__str_empty(sender_system)
  71               && !pcmk__str_empty(task),
  72               return NULL);
  73 
  74     if (reply_to == NULL) {
  75         subtype = PCMK__VALUE_REQUEST;
  76         message_id = crm_strdup_printf("%s-%s-%llu-%u", task, sender_system,
  77                                        (unsigned long long) time(NULL),
  78                                        message_counter++);
  79         reply_to = message_id;
  80     }
  81 
  82     message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE);
  83     pcmk__xe_set_props(message,
  84                        PCMK_XA_ORIGIN, origin,
  85                        PCMK__XA_T, pcmk__server_message_type(server),
  86                        PCMK__XA_SUBT, subtype,
  87                        PCMK_XA_VERSION, CRM_FEATURE_SET,
  88                        PCMK_XA_REFERENCE, reply_to,
  89                        PCMK__XA_CRM_SYS_FROM, sender_system,
  90                        PCMK__XA_CRM_HOST_TO, recipient_node,
  91                        PCMK__XA_CRM_SYS_TO, recipient_system,
  92                        PCMK__XA_CRM_TASK, task,
  93                        NULL);
  94     if (data != NULL) {
  95         xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML);
  96 
  97         pcmk__xml_copy(wrapper, data);
  98     }
  99     free(message_id);
 100     return message;
 101 }
 102 
 103 /*!
 104  * \internal
 105  * \brief Create a Pacemaker reply (for IPC or cluster layer)
 106  *
 107  * \param[in] origin            Name of function that called this one
 108  * \param[in] original_request  XML of request being replied to
 109  * \param[in] data              If not NULL, copy as reply's data (callers
 110  *                              should not add attributes to the returned
 111  *                              message element, but instead pass any desired
 112  *                              information here, though this is not always
 113  *                              honored currently)
 114  *
 115  * \return Newly created reply XML
 116  *
 117  * \note This function should not be called directly, but via the
 118  *       pcmk__new_reply() wrapper.
 119  * \note The caller is responsible for freeing the return value using
 120  *       \c pcmk__xml_free().
 121  */
 122 xmlNode *
 123 pcmk__new_reply_as(const char *origin, const xmlNode *original_request,
     /* [previous][next][first][last][top][bottom][index][help] */
 124                    xmlNode *data)
 125 {
 126     const char *message_type = crm_element_value(original_request, PCMK__XA_T);
 127     const char *host_from = crm_element_value(original_request, PCMK__XA_SRC);
 128     const char *sys_from = crm_element_value(original_request,
 129                                              PCMK__XA_CRM_SYS_FROM);
 130     const char *sys_to = crm_element_value(original_request,
 131                                            PCMK__XA_CRM_SYS_TO);
 132     const char *type = crm_element_value(original_request, PCMK__XA_SUBT);
 133     const char *operation = crm_element_value(original_request,
 134                                               PCMK__XA_CRM_TASK);
 135     const char *crm_msg_reference = crm_element_value(original_request,
 136                                                       PCMK_XA_REFERENCE);
 137     enum pcmk_ipc_server server = pcmk__parse_server(message_type);
 138 
 139     if (server == pcmk_ipc_unknown) {
 140         /* @COMPAT Not all requests currently specify a message type, so use a
 141          * default that preserves past behavior.
 142          *
 143          * @TODO Ensure all requests specify a message type, drop this check
 144          * after we no longer support rolling upgrades or Pacemaker Remote
 145          * connections involving versions before that.
 146          */
 147         server = pcmk_ipc_controld;
 148     }
 149 
 150     if (type == NULL) {
 151         crm_warn("Cannot reply to invalid message: No message type specified");
 152         return NULL;
 153     }
 154 
 155     if (strcmp(type, PCMK__VALUE_REQUEST) != 0) {
 156         /* Replies should only be generated for request messages, but it's possible
 157          * we expect replies to other messages right now so this can't be enforced.
 158          */
 159         crm_trace("Creating a reply for a non-request original message");
 160     }
 161 
 162     // Since this is a reply, we reverse the sender and recipient info
 163     return pcmk__new_message_as(origin, server, crm_msg_reference, sys_to,
 164                                 host_from, sys_from, operation, data);
 165 }
 166 
 167 /*!
 168  * \internal
 169  * \brief Register handlers for server commands
 170  *
 171  * \param[in] handlers  Array of handler functions for supported server commands
 172  *                      (the final entry must have a NULL command name, and if
 173  *                      it has a handler it will be used as the default handler
 174  *                      for unrecognized commands)
 175  *
 176  * \return Newly created hash table with commands and handlers
 177  * \note The caller is responsible for freeing the return value with
 178  *       g_hash_table_destroy().
 179  */
 180 GHashTable *
 181 pcmk__register_handlers(const pcmk__server_command_t handlers[])
     /* [previous][next][first][last][top][bottom][index][help] */
 182 {
 183     GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal);
 184 
 185     if (handlers != NULL) {
 186         int i;
 187 
 188         for (i = 0; handlers[i].command != NULL; ++i) {
 189             g_hash_table_insert(commands, (gpointer) handlers[i].command,
 190                                 handlers[i].handler);
 191         }
 192         if (handlers[i].handler != NULL) {
 193             // g_str_hash() can't handle NULL, so use empty string for default
 194             g_hash_table_insert(commands, (gpointer) "", handlers[i].handler);
 195         }
 196     }
 197     return commands;
 198 }
 199 
 200 /*!
 201  * \internal
 202  * \brief Process an incoming request
 203  *
 204  * \param[in,out] request   Request to process
 205  * \param[in]     handlers  Command table created by pcmk__register_handlers()
 206  *
 207  * \return XML to send as reply (or NULL if no reply is needed)
 208  */
 209 xmlNode *
 210 pcmk__process_request(pcmk__request_t *request, GHashTable *handlers)
     /* [previous][next][first][last][top][bottom][index][help] */
 211 {
 212     xmlNode *(*handler)(pcmk__request_t *request) = NULL;
 213 
 214     CRM_CHECK((request != NULL) && (request->op != NULL) && (handlers != NULL),
 215               return NULL);
 216 
 217     if (pcmk_is_set(request->flags, pcmk__request_sync)
 218         && (request->ipc_client != NULL)) {
 219         CRM_CHECK(request->ipc_client->request_id == request->ipc_id,
 220                   return NULL);
 221     }
 222 
 223     handler = g_hash_table_lookup(handlers, request->op);
 224     if (handler == NULL) {
 225         handler = g_hash_table_lookup(handlers, ""); // Default handler
 226         if (handler == NULL) {
 227             crm_info("Ignoring %s request from %s %s with no handler",
 228                      request->op, pcmk__request_origin_type(request),
 229                      pcmk__request_origin(request));
 230             return NULL;
 231         }
 232     }
 233 
 234     return (*handler)(request);
 235 }
 236 
 237 /*!
 238  * \internal
 239  * \brief Free memory used within a request (but not the request itself)
 240  *
 241  * \param[in,out] request  Request to reset
 242  */
 243 void
 244 pcmk__reset_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 245 {
 246     free(request->op);
 247     request->op = NULL;
 248 
 249     pcmk__reset_result(&(request->result));
 250 }

/* [previous][next][first][last][top][bottom][index][help] */