1 /*
2 * Copyright 2022-2026 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 General Public License version 2
7 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <inttypes.h> // PRIu32
13 #include <stdbool.h>
14
15 #include <glib.h>
16
17 #include <crm/cluster/internal.h> // pcmk__get_node()
18 #include <crm/common/xml.h>
19
20 #include "pacemaker-attrd.h"
21
22 int minimum_protocol_version = -1;
23
24 static GHashTable *attrd_handlers = NULL;
25
26 static bool
27 is_sync_point_attr(xmlAttrPtr attr, void *data)
28 {
29 return pcmk__str_eq((const char *) attr->name, PCMK__XA_ATTR_SYNC_POINT, pcmk__str_none);
30 }
31
32 static int
33 remove_sync_point_attribute(xmlNode *xml, void *data)
34 {
35 pcmk__xe_remove_matching_attrs(xml, false, is_sync_point_attr, NULL);
36 pcmk__xe_foreach_child(xml, PCMK_XE_OP, remove_sync_point_attribute, NULL);
37 return pcmk_rc_ok;
38 }
39
40 /* Sync points on a multi-update IPC message to an attrd too old to support
41 * multi-update messages won't work. Strip the sync point attribute off here
42 * so we don't pretend to support this situation and instead ACK the client
43 * immediately.
44 */
45 static void
46 remove_unsupported_sync_points(pcmk__request_t *request)
47 {
48 if (request->xml->children != NULL && !ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version) &&
49 attrd_request_has_sync_point(request->xml)) {
50 pcmk__warn("Ignoring sync point in request from %s because not all "
51 "nodes support it",
52 pcmk__request_origin(request));
53 remove_sync_point_attribute(request->xml, NULL);
54 }
55 }
56
57 static xmlNode *
58 handle_unknown_request(pcmk__request_t *request)
59 {
60 pcmk__err("Unknown %s request %s from %s %s",
61 (request->ipc_client != NULL) ? "IPC" : "CPG",
62 request->op, pcmk__request_origin_type(request),
63 pcmk__request_origin(request));
64 pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
65 "Unknown request type '%s' (bug?)",
66 pcmk__s(request->op, ""));
67 return NULL;
68 }
69
70 static xmlNode *
71 handle_clear_failure_request(pcmk__request_t *request)
72 {
73 if (request->peer != NULL) {
74 /* It is not currently possible to receive this as a peer command,
75 * but will be, if we one day enable propagating this operation.
76 */
77 attrd_peer_clear_failure(request);
78 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
79 return NULL;
80 }
81
82 remove_unsupported_sync_points(request);
83
84 if (attrd_request_has_sync_point(request->xml)) {
85 /* If this client supplied a sync point it wants to wait for, add it to
86 * the wait list. Clients on this list will not receive an ACK until
87 * their sync point is hit which will result in the client stalled there
88 * until it receives a response.
89 *
90 * All other clients will receive the expected response as normal.
91 */
92 attrd_add_client_to_waitlist(request);
93
94 } else {
95 /* If the client doesn't want to wait for a sync point, go ahead and
96 * send the ACK immediately. Otherwise, we'll send the ACK when the
97 * appropriate sync point is reached.
98 */
99 attrd_send_ack(request->ipc_client, request->ipc_id,
100 request->ipc_flags);
101 }
102
103 attrd_client_clear_failure(request);
104 return NULL;
105 }
106
107 static xmlNode *
108 handle_confirm_request(pcmk__request_t *request)
109 {
110 int callid = 0;
111
112 if (request->ipc_client != NULL) {
113 return handle_unknown_request(request);
114 }
115
116 pcmk__debug("Received confirmation from %s", request->peer);
117
118 if (pcmk__xe_get_int(request->xml, PCMK__XA_CALL_ID,
119 &callid) != pcmk_rc_ok) {
120 pcmk__set_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
121 "Could not get callid from XML");
122 } else {
123 attrd_handle_confirmation(callid, request->peer);
124 }
125
126 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
127 return NULL;
128 }
129
130 static xmlNode *
131 handle_query_request(pcmk__request_t *request)
132 {
133 if (request->peer != NULL) {
134 return handle_unknown_request(request);
135 }
136
137 return attrd_client_query(request);
138 }
139
140 static xmlNode *
141 handle_remove_request(pcmk__request_t *request)
142 {
143 const char *host = NULL;
144 bool reap = false;
145
146 if (request->ipc_client != NULL) {
147 attrd_client_peer_remove(request);
148 return NULL;
149 }
150
151 host = pcmk__xe_get(request->xml, PCMK__XA_ATTR_HOST);
152
153 if (pcmk__xe_get_bool(request->xml, PCMK__XA_REAP,
154 &reap) != pcmk_rc_ok) {
155 reap = true; // Default to true for backward compatibility
156 }
157
158 attrd_peer_remove(host, reap, request->peer);
159 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
160 return NULL;
161 }
162
163 static xmlNode *
164 handle_refresh_request(pcmk__request_t *request)
165 {
166 if (request->peer != NULL) {
167 return handle_unknown_request(request);
168 }
169
170 attrd_client_refresh(request);
171 return NULL;
172 }
173
174 static xmlNode *
175 handle_sync_response_request(pcmk__request_t *request)
176 {
177 pcmk__node_status_t *peer = NULL;
178 bool peer_won = false;
179
180 if (request->ipc_client != NULL) {
181 return handle_unknown_request(request);
182 }
183
184 peer = pcmk__get_node(0, request->peer, NULL,
185 pcmk__node_search_cluster_member);
186 peer_won = attrd_check_for_new_writer(peer, request->xml);
187
188 if (!pcmk__str_eq(peer->name, attrd_cluster->priv->node_name,
189 pcmk__str_casei)) {
190 attrd_peer_sync_response(peer, peer_won, request->xml);
191 }
192
193 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
194 return NULL;
195 }
196
197 static xmlNode *
198 handle_update_request(pcmk__request_t *request)
199 {
200 if (request->peer != NULL) {
201 const char *host = pcmk__xe_get(request->xml, PCMK__XA_ATTR_HOST);
202 pcmk__node_status_t *peer =
203 pcmk__get_node(0, request->peer, NULL,
204 pcmk__node_search_cluster_member);
205
206 attrd_peer_update(peer, request->xml, host, false);
207 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
208 return NULL;
209 }
210
211 remove_unsupported_sync_points(request);
212
213 if (attrd_request_has_sync_point(request->xml)) {
214 /* If this client supplied a sync point it wants to wait for, add it to
215 * the wait list. Clients on this list will not receive an ACK until
216 * their sync point is hit which will result in the client stalled there
217 * until it receives a response.
218 *
219 * All other clients will receive the expected response as normal.
220 */
221 attrd_add_client_to_waitlist(request);
222
223 } else {
224 /* If the client doesn't want to wait for a sync point, go ahead and
225 * send the ACK immediately. Otherwise, we'll send the ACK when the
226 * appropriate sync point is reached.
227 *
228 * In the normal case, attrd_client_update() can be called recursively,
229 * which makes where to send the ACK tricky. Doing it here ensures that
230 * the client only ever receives one.
231 */
232 attrd_send_ack(request->ipc_client, request->ipc_id,
233 request->flags|crm_ipc_client_response);
234 }
235
236 attrd_client_update(request);
237 return NULL;
238 }
239
240 static void
241 attrd_register_handlers(void)
242 {
243 pcmk__server_command_t handlers[] = {
244 { PCMK__ATTRD_CMD_CLEAR_FAILURE, handle_clear_failure_request },
245 { PCMK__ATTRD_CMD_CONFIRM, handle_confirm_request },
246 { PCMK__ATTRD_CMD_PEER_REMOVE, handle_remove_request },
247 { PCMK__ATTRD_CMD_QUERY, handle_query_request },
248 { PCMK__ATTRD_CMD_REFRESH, handle_refresh_request },
249 { PCMK__ATTRD_CMD_SYNC_RESPONSE, handle_sync_response_request },
250 { PCMK__ATTRD_CMD_UPDATE, handle_update_request },
251 { PCMK__ATTRD_CMD_UPDATE_DELAY, handle_update_request },
252 { PCMK__ATTRD_CMD_UPDATE_BOTH, handle_update_request },
253 { NULL, handle_unknown_request },
254 };
255
256 attrd_handlers = pcmk__register_handlers(handlers);
257 }
258
259 void
260 attrd_unregister_handlers(void)
261 {
|
CID (unavailable; MK=950be591a1fc61aaa6f7d0695c9b3831) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(1) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(2) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
262 g_clear_pointer(&attrd_handlers, g_hash_table_destroy);
263 }
264
265 void
266 attrd_handle_request(pcmk__request_t *request)
267 {
268 xmlNode *reply = NULL;
269 char *log_msg = NULL;
270 const char *exec_status_s = NULL;
271 const char *reason = NULL;
272
273 if (attrd_handlers == NULL) {
274 attrd_register_handlers();
275 }
276
277 reply = pcmk__process_request(request, attrd_handlers);
278
279 if (reply != NULL) {
280 pcmk__log_xml_trace(reply, "Reply");
281
282 if (request->ipc_client != NULL) {
283 pcmk__ipc_send_xml(request->ipc_client, request->ipc_id, reply,
284 request->ipc_flags);
285 } else {
286 pcmk__err("Not sending CPG reply to client");
287 }
288
289 pcmk__xml_free(reply);
290 }
291
292 exec_status_s = pcmk_exec_status_str(request->result.execution_status);
293 reason = request->result.exit_reason;
294 log_msg = pcmk__assert_asprintf("Processed %s request from %s %s: %s%s%s%s",
295 request->op,
296 pcmk__request_origin_type(request),
297 pcmk__request_origin(request),
298 exec_status_s,
299 (reason == NULL)? "" : " (",
300 pcmk__s(reason, ""),
301 (reason == NULL)? "" : ")");
302
303 if (!pcmk__result_ok(&request->result)) {
304 pcmk__warn("%s", log_msg);
305 } else {
306 pcmk__debug("%s", log_msg);
307 }
308
309 free(log_msg);
310 pcmk__reset_request(request);
311 }
312
313 /*!
314 \internal
315 \brief Send or broadcast private attribute for local node with protocol version
316 */
317 void
318 attrd_send_protocol(const pcmk__node_status_t *peer)
319 {
320 xmlNode *attrd_op = pcmk__xe_create(NULL, __func__);
321
322 pcmk__xe_set(attrd_op, PCMK__XA_T, PCMK__VALUE_ATTRD);
323 pcmk__xe_set(attrd_op, PCMK__XA_SRC, crm_system_name);
324 pcmk__xe_set(attrd_op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
325 pcmk__xe_set(attrd_op, PCMK__XA_ATTR_NAME, CRM_ATTR_PROTOCOL);
326 pcmk__xe_set(attrd_op, PCMK__XA_ATTR_VALUE, ATTRD_PROTOCOL_VERSION);
327 pcmk__xe_set_int(attrd_op, PCMK__XA_ATTR_IS_PRIVATE, 1);
328 pcmk__xe_set(attrd_op, PCMK__XA_ATTR_HOST, attrd_cluster->priv->node_name);
329 pcmk__xe_set(attrd_op, PCMK__XA_ATTR_HOST_ID,
330 attrd_cluster->priv->node_xml_id);
331
332 if (peer == NULL) {
333 pcmk__debug("Broadcasting attrd protocol version "
334 ATTRD_PROTOCOL_VERSION " for node %s[%" PRIu32 "]",
335 pcmk__s(attrd_cluster->priv->node_name, "unknown"),
336 attrd_cluster->priv->node_id);
337
338 } else {
339 pcmk__debug("Sending attrd protocol version " ATTRD_PROTOCOL_VERSION " "
340 "for node %s[%" PRIu32 "] to node %s[%" PRIu32 "]",
341 pcmk__s(attrd_cluster->priv->node_name, "unknown"),
342 attrd_cluster->priv->node_id,
343 pcmk__s(peer->name, "unknown"), peer->cluster_layer_id);
344 }
345
346 attrd_send_message(peer, attrd_op, false); /* ends up at attrd_peer_message() */
347
348 pcmk__xml_free(attrd_op);
349 }
350
351 gboolean
352 attrd_send_message(const pcmk__node_status_t *node, xmlNode *data, bool confirm)
353 {
354 const char *op = pcmk__xe_get(data, PCMK_XA_TASK);
355
356 pcmk__xe_set(data, PCMK__XA_T, PCMK__VALUE_ATTRD);
357 pcmk__xe_set(data, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
358
359 /* Request a confirmation from the destination peer node (which could
360 * be all if node is NULL) that the message has been received and
361 * acted upon.
362 */
363 if (!pcmk__str_eq(op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
364 pcmk__xe_set_bool(data, PCMK__XA_CONFIRM, confirm);
365 }
366
367 attrd_xml_add_writer(data);
368 return pcmk__cluster_send_message(node, pcmk_ipc_attrd, data);
369 }
370