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