Line data Source code
1 : /*
2 : * Copyright 2011-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 : #ifndef _GNU_SOURCE
11 : # define _GNU_SOURCE
12 : #endif
13 :
14 : #include <crm_internal.h>
15 :
16 : #include <stdio.h>
17 :
18 : #include <crm/crm.h>
19 : #include <crm/common/attrs_internal.h>
20 : #include <crm/common/ipc.h>
21 : #include <crm/common/ipc_attrd_internal.h>
22 : #include <crm/common/xml.h>
23 : #include "crmcommon_private.h"
24 :
25 : static void
26 0 : set_pairs_data(pcmk__attrd_api_reply_t *data, xmlNode *msg_data)
27 : {
28 0 : const char *name = NULL;
29 : pcmk__attrd_query_pair_t *pair;
30 :
31 0 : name = crm_element_value(msg_data, PCMK__XA_ATTR_NAME);
32 :
33 0 : for (xmlNode *node = pcmk__xe_first_child(msg_data, PCMK_XE_NODE, NULL,
34 : NULL);
35 0 : node != NULL; node = pcmk__xe_next_same(node)) {
36 :
37 0 : pair = pcmk__assert_alloc(1, sizeof(pcmk__attrd_query_pair_t));
38 :
39 0 : pair->node = crm_element_value(node, PCMK__XA_ATTR_HOST);
40 0 : pair->name = name;
41 0 : pair->value = crm_element_value(node, PCMK__XA_ATTR_VALUE);
42 0 : data->data.pairs = g_list_prepend(data->data.pairs, pair);
43 : }
44 0 : }
45 :
46 : static bool
47 0 : reply_expected(pcmk_ipc_api_t *api, const xmlNode *request)
48 : {
49 0 : const char *command = crm_element_value(request, PCMK_XA_TASK);
50 :
51 0 : return pcmk__str_any_of(command,
52 : PCMK__ATTRD_CMD_CLEAR_FAILURE,
53 : PCMK__ATTRD_CMD_QUERY,
54 : PCMK__ATTRD_CMD_REFRESH,
55 : PCMK__ATTRD_CMD_UPDATE,
56 : PCMK__ATTRD_CMD_UPDATE_BOTH,
57 : PCMK__ATTRD_CMD_UPDATE_DELAY,
58 : NULL);
59 : }
60 :
61 : static bool
62 0 : dispatch(pcmk_ipc_api_t *api, xmlNode *reply)
63 : {
64 0 : const char *value = NULL;
65 0 : crm_exit_t status = CRM_EX_OK;
66 :
67 0 : pcmk__attrd_api_reply_t reply_data = {
68 : pcmk__attrd_reply_unknown
69 : };
70 :
71 0 : if (pcmk__xe_is(reply, PCMK__XE_ACK)) {
72 0 : return false;
73 : }
74 :
75 : /* Do some basic validation of the reply */
76 0 : value = crm_element_value(reply, PCMK__XA_T);
77 0 : if (pcmk__str_empty(value)
78 0 : || !pcmk__str_eq(value, PCMK__VALUE_ATTRD, pcmk__str_none)) {
79 0 : crm_info("Unrecognizable message from attribute manager: "
80 : "message type '%s' not '" PCMK__VALUE_ATTRD "'",
81 : pcmk__s(value, ""));
82 0 : status = CRM_EX_PROTOCOL;
83 0 : goto done;
84 : }
85 :
86 0 : value = crm_element_value(reply, PCMK__XA_SUBT);
87 :
88 : /* Only the query command gets a reply for now. NULL counts as query for
89 : * backward compatibility with attribute managers <2.1.3 that didn't set it.
90 : */
91 0 : if (pcmk__str_eq(value, PCMK__ATTRD_CMD_QUERY, pcmk__str_null_matches)) {
92 0 : if (!xmlHasProp(reply, (pcmkXmlStr) PCMK__XA_ATTR_NAME)) {
93 0 : status = ENXIO; // Most likely, the attribute doesn't exist
94 0 : goto done;
95 : }
96 0 : reply_data.reply_type = pcmk__attrd_reply_query;
97 0 : set_pairs_data(&reply_data, reply);
98 :
99 : } else {
100 0 : crm_info("Unrecognizable message from attribute manager: "
101 : "message subtype '%s' unknown", pcmk__s(value, ""));
102 0 : status = CRM_EX_PROTOCOL;
103 0 : goto done;
104 : }
105 :
106 0 : done:
107 0 : pcmk__call_ipc_callback(api, pcmk_ipc_event_reply, status, &reply_data);
108 :
109 : /* Free any reply data that was allocated */
110 0 : if (reply_data.data.pairs) {
111 0 : g_list_free_full(reply_data.data.pairs, free);
112 : }
113 :
114 0 : return false;
115 : }
116 :
117 : pcmk__ipc_methods_t *
118 0 : pcmk__attrd_api_methods(void)
119 : {
120 0 : pcmk__ipc_methods_t *cmds = calloc(1, sizeof(pcmk__ipc_methods_t));
121 :
122 0 : if (cmds != NULL) {
123 0 : cmds->new_data = NULL;
124 0 : cmds->free_data = NULL;
125 0 : cmds->post_connect = NULL;
126 0 : cmds->reply_expected = reply_expected;
127 0 : cmds->dispatch = dispatch;
128 : }
129 0 : return cmds;
130 : }
131 :
132 : /*!
133 : * \internal
134 : * \brief Create a generic pacemaker-attrd operation
135 : *
136 : * \param[in] user_name If not NULL, ACL user to set for operation
137 : *
138 : * \return XML of pacemaker-attrd operation
139 : */
140 : static xmlNode *
141 0 : create_attrd_op(const char *user_name)
142 : {
143 0 : xmlNode *attrd_op = pcmk__xe_create(NULL, __func__);
144 :
145 0 : crm_xml_add(attrd_op, PCMK__XA_T, PCMK__VALUE_ATTRD);
146 0 : crm_xml_add(attrd_op, PCMK__XA_SRC, pcmk__s(crm_system_name, "unknown"));
147 0 : crm_xml_add(attrd_op, PCMK__XA_ATTR_USER, user_name);
148 :
149 0 : return attrd_op;
150 : }
151 :
152 : static int
153 0 : connect_and_send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request)
154 : {
155 0 : int rc = pcmk_rc_ok;
156 0 : bool created_api = false;
157 :
158 0 : if (api == NULL) {
159 0 : rc = pcmk_new_ipc_api(&api, pcmk_ipc_attrd);
160 0 : if (rc != pcmk_rc_ok) {
161 0 : return rc;
162 : }
163 0 : created_api = true;
164 : }
165 :
166 0 : rc = pcmk__connect_ipc(api, pcmk_ipc_dispatch_sync, 5);
167 0 : if (rc == pcmk_rc_ok) {
168 0 : rc = pcmk__send_ipc_request(api, request);
169 : }
170 :
171 0 : if (created_api) {
172 0 : pcmk_free_ipc_api(api);
173 : }
174 0 : return rc;
175 : }
176 :
177 : int
178 0 : pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node,
179 : const char *resource, const char *operation,
180 : const char *interval_spec, const char *user_name,
181 : uint32_t options)
182 : {
183 0 : int rc = pcmk_rc_ok;
184 0 : xmlNode *request = create_attrd_op(user_name);
185 0 : const char *interval_desc = NULL;
186 0 : const char *op_desc = NULL;
187 0 : const char *target = pcmk__node_attr_target(node);
188 :
189 0 : if (target != NULL) {
190 0 : node = target;
191 : }
192 :
193 0 : if (operation) {
194 0 : interval_desc = pcmk__s(interval_spec, "nonrecurring");
195 0 : op_desc = operation;
196 : } else {
197 0 : interval_desc = "all";
198 0 : op_desc = "operations";
199 : }
200 0 : crm_debug("Asking %s to clear failure of %s %s for %s on %s",
201 : pcmk_ipc_name(api, true), interval_desc, op_desc,
202 : pcmk__s(resource, "all resources"), pcmk__s(node, "all nodes"));
203 :
204 0 : crm_xml_add(request, PCMK_XA_TASK, PCMK__ATTRD_CMD_CLEAR_FAILURE);
205 0 : pcmk__xe_add_node(request, node, 0);
206 0 : crm_xml_add(request, PCMK__XA_ATTR_RESOURCE, resource);
207 0 : crm_xml_add(request, PCMK__XA_ATTR_CLEAR_OPERATION, operation);
208 0 : crm_xml_add(request, PCMK__XA_ATTR_CLEAR_INTERVAL, interval_spec);
209 0 : crm_xml_add_int(request, PCMK__XA_ATTR_IS_REMOTE,
210 0 : pcmk_is_set(options, pcmk__node_attr_remote));
211 :
212 0 : rc = connect_and_send_attrd_request(api, request);
213 :
214 0 : free_xml(request);
215 0 : return rc;
216 : }
217 :
218 : int
219 0 : pcmk__attrd_api_delete(pcmk_ipc_api_t *api, const char *node, const char *name,
220 : uint32_t options)
221 : {
222 0 : const char *target = NULL;
223 :
224 0 : if (name == NULL) {
225 0 : return EINVAL;
226 : }
227 :
228 0 : target = pcmk__node_attr_target(node);
229 :
230 0 : if (target != NULL) {
231 0 : node = target;
232 : }
233 :
234 : /* Make sure the right update option is set. */
235 0 : options &= ~pcmk__node_attr_delay;
236 0 : options |= pcmk__node_attr_value;
237 :
238 0 : return pcmk__attrd_api_update(api, node, name, NULL, NULL, NULL, NULL, options);
239 : }
240 :
241 : int
242 0 : pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap)
243 : {
244 0 : int rc = pcmk_rc_ok;
245 0 : xmlNode *request = NULL;
246 0 : const char *target = pcmk__node_attr_target(node);
247 :
248 0 : if (target != NULL) {
249 0 : node = target;
250 : }
251 :
252 0 : crm_debug("Asking %s to purge transient attributes%s for %s",
253 : pcmk_ipc_name(api, true),
254 : (reap? " and node cache entries" : ""),
255 : pcmk__s(node, "local node"));
256 :
257 0 : request = create_attrd_op(NULL);
258 :
259 0 : crm_xml_add(request, PCMK_XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
260 0 : pcmk__xe_set_bool_attr(request, PCMK__XA_REAP, reap);
261 0 : pcmk__xe_add_node(request, node, 0);
262 :
263 0 : rc = connect_and_send_attrd_request(api, request);
264 :
265 0 : free_xml(request);
266 0 : return rc;
267 : }
268 :
269 : int
270 0 : pcmk__attrd_api_query(pcmk_ipc_api_t *api, const char *node, const char *name,
271 : uint32_t options)
272 : {
273 0 : int rc = pcmk_rc_ok;
274 0 : xmlNode *request = NULL;
275 0 : const char *target = NULL;
276 :
277 0 : if (name == NULL) {
278 0 : return EINVAL;
279 : }
280 :
281 0 : if (pcmk_is_set(options, pcmk__node_attr_query_all)) {
282 0 : node = NULL;
283 : } else {
284 0 : target = pcmk__node_attr_target(node);
285 :
286 0 : if (target != NULL) {
287 0 : node = target;
288 : }
289 : }
290 :
291 0 : crm_debug("Querying %s for value of '%s'%s%s",
292 : pcmk_ipc_name(api, true), name,
293 : ((node == NULL)? "" : " on "), pcmk__s(node, ""));
294 :
295 0 : request = create_attrd_op(NULL);
296 :
297 0 : crm_xml_add(request, PCMK__XA_ATTR_NAME, name);
298 0 : crm_xml_add(request, PCMK_XA_TASK, PCMK__ATTRD_CMD_QUERY);
299 0 : pcmk__xe_add_node(request, node, 0);
300 :
301 0 : rc = connect_and_send_attrd_request(api, request);
302 0 : free_xml(request);
303 0 : return rc;
304 : }
305 :
306 : int
307 0 : pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node)
308 : {
309 0 : int rc = pcmk_rc_ok;
310 0 : xmlNode *request = NULL;
311 0 : const char *target = pcmk__node_attr_target(node);
312 :
313 0 : if (target != NULL) {
314 0 : node = target;
315 : }
316 :
317 0 : crm_debug("Asking %s to write all transient attributes for %s to CIB",
318 : pcmk_ipc_name(api, true), pcmk__s(node, "local node"));
319 :
320 0 : request = create_attrd_op(NULL);
321 :
322 0 : crm_xml_add(request, PCMK_XA_TASK, PCMK__ATTRD_CMD_REFRESH);
323 0 : pcmk__xe_add_node(request, node, 0);
324 :
325 0 : rc = connect_and_send_attrd_request(api, request);
326 :
327 0 : free_xml(request);
328 0 : return rc;
329 : }
330 :
331 : static void
332 0 : add_op_attr(xmlNode *op, uint32_t options)
333 : {
334 0 : if (pcmk_all_flags_set(options, pcmk__node_attr_value | pcmk__node_attr_delay)) {
335 0 : crm_xml_add(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE_BOTH);
336 0 : } else if (pcmk_is_set(options, pcmk__node_attr_value)) {
337 0 : crm_xml_add(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
338 0 : } else if (pcmk_is_set(options, pcmk__node_attr_delay)) {
339 0 : crm_xml_add(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE_DELAY);
340 : }
341 0 : }
342 :
343 : static void
344 0 : populate_update_op(xmlNode *op, const char *node, const char *name, const char *value,
345 : const char *dampen, const char *set, uint32_t options)
346 : {
347 0 : if (pcmk_is_set(options, pcmk__node_attr_pattern)) {
348 0 : crm_xml_add(op, PCMK__XA_ATTR_REGEX, name);
349 : } else {
350 0 : crm_xml_add(op, PCMK__XA_ATTR_NAME, name);
351 : }
352 :
353 0 : if (pcmk_is_set(options, pcmk__node_attr_utilization)) {
354 0 : crm_xml_add(op, PCMK__XA_ATTR_SET_TYPE, PCMK_XE_UTILIZATION);
355 : } else {
356 0 : crm_xml_add(op, PCMK__XA_ATTR_SET_TYPE, PCMK_XE_INSTANCE_ATTRIBUTES);
357 : }
358 :
359 0 : add_op_attr(op, options);
360 :
361 0 : crm_xml_add(op, PCMK__XA_ATTR_VALUE, value);
362 0 : crm_xml_add(op, PCMK__XA_ATTR_DAMPENING, dampen);
363 0 : pcmk__xe_add_node(op, node, 0);
364 0 : crm_xml_add(op, PCMK__XA_ATTR_SET, set);
365 0 : crm_xml_add_int(op, PCMK__XA_ATTR_IS_REMOTE,
366 0 : pcmk_is_set(options, pcmk__node_attr_remote));
367 0 : crm_xml_add_int(op, PCMK__XA_ATTR_IS_PRIVATE,
368 0 : pcmk_is_set(options, pcmk__node_attr_private));
369 :
370 0 : if (pcmk_is_set(options, pcmk__node_attr_sync_local)) {
371 0 : crm_xml_add(op, PCMK__XA_ATTR_SYNC_POINT, PCMK__VALUE_LOCAL);
372 0 : } else if (pcmk_is_set(options, pcmk__node_attr_sync_cluster)) {
373 0 : crm_xml_add(op, PCMK__XA_ATTR_SYNC_POINT, PCMK__VALUE_CLUSTER);
374 : }
375 0 : }
376 :
377 : int
378 0 : pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name,
379 : const char *value, const char *dampen, const char *set,
380 : const char *user_name, uint32_t options)
381 : {
382 0 : int rc = pcmk_rc_ok;
383 0 : xmlNode *request = NULL;
384 0 : const char *target = NULL;
385 :
386 0 : if (name == NULL) {
387 0 : return EINVAL;
388 : }
389 :
390 0 : target = pcmk__node_attr_target(node);
391 :
392 0 : if (target != NULL) {
393 0 : node = target;
394 : }
395 :
396 0 : crm_debug("Asking %s to update '%s' to '%s' for %s",
397 : pcmk_ipc_name(api, true), name, pcmk__s(value, "(null)"),
398 : pcmk__s(node, "local node"));
399 :
400 0 : request = create_attrd_op(user_name);
401 0 : populate_update_op(request, node, name, value, dampen, set, options);
402 :
403 0 : rc = connect_and_send_attrd_request(api, request);
404 :
405 0 : free_xml(request);
406 0 : return rc;
407 : }
408 :
409 : int
410 0 : pcmk__attrd_api_update_list(pcmk_ipc_api_t *api, GList *attrs, const char *dampen,
411 : const char *set, const char *user_name,
412 : uint32_t options)
413 : {
414 0 : int rc = pcmk_rc_ok;
415 0 : xmlNode *request = NULL;
416 :
417 0 : if (attrs == NULL) {
418 0 : return EINVAL;
419 : }
420 :
421 : /* There are two different ways of handling a list of attributes:
422 : *
423 : * (1) For messages originating from some command line tool, we have to send
424 : * them one at a time. In this loop, we just call pcmk__attrd_api_update
425 : * for each, letting it deal with creating the API object if it doesn't
426 : * already exist.
427 : *
428 : * The reason we can't use a single message in this case is that we can't
429 : * trust that the server supports it. Remote nodes could be involved
430 : * here, and there's no guarantee that a newer client running on a remote
431 : * node is talking to (or proxied through) a cluster node with a newer
432 : * attrd. We also can't just try sending a single message and then falling
433 : * back on multiple. There's no handshake with the attrd server to
434 : * determine its version. And then we would need to do that fallback in the
435 : * dispatch function for this to work for all connection types (mainloop in
436 : * particular), and at that point we won't know what the original message
437 : * was in order to break it apart and resend as individual messages.
438 : *
439 : * (2) For messages between daemons, we can be assured that the local attrd
440 : * will support the new message and that it can send to the other attrds
441 : * as one request or split up according to the minimum supported version.
442 : */
443 0 : for (GList *iter = attrs; iter != NULL; iter = iter->next) {
444 0 : pcmk__attrd_query_pair_t *pair = (pcmk__attrd_query_pair_t *) iter->data;
445 :
446 0 : if (pcmk__is_daemon) {
447 0 : const char *target = NULL;
448 0 : xmlNode *child = NULL;
449 :
450 : /* First time through this loop - create the basic request. */
451 0 : if (request == NULL) {
452 0 : request = create_attrd_op(user_name);
453 0 : add_op_attr(request, options);
454 : }
455 :
456 : /* Add a child node for this operation. We add the task to the top
457 : * level XML node so attrd_ipc_dispatch doesn't need changes. And
458 : * then we also add the task to each child node in populate_update_op
459 : * so attrd_client_update knows what form of update is taking place.
460 : */
461 0 : child = pcmk__xe_create(request, PCMK_XE_OP);
462 0 : target = pcmk__node_attr_target(pair->node);
463 :
464 0 : if (target != NULL) {
465 0 : pair->node = target;
466 : }
467 :
468 0 : populate_update_op(child, pair->node, pair->name, pair->value, dampen,
469 : set, options);
470 : } else {
471 0 : rc = pcmk__attrd_api_update(api, pair->node, pair->name, pair->value,
472 : dampen, set, user_name, options);
473 : }
474 : }
475 :
476 : /* If we were doing multiple attributes at once, we still need to send the
477 : * request. Do that now, creating and destroying the API object if needed.
478 : */
479 0 : if (pcmk__is_daemon) {
480 0 : rc = connect_and_send_attrd_request(api, request);
481 0 : free_xml(request);
482 : }
483 :
484 0 : return rc;
485 : }
|