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