/src/pacemaker/lib/common/ipc_controld.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020-2025 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 <errno.h> |
13 | | #include <inttypes.h> // PRIu32 |
14 | | #include <stdbool.h> |
15 | | #include <stdint.h> // uint32_t |
16 | | #include <stdio.h> |
17 | | |
18 | | #include <libxml/tree.h> |
19 | | |
20 | | #include <crm/crm.h> |
21 | | #include <crm/common/xml.h> |
22 | | #include <crm/common/ipc.h> |
23 | | #include <crm/common/ipc_internal.h> |
24 | | #include <crm/common/ipc_controld.h> |
25 | | #include "crmcommon_private.h" |
26 | | |
27 | | struct controld_api_private_s { |
28 | | char *client_uuid; |
29 | | unsigned int replies_expected; |
30 | | }; |
31 | | |
32 | | static xmlNode *create_hello_message(const char *uuid, const char *client_name, |
33 | | const char *major_version, |
34 | | const char *minor_version); |
35 | | |
36 | | /*! |
37 | | * \internal |
38 | | * \brief Get a string representation of a controller API reply type |
39 | | * |
40 | | * \param[in] reply Controller API reply type |
41 | | * |
42 | | * \return String representation of a controller API reply type |
43 | | */ |
44 | | const char * |
45 | | pcmk__controld_api_reply2str(enum pcmk_controld_api_reply reply) |
46 | 0 | { |
47 | 0 | switch (reply) { |
48 | 0 | case pcmk_controld_reply_reprobe: |
49 | 0 | return "reprobe"; |
50 | 0 | case pcmk_controld_reply_info: |
51 | 0 | return "info"; |
52 | 0 | case pcmk_controld_reply_resource: |
53 | 0 | return "resource"; |
54 | 0 | case pcmk_controld_reply_ping: |
55 | 0 | return "ping"; |
56 | 0 | case pcmk_controld_reply_nodes: |
57 | 0 | return "nodes"; |
58 | 0 | default: |
59 | 0 | return "unknown"; |
60 | 0 | } |
61 | 0 | } |
62 | | |
63 | | // \return Standard Pacemaker return code |
64 | | static int |
65 | | new_data(pcmk_ipc_api_t *api) |
66 | 0 | { |
67 | 0 | struct controld_api_private_s *private = NULL; |
68 | |
|
69 | 0 | api->api_data = calloc(1, sizeof(struct controld_api_private_s)); |
70 | |
|
71 | 0 | if (api->api_data == NULL) { |
72 | 0 | return errno; |
73 | 0 | } |
74 | | |
75 | 0 | private = api->api_data; |
76 | | |
77 | | /* This is set to the PID because that's how it was always done, but PIDs |
78 | | * are not unique because clients can be remote. The value appears to be |
79 | | * unused other than as part of PCMK__XA_CRM_SYS_FROM in IPC requests, which |
80 | | * is only compared against the internal system names (CRM_SYSTEM_TENGINE, |
81 | | * etc.), so it shouldn't be a problem. |
82 | | */ |
83 | 0 | private->client_uuid = pcmk__getpid_s(); |
84 | | |
85 | | /* @TODO Implement a call ID model similar to the CIB, executor, and fencer |
86 | | * IPC APIs, so that requests and replies can be matched, and |
87 | | * duplicate replies can be discarded. |
88 | | */ |
89 | 0 | return pcmk_rc_ok; |
90 | 0 | } |
91 | | |
92 | | static void |
93 | | free_data(void *data) |
94 | 0 | { |
95 | 0 | free(((struct controld_api_private_s *) data)->client_uuid); |
96 | 0 | free(data); |
97 | 0 | } |
98 | | |
99 | | // \return Standard Pacemaker return code |
100 | | static int |
101 | | post_connect(pcmk_ipc_api_t *api) |
102 | 0 | { |
103 | | /* The controller currently requires clients to register via a hello |
104 | | * request, but does not reply back. |
105 | | */ |
106 | 0 | struct controld_api_private_s *private = api->api_data; |
107 | 0 | const char *client_name = crm_system_name? crm_system_name : "client"; |
108 | 0 | xmlNode *hello; |
109 | 0 | int rc; |
110 | |
|
111 | 0 | hello = create_hello_message(private->client_uuid, client_name, |
112 | 0 | PCMK__CONTROLD_API_MAJOR, |
113 | 0 | PCMK__CONTROLD_API_MINOR); |
114 | 0 | rc = pcmk__send_ipc_request(api, hello); |
115 | 0 | pcmk__xml_free(hello); |
116 | 0 | if (rc != pcmk_rc_ok) { |
117 | 0 | pcmk__info("Could not send IPC hello to %s: %s " QB_XS " rc=%s", |
118 | 0 | pcmk_ipc_name(api, true), pcmk_rc_str(rc), rc); |
119 | 0 | } else { |
120 | 0 | pcmk__debug("Sent IPC hello to %s", pcmk_ipc_name(api, true)); |
121 | 0 | } |
122 | 0 | return rc; |
123 | 0 | } |
124 | | |
125 | | static void |
126 | | set_node_info_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data) |
127 | 0 | { |
128 | 0 | data->reply_type = pcmk_controld_reply_info; |
129 | 0 | if (msg_data == NULL) { |
130 | 0 | return; |
131 | 0 | } |
132 | 0 | data->data.node_info.have_quorum = |
133 | 0 | pcmk__xe_attr_is_true(msg_data, PCMK_XA_HAVE_QUORUM); |
134 | 0 | data->data.node_info.is_remote = |
135 | 0 | pcmk__xe_attr_is_true(msg_data, PCMK_XA_REMOTE_NODE); |
136 | | |
137 | | /* Integer node_info.id is currently valid only for Corosync nodes. |
138 | | * |
139 | | * @TODO: Improve handling after pcmk__node_status_t is refactored to handle |
140 | | * layer-specific data better. |
141 | | */ |
142 | 0 | pcmk__xe_get_int(msg_data, PCMK_XA_ID, &(data->data.node_info.id)); |
143 | |
|
144 | 0 | data->data.node_info.uuid = pcmk__xe_get(msg_data, PCMK_XA_ID); |
145 | 0 | data->data.node_info.uname = pcmk__xe_get(msg_data, PCMK_XA_UNAME); |
146 | 0 | data->data.node_info.state = pcmk__xe_get(msg_data, PCMK_XA_CRMD); |
147 | 0 | } |
148 | | |
149 | | static void |
150 | | set_ping_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data) |
151 | 0 | { |
152 | 0 | data->reply_type = pcmk_controld_reply_ping; |
153 | 0 | if (msg_data == NULL) { |
154 | 0 | return; |
155 | 0 | } |
156 | 0 | data->data.ping.sys_from = pcmk__xe_get(msg_data, PCMK__XA_CRM_SUBSYSTEM); |
157 | 0 | data->data.ping.fsa_state = pcmk__xe_get(msg_data, PCMK__XA_CRMD_STATE); |
158 | 0 | data->data.ping.result = pcmk__xe_get(msg_data, PCMK_XA_RESULT); |
159 | 0 | } |
160 | | |
161 | | static void |
162 | | set_nodes_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data) |
163 | 0 | { |
164 | 0 | pcmk_controld_api_node_t *node_info; |
165 | |
|
166 | 0 | data->reply_type = pcmk_controld_reply_nodes; |
167 | 0 | for (xmlNode *node = pcmk__xe_first_child(msg_data, PCMK_XE_NODE, NULL, |
168 | 0 | NULL); |
169 | 0 | node != NULL; node = pcmk__xe_next(node, PCMK_XE_NODE)) { |
170 | |
|
171 | 0 | long long id_ll = 0; |
172 | |
|
173 | 0 | node_info = pcmk__assert_alloc(1, sizeof(pcmk_controld_api_node_t)); |
174 | 0 | pcmk__xe_get_ll(node, PCMK_XA_ID, &id_ll); |
175 | 0 | if (id_ll > 0) { |
176 | 0 | node_info->id = id_ll; |
177 | 0 | } |
178 | 0 | node_info->uname = pcmk__xe_get(node, PCMK_XA_UNAME); |
179 | 0 | node_info->state = pcmk__xe_get(node, PCMK__XA_IN_CCM); |
180 | 0 | data->data.nodes = g_list_prepend(data->data.nodes, node_info); |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | static bool |
185 | | reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) |
186 | 0 | { |
187 | | // We only need to handle commands that API functions can send |
188 | 0 | return pcmk__str_any_of(pcmk__xe_get(request, PCMK__XA_CRM_TASK), |
189 | 0 | PCMK__CONTROLD_CMD_NODES, |
190 | 0 | CRM_OP_LRM_DELETE, |
191 | 0 | CRM_OP_LRM_FAIL, |
192 | 0 | CRM_OP_NODE_INFO, |
193 | 0 | CRM_OP_PING, |
194 | 0 | CRM_OP_REPROBE, |
195 | 0 | CRM_OP_RM_NODE_CACHE, |
196 | 0 | NULL); |
197 | 0 | } |
198 | | |
199 | | static bool |
200 | | dispatch(pcmk_ipc_api_t *api, xmlNode *reply) |
201 | 0 | { |
202 | 0 | struct controld_api_private_s *private = api->api_data; |
203 | 0 | crm_exit_t status = CRM_EX_OK; |
204 | 0 | xmlNode *wrapper = NULL; |
205 | 0 | xmlNode *msg_data = NULL; |
206 | 0 | const char *value = NULL; |
207 | 0 | pcmk_controld_api_reply_t reply_data = { |
208 | 0 | pcmk_controld_reply_unknown, NULL, NULL, |
209 | 0 | }; |
210 | |
|
211 | 0 | if (pcmk__xe_is(reply, PCMK__XE_ACK)) { |
212 | | /* ACKs are trivial responses that do not count toward expected replies, |
213 | | * and do not have all the fields that validation requires, so skip that |
214 | | * processing. |
215 | | */ |
216 | 0 | return private->replies_expected > 0; |
217 | 0 | } |
218 | | |
219 | 0 | if (private->replies_expected > 0) { |
220 | 0 | private->replies_expected--; |
221 | 0 | } |
222 | | |
223 | | // Do some basic validation of the reply |
224 | |
|
225 | 0 | value = pcmk__xe_get(reply, PCMK__XA_SUBT); |
226 | 0 | if (pcmk__str_eq(value, PCMK__VALUE_REQUEST, pcmk__str_none)) { |
227 | | /* @COMPAT Controllers <3.0.0 set PCMK__XA_SUBT to PCMK__VALUE_REQUEST |
228 | | * for certain replies. Once we no longer support Pacemaker Remote nodes |
229 | | * connecting to cluster nodes <3.0.0, or rolling upgrades from <3.0.0, |
230 | | * we can drop this check. |
231 | | */ |
232 | 0 | pcmk__trace("Received a reply that was marked as a request (bug unless " |
233 | 0 | "sent by a controller <3.0.0)"); |
234 | |
|
235 | 0 | } else if (!pcmk__str_eq(value, PCMK__VALUE_RESPONSE, pcmk__str_none)) { |
236 | 0 | pcmk__info("Unrecognizable message from controller: invalid message " |
237 | 0 | "type '%s'", |
238 | 0 | pcmk__s(value, "")); |
239 | 0 | status = CRM_EX_PROTOCOL; |
240 | 0 | goto done; |
241 | 0 | } |
242 | | |
243 | 0 | if (pcmk__str_empty(pcmk__xe_get(reply, PCMK_XA_REFERENCE))) { |
244 | 0 | pcmk__info("Unrecognizable message from controller: no reference"); |
245 | 0 | status = CRM_EX_PROTOCOL; |
246 | 0 | goto done; |
247 | 0 | } |
248 | | |
249 | 0 | value = pcmk__xe_get(reply, PCMK__XA_CRM_TASK); |
250 | 0 | if (pcmk__str_empty(value)) { |
251 | 0 | pcmk__info("Unrecognizable message from controller: no command name"); |
252 | 0 | status = CRM_EX_PROTOCOL; |
253 | 0 | goto done; |
254 | 0 | } |
255 | | |
256 | | // Parse useful info from reply |
257 | | |
258 | 0 | reply_data.feature_set = pcmk__xe_get(reply, PCMK_XA_VERSION); |
259 | 0 | reply_data.host_from = pcmk__xe_get(reply, PCMK__XA_SRC); |
260 | |
|
261 | 0 | wrapper = pcmk__xe_first_child(reply, PCMK__XE_CRM_XML, NULL, NULL); |
262 | 0 | msg_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); |
263 | |
|
264 | 0 | if (!strcmp(value, CRM_OP_REPROBE)) { |
265 | 0 | reply_data.reply_type = pcmk_controld_reply_reprobe; |
266 | |
|
267 | 0 | } else if (!strcmp(value, CRM_OP_NODE_INFO)) { |
268 | 0 | set_node_info_data(&reply_data, msg_data); |
269 | |
|
270 | 0 | } else if (!strcmp(value, CRM_OP_INVOKE_LRM)) { |
271 | 0 | reply_data.reply_type = pcmk_controld_reply_resource; |
272 | 0 | reply_data.data.resource.node_state = msg_data; |
273 | |
|
274 | 0 | } else if (!strcmp(value, CRM_OP_PING)) { |
275 | 0 | set_ping_data(&reply_data, msg_data); |
276 | |
|
277 | 0 | } else if (!strcmp(value, PCMK__CONTROLD_CMD_NODES)) { |
278 | 0 | set_nodes_data(&reply_data, msg_data); |
279 | |
|
280 | 0 | } else { |
281 | 0 | pcmk__info("Unrecognizable message from controller: unknown command " |
282 | 0 | "'%s'", |
283 | 0 | value); |
284 | 0 | status = CRM_EX_PROTOCOL; |
285 | 0 | } |
286 | |
|
287 | 0 | done: |
288 | 0 | pcmk__call_ipc_callback(api, pcmk_ipc_event_reply, status, &reply_data); |
289 | | |
290 | | // Free any reply data that was allocated |
291 | 0 | if (pcmk__str_eq(value, PCMK__CONTROLD_CMD_NODES, pcmk__str_casei)) { |
292 | 0 | g_list_free_full(reply_data.data.nodes, free); |
293 | 0 | } |
294 | |
|
295 | 0 | return false; // No further replies needed |
296 | 0 | } |
297 | | |
298 | | pcmk__ipc_methods_t * |
299 | | pcmk__controld_api_methods(void) |
300 | 0 | { |
301 | 0 | pcmk__ipc_methods_t *cmds = calloc(1, sizeof(pcmk__ipc_methods_t)); |
302 | |
|
303 | 0 | if (cmds != NULL) { |
304 | 0 | cmds->new_data = new_data; |
305 | 0 | cmds->free_data = free_data; |
306 | 0 | cmds->post_connect = post_connect; |
307 | 0 | cmds->reply_expected = reply_expected; |
308 | 0 | cmds->dispatch = dispatch; |
309 | 0 | } |
310 | 0 | return cmds; |
311 | 0 | } |
312 | | |
313 | | /*! |
314 | | * \internal |
315 | | * \brief Create XML for a controller IPC request |
316 | | * |
317 | | * \param[in] api Controller connection |
318 | | * \param[in] op Controller IPC command name |
319 | | * \param[in] node Node name to set as destination host |
320 | | * \param[in] msg_data XML to attach to request as message data |
321 | | * |
322 | | * \return Newly allocated XML for request |
323 | | */ |
324 | | static xmlNode * |
325 | | create_controller_request(const pcmk_ipc_api_t *api, const char *op, |
326 | | const char *node, xmlNode *msg_data) |
327 | 0 | { |
328 | 0 | struct controld_api_private_s *private = NULL; |
329 | 0 | const char *sys_to = NULL; |
330 | 0 | char *sender_system = NULL; |
331 | 0 | xmlNode *request = NULL; |
332 | |
|
333 | 0 | if (api == NULL) { |
334 | 0 | return NULL; |
335 | 0 | } |
336 | 0 | private = api->api_data; |
337 | 0 | if ((node == NULL) && !strcmp(op, CRM_OP_PING)) { |
338 | 0 | sys_to = CRM_SYSTEM_DC; |
339 | 0 | } else { |
340 | 0 | sys_to = CRM_SYSTEM_CRMD; |
341 | 0 | } |
342 | 0 | sender_system = pcmk__assert_asprintf("%s_%s", private->client_uuid, |
343 | 0 | pcmk__s(crm_system_name, "client")); |
344 | 0 | request = pcmk__new_request(pcmk_ipc_controld, sender_system, node, sys_to, |
345 | 0 | op, msg_data); |
346 | 0 | free(sender_system); |
347 | 0 | return request; |
348 | 0 | } |
349 | | |
350 | | // \return Standard Pacemaker return code |
351 | | static int |
352 | | send_controller_request(pcmk_ipc_api_t *api, const xmlNode *request, |
353 | | bool reply_is_expected) |
354 | 0 | { |
355 | 0 | if (pcmk__xe_get(request, PCMK_XA_REFERENCE) == NULL) { |
356 | 0 | return EINVAL; |
357 | 0 | } |
358 | 0 | if (reply_is_expected) { |
359 | 0 | struct controld_api_private_s *private = api->api_data; |
360 | |
|
361 | 0 | private->replies_expected++; |
362 | 0 | } |
363 | 0 | return pcmk__send_ipc_request(api, request); |
364 | 0 | } |
365 | | |
366 | | static xmlNode * |
367 | | create_reprobe_message_data(const char *target_node, const char *router_node) |
368 | 0 | { |
369 | 0 | xmlNode *msg_data; |
370 | |
|
371 | 0 | msg_data = pcmk__xe_create(NULL, "data_for_" CRM_OP_REPROBE); |
372 | 0 | pcmk__xe_set(msg_data, PCMK__META_ON_NODE, target_node); |
373 | 0 | if ((router_node != NULL) && !pcmk__str_eq(router_node, target_node, pcmk__str_casei)) { |
374 | 0 | pcmk__xe_set(msg_data, PCMK__XA_ROUTER_NODE, router_node); |
375 | 0 | } |
376 | 0 | return msg_data; |
377 | 0 | } |
378 | | |
379 | | /*! |
380 | | * \brief Send a reprobe controller operation |
381 | | * |
382 | | * \param[in,out] api Controller connection |
383 | | * \param[in] target_node Name of node to reprobe |
384 | | * \param[in] router_node Router node for host |
385 | | * |
386 | | * \return Standard Pacemaker return code |
387 | | * \note Event callback will get a reply of type pcmk_controld_reply_reprobe. |
388 | | */ |
389 | | int |
390 | | pcmk_controld_api_reprobe(pcmk_ipc_api_t *api, const char *target_node, |
391 | | const char *router_node) |
392 | 0 | { |
393 | 0 | xmlNode *request; |
394 | 0 | xmlNode *msg_data; |
395 | 0 | int rc = pcmk_rc_ok; |
396 | |
|
397 | 0 | if (api == NULL) { |
398 | 0 | return EINVAL; |
399 | 0 | } |
400 | 0 | if (router_node == NULL) { |
401 | 0 | router_node = target_node; |
402 | 0 | } |
403 | 0 | pcmk__debug("Sending %s IPC request to reprobe %s via %s", |
404 | 0 | pcmk_ipc_name(api, true), pcmk__s(target_node, "local node"), |
405 | 0 | pcmk__s(router_node, "local node")); |
406 | 0 | msg_data = create_reprobe_message_data(target_node, router_node); |
407 | 0 | request = create_controller_request(api, CRM_OP_REPROBE, router_node, |
408 | 0 | msg_data); |
409 | 0 | rc = send_controller_request(api, request, true); |
410 | 0 | pcmk__xml_free(msg_data); |
411 | 0 | pcmk__xml_free(request); |
412 | 0 | return rc; |
413 | 0 | } |
414 | | |
415 | | /*! |
416 | | * \brief Send a "node info" controller operation |
417 | | * |
418 | | * \param[in,out] api Controller connection |
419 | | * \param[in] nodeid ID of node to get info for (or 0 for local node) |
420 | | * |
421 | | * \return Standard Pacemaker return code |
422 | | * \note Event callback will get a reply of type pcmk_controld_reply_info. |
423 | | */ |
424 | | int |
425 | | pcmk_controld_api_node_info(pcmk_ipc_api_t *api, uint32_t nodeid) |
426 | 0 | { |
427 | 0 | xmlNode *request; |
428 | 0 | int rc = pcmk_rc_ok; |
429 | |
|
430 | 0 | request = create_controller_request(api, CRM_OP_NODE_INFO, NULL, NULL); |
431 | 0 | if (request == NULL) { |
432 | 0 | return EINVAL; |
433 | 0 | } |
434 | 0 | if (nodeid > 0) { |
435 | 0 | pcmk__xe_set_ll(request, PCMK_XA_ID, nodeid); |
436 | 0 | } |
437 | |
|
438 | 0 | rc = send_controller_request(api, request, true); |
439 | 0 | pcmk__xml_free(request); |
440 | 0 | return rc; |
441 | 0 | } |
442 | | |
443 | | /*! |
444 | | * \brief Ask the controller for status |
445 | | * |
446 | | * \param[in,out] api Controller connection |
447 | | * \param[in] node_name Name of node whose status is desired (NULL for DC) |
448 | | * |
449 | | * \return Standard Pacemaker return code |
450 | | * \note Event callback will get a reply of type pcmk_controld_reply_ping. |
451 | | */ |
452 | | int |
453 | | pcmk_controld_api_ping(pcmk_ipc_api_t *api, const char *node_name) |
454 | 0 | { |
455 | 0 | xmlNode *request; |
456 | 0 | int rc = pcmk_rc_ok; |
457 | |
|
458 | 0 | request = create_controller_request(api, CRM_OP_PING, node_name, NULL); |
459 | 0 | if (request == NULL) { |
460 | 0 | return EINVAL; |
461 | 0 | } |
462 | 0 | rc = send_controller_request(api, request, true); |
463 | 0 | pcmk__xml_free(request); |
464 | 0 | return rc; |
465 | 0 | } |
466 | | |
467 | | /*! |
468 | | * \brief Ask the controller for cluster information |
469 | | * |
470 | | * \param[in,out] api Controller connection |
471 | | * |
472 | | * \return Standard Pacemaker return code |
473 | | * \note Event callback will get a reply of type pcmk_controld_reply_nodes. |
474 | | */ |
475 | | int |
476 | | pcmk_controld_api_list_nodes(pcmk_ipc_api_t *api) |
477 | 0 | { |
478 | 0 | xmlNode *request; |
479 | 0 | int rc = EINVAL; |
480 | |
|
481 | 0 | request = create_controller_request(api, PCMK__CONTROLD_CMD_NODES, NULL, |
482 | 0 | NULL); |
483 | 0 | if (request != NULL) { |
484 | 0 | rc = send_controller_request(api, request, true); |
485 | 0 | pcmk__xml_free(request); |
486 | 0 | } |
487 | 0 | return rc; |
488 | 0 | } |
489 | | |
490 | | // \return Standard Pacemaker return code |
491 | | static int |
492 | | controller_resource_op(pcmk_ipc_api_t *api, const char *op, |
493 | | const char *target_node, const char *router_node, |
494 | | bool cib_only, const char *rsc_id, |
495 | | const char *rsc_long_id, const char *standard, |
496 | | const char *provider, const char *type) |
497 | 0 | { |
498 | 0 | int rc = pcmk_rc_ok; |
499 | 0 | char *key; |
500 | 0 | xmlNode *request, *msg_data, *xml_rsc, *params; |
501 | |
|
502 | 0 | if (api == NULL) { |
503 | 0 | return EINVAL; |
504 | 0 | } |
505 | 0 | if (router_node == NULL) { |
506 | 0 | router_node = target_node; |
507 | 0 | } |
508 | |
|
509 | 0 | msg_data = pcmk__xe_create(NULL, PCMK__XE_RSC_OP); |
510 | | |
511 | | /* The controller logs the transition key from resource op requests, so we |
512 | | * need to have *something* for it. |
513 | | * @TODO don't use "crm-resource" |
514 | | */ |
515 | 0 | key = pcmk__transition_key(0, getpid(), 0, |
516 | 0 | "xxxxxxxx-xrsc-opxx-xcrm-resourcexxxx"); |
517 | 0 | pcmk__xe_set(msg_data, PCMK__XA_TRANSITION_KEY, key); |
518 | 0 | free(key); |
519 | |
|
520 | 0 | pcmk__xe_set(msg_data, PCMK__META_ON_NODE, target_node); |
521 | 0 | if (!pcmk__str_eq(router_node, target_node, pcmk__str_casei)) { |
522 | 0 | pcmk__xe_set(msg_data, PCMK__XA_ROUTER_NODE, router_node); |
523 | 0 | } |
524 | |
|
525 | 0 | if (cib_only) { |
526 | | // Indicate that only the CIB needs to be cleaned |
527 | 0 | pcmk__xe_set(msg_data, PCMK__XA_MODE, PCMK__VALUE_CIB); |
528 | 0 | } |
529 | |
|
530 | 0 | xml_rsc = pcmk__xe_create(msg_data, PCMK_XE_PRIMITIVE); |
531 | 0 | pcmk__xe_set(xml_rsc, PCMK_XA_ID, rsc_id); |
532 | 0 | pcmk__xe_set(xml_rsc, PCMK__XA_LONG_ID, rsc_long_id); |
533 | 0 | pcmk__xe_set(xml_rsc, PCMK_XA_CLASS, standard); |
534 | 0 | pcmk__xe_set(xml_rsc, PCMK_XA_PROVIDER, provider); |
535 | 0 | pcmk__xe_set(xml_rsc, PCMK_XA_TYPE, type); |
536 | |
|
537 | 0 | params = pcmk__xe_create(msg_data, PCMK__XE_ATTRIBUTES); |
538 | 0 | pcmk__xe_set(params, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); |
539 | | |
540 | | // The controller parses the timeout from the request |
541 | 0 | key = crm_meta_name(PCMK_META_TIMEOUT); |
542 | 0 | pcmk__xe_set(params, key, "60000"); /* 1 minute */ //@TODO pass as arg |
543 | 0 | free(key); |
544 | |
|
545 | 0 | request = create_controller_request(api, op, router_node, msg_data); |
546 | 0 | rc = send_controller_request(api, request, true); |
547 | 0 | pcmk__xml_free(msg_data); |
548 | 0 | pcmk__xml_free(request); |
549 | 0 | return rc; |
550 | 0 | } |
551 | | |
552 | | /*! |
553 | | * \brief Ask the controller to fail a resource |
554 | | * |
555 | | * \param[in,out] api Controller connection |
556 | | * \param[in] target_node Name of node resource is on |
557 | | * \param[in] router_node Router node for target |
558 | | * \param[in] rsc_id ID of resource to fail |
559 | | * \param[in] rsc_long_id Long ID of resource (if any) |
560 | | * \param[in] standard Standard of resource |
561 | | * \param[in] provider Provider of resource (if any) |
562 | | * \param[in] type Type of resource to fail |
563 | | * |
564 | | * \return Standard Pacemaker return code |
565 | | * \note Event callback will get a reply of type pcmk_controld_reply_resource. |
566 | | */ |
567 | | int |
568 | | pcmk_controld_api_fail(pcmk_ipc_api_t *api, |
569 | | const char *target_node, const char *router_node, |
570 | | const char *rsc_id, const char *rsc_long_id, |
571 | | const char *standard, const char *provider, |
572 | | const char *type) |
573 | 0 | { |
574 | 0 | pcmk__debug("Sending %s IPC request to fail %s (a.k.a. %s) on %s via %s", |
575 | 0 | pcmk_ipc_name(api, true), pcmk__s(rsc_id, "unknown resource"), |
576 | 0 | pcmk__s(rsc_long_id, "no other names"), |
577 | 0 | pcmk__s(target_node, "unspecified node"), |
578 | 0 | pcmk__s(router_node, "unspecified node")); |
579 | 0 | return controller_resource_op(api, CRM_OP_LRM_FAIL, target_node, |
580 | 0 | router_node, false, rsc_id, rsc_long_id, |
581 | 0 | standard, provider, type); |
582 | 0 | } |
583 | | |
584 | | /*! |
585 | | * \brief Ask the controller to refresh a resource |
586 | | * |
587 | | * \param[in,out] api Controller connection |
588 | | * \param[in] target_node Name of node resource is on |
589 | | * \param[in] router_node Router node for target |
590 | | * \param[in] rsc_id ID of resource to refresh |
591 | | * \param[in] rsc_long_id Long ID of resource (if any) |
592 | | * \param[in] standard Standard of resource |
593 | | * \param[in] provider Provider of resource (if any) |
594 | | * \param[in] type Type of resource |
595 | | * \param[in] cib_only If true, clean resource from CIB only |
596 | | * |
597 | | * \return Standard Pacemaker return code |
598 | | * \note Event callback will get a reply of type pcmk_controld_reply_resource. |
599 | | */ |
600 | | int |
601 | | pcmk_controld_api_refresh(pcmk_ipc_api_t *api, const char *target_node, |
602 | | const char *router_node, |
603 | | const char *rsc_id, const char *rsc_long_id, |
604 | | const char *standard, const char *provider, |
605 | | const char *type, bool cib_only) |
606 | 0 | { |
607 | 0 | pcmk__debug("Sending %s IPC request to refresh %s (a.k.a. %s) on %s via %s", |
608 | 0 | pcmk_ipc_name(api, true), pcmk__s(rsc_id, "unknown resource"), |
609 | 0 | pcmk__s(rsc_long_id, "no other names"), |
610 | 0 | pcmk__s(target_node, "unspecified node"), |
611 | 0 | pcmk__s(router_node, "unspecified node")); |
612 | 0 | return controller_resource_op(api, CRM_OP_LRM_DELETE, target_node, |
613 | 0 | router_node, cib_only, rsc_id, rsc_long_id, |
614 | 0 | standard, provider, type); |
615 | 0 | } |
616 | | |
617 | | /*! |
618 | | * \brief Get the number of IPC replies currently expected from the controller |
619 | | * |
620 | | * \param[in] api Controller IPC API connection |
621 | | * |
622 | | * \return Number of replies expected |
623 | | */ |
624 | | unsigned int |
625 | | pcmk_controld_api_replies_expected(const pcmk_ipc_api_t *api) |
626 | 0 | { |
627 | 0 | struct controld_api_private_s *private = api->api_data; |
628 | |
|
629 | 0 | return private->replies_expected; |
630 | 0 | } |
631 | | |
632 | | /*! |
633 | | * \internal |
634 | | * \brief Create XML for a controller IPC "hello" message |
635 | | */ |
636 | | static xmlNode * |
637 | | create_hello_message(const char *uuid, const char *client_name, |
638 | | const char *major_version, const char *minor_version) |
639 | 0 | { |
640 | 0 | xmlNode *hello_node = NULL; |
641 | 0 | xmlNode *hello = NULL; |
642 | 0 | char *sender_system = NULL; |
643 | |
|
644 | 0 | if (pcmk__str_empty(uuid) || pcmk__str_empty(client_name) |
645 | 0 | || pcmk__str_empty(major_version) || pcmk__str_empty(minor_version)) { |
646 | 0 | pcmk__err("Could not create IPC hello message from %s (UUID %s): " |
647 | 0 | "missing information", |
648 | 0 | pcmk__s(client_name, "unknown client"), |
649 | 0 | pcmk__s(uuid, "unknown")); |
650 | 0 | return NULL; |
651 | 0 | } |
652 | | |
653 | 0 | hello_node = pcmk__xe_create(NULL, PCMK__XE_OPTIONS); |
654 | 0 | pcmk__xe_set(hello_node, PCMK__XA_MAJOR_VERSION, major_version); |
655 | 0 | pcmk__xe_set(hello_node, PCMK__XA_MINOR_VERSION, minor_version); |
656 | 0 | pcmk__xe_set(hello_node, PCMK__XA_CLIENT_NAME, client_name); |
657 | | |
658 | | // @TODO Nothing uses this. Drop, or keep for debugging? |
659 | 0 | pcmk__xe_set(hello_node, PCMK__XA_CLIENT_UUID, uuid); |
660 | |
|
661 | 0 | sender_system = pcmk__assert_asprintf("%s_%s", uuid, client_name); |
662 | 0 | hello = pcmk__new_request(pcmk_ipc_controld, sender_system, NULL, NULL, |
663 | 0 | CRM_OP_HELLO, hello_node); |
664 | 0 | free(sender_system); |
665 | 0 | pcmk__xml_free(hello_node); |
666 | 0 | if (hello == NULL) { |
667 | 0 | pcmk__err("Could not create IPC hello message from %s (UUID %s): " |
668 | 0 | "Request creation failed", |
669 | 0 | client_name, uuid); |
670 | 0 | return NULL; |
671 | 0 | } |
672 | | |
673 | 0 | pcmk__trace("Created hello message from %s (UUID %s)", client_name, uuid); |
674 | 0 | return hello; |
675 | 0 | } |