/src/pacemaker/lib/common/actions.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-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 <stdbool.h> |
13 | | #include <stdio.h> |
14 | | #include <string.h> |
15 | | #include <stdlib.h> |
16 | | #include <sys/types.h> |
17 | | #include <ctype.h> |
18 | | |
19 | | #include <crm/crm.h> |
20 | | #include <crm/lrmd.h> |
21 | | #include <crm/common/xml.h> |
22 | | #include <crm/common/util.h> |
23 | | #include <crm/common/scheduler.h> |
24 | | |
25 | | /*! |
26 | | * \internal |
27 | | * \brief Get string equivalent of an action type |
28 | | * |
29 | | * \param[in] action Action type |
30 | | * |
31 | | * \return Static string describing \p action |
32 | | */ |
33 | | const char * |
34 | | pcmk__action_text(enum pcmk__action_type action) |
35 | 0 | { |
36 | 0 | switch (action) { |
37 | 0 | case pcmk__action_stop: |
38 | 0 | return PCMK_ACTION_STOP; |
39 | | |
40 | 0 | case pcmk__action_stopped: |
41 | 0 | return PCMK_ACTION_STOPPED; |
42 | | |
43 | 0 | case pcmk__action_start: |
44 | 0 | return PCMK_ACTION_START; |
45 | | |
46 | 0 | case pcmk__action_started: |
47 | 0 | return PCMK_ACTION_RUNNING; |
48 | | |
49 | 0 | case pcmk__action_shutdown: |
50 | 0 | return PCMK_ACTION_DO_SHUTDOWN; |
51 | | |
52 | 0 | case pcmk__action_fence: |
53 | 0 | return PCMK_ACTION_STONITH; |
54 | | |
55 | 0 | case pcmk__action_monitor: |
56 | 0 | return PCMK_ACTION_MONITOR; |
57 | | |
58 | 0 | case pcmk__action_notify: |
59 | 0 | return PCMK_ACTION_NOTIFY; |
60 | | |
61 | 0 | case pcmk__action_notified: |
62 | 0 | return PCMK_ACTION_NOTIFIED; |
63 | | |
64 | 0 | case pcmk__action_promote: |
65 | 0 | return PCMK_ACTION_PROMOTE; |
66 | | |
67 | 0 | case pcmk__action_promoted: |
68 | 0 | return PCMK_ACTION_PROMOTED; |
69 | | |
70 | 0 | case pcmk__action_demote: |
71 | 0 | return PCMK_ACTION_DEMOTE; |
72 | | |
73 | 0 | case pcmk__action_demoted: |
74 | 0 | return PCMK_ACTION_DEMOTED; |
75 | | |
76 | 0 | default: // pcmk__action_unspecified or invalid |
77 | 0 | return "no_action"; |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | /*! |
82 | | * \internal |
83 | | * \brief Parse an action type from an action name |
84 | | * |
85 | | * \param[in] action_name Action name |
86 | | * |
87 | | * \return Action type corresponding to \p action_name |
88 | | */ |
89 | | enum pcmk__action_type |
90 | | pcmk__parse_action(const char *action_name) |
91 | 0 | { |
92 | 0 | if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) { |
93 | 0 | return pcmk__action_stop; |
94 | |
|
95 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOPPED, pcmk__str_none)) { |
96 | 0 | return pcmk__action_stopped; |
97 | |
|
98 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) { |
99 | 0 | return pcmk__action_start; |
100 | |
|
101 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_RUNNING, pcmk__str_none)) { |
102 | 0 | return pcmk__action_started; |
103 | |
|
104 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_DO_SHUTDOWN, |
105 | 0 | pcmk__str_none)) { |
106 | 0 | return pcmk__action_shutdown; |
107 | |
|
108 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_STONITH, pcmk__str_none)) { |
109 | 0 | return pcmk__action_fence; |
110 | |
|
111 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_MONITOR, pcmk__str_none)) { |
112 | 0 | return pcmk__action_monitor; |
113 | |
|
114 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFY, pcmk__str_none)) { |
115 | 0 | return pcmk__action_notify; |
116 | |
|
117 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFIED, |
118 | 0 | pcmk__str_none)) { |
119 | 0 | return pcmk__action_notified; |
120 | |
|
121 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) { |
122 | 0 | return pcmk__action_promote; |
123 | |
|
124 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)) { |
125 | 0 | return pcmk__action_demote; |
126 | |
|
127 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTED, |
128 | 0 | pcmk__str_none)) { |
129 | 0 | return pcmk__action_promoted; |
130 | |
|
131 | 0 | } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTED, pcmk__str_none)) { |
132 | 0 | return pcmk__action_demoted; |
133 | 0 | } |
134 | 0 | return pcmk__action_unspecified; |
135 | 0 | } |
136 | | |
137 | | /*! |
138 | | * \internal |
139 | | * \brief Get string equivalent of a failure handling type |
140 | | * |
141 | | * \param[in] on_fail Failure handling type |
142 | | * |
143 | | * \return Static string describing \p on_fail |
144 | | */ |
145 | | const char * |
146 | | pcmk__on_fail_text(enum pcmk__on_fail on_fail) |
147 | 0 | { |
148 | 0 | switch (on_fail) { |
149 | 0 | case pcmk__on_fail_ignore: |
150 | 0 | return "ignore"; |
151 | | |
152 | 0 | case pcmk__on_fail_demote: |
153 | 0 | return "demote"; |
154 | | |
155 | 0 | case pcmk__on_fail_block: |
156 | 0 | return "block"; |
157 | | |
158 | 0 | case pcmk__on_fail_restart: |
159 | 0 | return "recover"; |
160 | | |
161 | 0 | case pcmk__on_fail_ban: |
162 | 0 | return "migrate"; |
163 | | |
164 | 0 | case pcmk__on_fail_stop: |
165 | 0 | return "stop"; |
166 | | |
167 | 0 | case pcmk__on_fail_fence_node: |
168 | 0 | return "fence"; |
169 | | |
170 | 0 | case pcmk__on_fail_standby_node: |
171 | 0 | return "standby"; |
172 | | |
173 | 0 | case pcmk__on_fail_restart_container: |
174 | 0 | return "restart-container"; |
175 | | |
176 | 0 | case pcmk__on_fail_reset_remote: |
177 | 0 | return "reset-remote"; |
178 | 0 | } |
179 | 0 | return "<unknown>"; |
180 | 0 | } |
181 | | |
182 | | /*! |
183 | | * \internal |
184 | | * \brief Free an action object |
185 | | * |
186 | | * \param[in,out] user_data Action object to free |
187 | | */ |
188 | | void |
189 | | pcmk__free_action(gpointer user_data) |
190 | 0 | { |
191 | 0 | pcmk_action_t *action = user_data; |
192 | |
|
193 | 0 | if (action == NULL) { |
194 | 0 | return; |
195 | 0 | } |
196 | 0 | g_list_free_full(action->actions_before, free); |
197 | 0 | g_list_free_full(action->actions_after, free); |
198 | 0 | if (action->extra != NULL) { |
199 | 0 | g_hash_table_destroy(action->extra); |
200 | 0 | } |
201 | 0 | if (action->meta != NULL) { |
202 | 0 | g_hash_table_destroy(action->meta); |
203 | 0 | } |
204 | 0 | pcmk__free_node_copy(action->node); |
205 | 0 | free(action->cancel_task); |
206 | 0 | free(action->reason); |
207 | 0 | free(action->task); |
208 | 0 | free(action->uuid); |
209 | 0 | free(action); |
210 | 0 | } |
211 | | |
212 | | /*! |
213 | | * \brief Generate an operation key (RESOURCE_ACTION_INTERVAL) |
214 | | * |
215 | | * \param[in] rsc_id ID of resource being operated on |
216 | | * \param[in] op_type Operation name |
217 | | * \param[in] interval_ms Operation interval |
218 | | * |
219 | | * \return Newly allocated memory containing operation key as string |
220 | | * |
221 | | * \note This function asserts on errors, so it will never return NULL. |
222 | | * The caller is responsible for freeing the result with free(). |
223 | | */ |
224 | | char * |
225 | | pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms) |
226 | 0 | { |
227 | 0 | pcmk__assert((rsc_id != NULL) && (op_type != NULL)); |
228 | 0 | return pcmk__assert_asprintf(PCMK__OP_FMT, rsc_id, op_type, interval_ms); |
229 | 0 | } |
230 | | |
231 | | static inline gboolean |
232 | | convert_interval(const char *s, guint *interval_ms) |
233 | 0 | { |
234 | 0 | unsigned long l; |
235 | |
|
236 | 0 | errno = 0; |
237 | 0 | l = strtoul(s, NULL, 10); |
238 | |
|
239 | 0 | if (errno != 0) { |
240 | 0 | return FALSE; |
241 | 0 | } |
242 | | |
243 | 0 | *interval_ms = (guint) l; |
244 | 0 | return TRUE; |
245 | 0 | } |
246 | | |
247 | | /*! |
248 | | * \internal |
249 | | * \brief Check for underbar-separated substring match |
250 | | * |
251 | | * \param[in] key Overall string being checked |
252 | | * \param[in] position Match before underbar at this \p key index |
253 | | * \param[in] matches Substrings to match (may contain underbars) |
254 | | * |
255 | | * \return \p key index of underbar before any matching substring, |
256 | | * or 0 if none |
257 | | */ |
258 | | static size_t |
259 | | match_before(const char *key, size_t position, const char **matches) |
260 | 0 | { |
261 | 0 | for (int i = 0; matches[i] != NULL; ++i) { |
262 | 0 | const size_t match_len = strlen(matches[i]); |
263 | | |
264 | | // Must have at least X_MATCH before position |
265 | 0 | if (position > (match_len + 1)) { |
266 | 0 | const size_t possible = position - match_len - 1; |
267 | |
|
268 | 0 | if ((key[possible] == '_') |
269 | 0 | && (strncmp(key + possible + 1, matches[i], match_len) == 0)) { |
270 | 0 | return possible; |
271 | 0 | } |
272 | 0 | } |
273 | 0 | } |
274 | 0 | return 0; |
275 | 0 | } |
276 | | |
277 | | gboolean |
278 | | parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms) |
279 | 0 | { |
280 | 0 | guint local_interval_ms = 0; |
281 | 0 | const size_t key_len = (key == NULL)? 0 : strlen(key); |
282 | | |
283 | | // Operation keys must be formatted as RSC_ACTION_INTERVAL |
284 | 0 | size_t action_underbar = 0; // Index in key of underbar before ACTION |
285 | 0 | size_t interval_underbar = 0; // Index in key of underbar before INTERVAL |
286 | 0 | size_t possible = 0; |
287 | | |
288 | | /* Underbar was a poor choice of separator since both RSC and ACTION can |
289 | | * contain underbars. Here, list action names and name prefixes that can. |
290 | | */ |
291 | 0 | const char *actions_with_underbars[] = { |
292 | 0 | PCMK_ACTION_MIGRATE_FROM, |
293 | 0 | PCMK_ACTION_MIGRATE_TO, |
294 | 0 | NULL |
295 | 0 | }; |
296 | 0 | const char *action_prefixes_with_underbars[] = { |
297 | 0 | "pre_" PCMK_ACTION_NOTIFY, |
298 | 0 | "post_" PCMK_ACTION_NOTIFY, |
299 | 0 | "confirmed-pre_" PCMK_ACTION_NOTIFY, |
300 | 0 | "confirmed-post_" PCMK_ACTION_NOTIFY, |
301 | 0 | NULL, |
302 | 0 | }; |
303 | | |
304 | | // Initialize output variables in case of early return |
305 | 0 | if (rsc_id) { |
306 | 0 | *rsc_id = NULL; |
307 | 0 | } |
308 | 0 | if (op_type) { |
309 | 0 | *op_type = NULL; |
310 | 0 | } |
311 | 0 | if (interval_ms) { |
312 | 0 | *interval_ms = 0; |
313 | 0 | } |
314 | | |
315 | | // RSC_ACTION_INTERVAL implies a minimum of 5 characters |
316 | 0 | if (key_len < 5) { |
317 | 0 | return FALSE; |
318 | 0 | } |
319 | | |
320 | | // Find, parse, and validate interval |
321 | 0 | interval_underbar = key_len - 2; |
322 | 0 | while ((interval_underbar > 2) && (key[interval_underbar] != '_')) { |
323 | 0 | --interval_underbar; |
324 | 0 | } |
325 | 0 | if ((interval_underbar == 2) |
326 | 0 | || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) { |
327 | 0 | return FALSE; |
328 | 0 | } |
329 | | |
330 | | // Find the base (OCF) action name, disregarding prefixes |
331 | 0 | action_underbar = match_before(key, interval_underbar, |
332 | 0 | actions_with_underbars); |
333 | 0 | if (action_underbar == 0) { |
334 | 0 | action_underbar = interval_underbar - 2; |
335 | 0 | while ((action_underbar > 0) && (key[action_underbar] != '_')) { |
336 | 0 | --action_underbar; |
337 | 0 | } |
338 | 0 | if (action_underbar == 0) { |
339 | 0 | return FALSE; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | possible = match_before(key, action_underbar, |
343 | 0 | action_prefixes_with_underbars); |
344 | 0 | if (possible != 0) { |
345 | 0 | action_underbar = possible; |
346 | 0 | } |
347 | | |
348 | | // Set output variables |
349 | 0 | if (rsc_id != NULL) { |
350 | 0 | *rsc_id = strndup(key, action_underbar); |
351 | 0 | pcmk__mem_assert(*rsc_id); |
352 | 0 | } |
353 | 0 | if (op_type != NULL) { |
354 | 0 | *op_type = strndup(key + action_underbar + 1, |
355 | 0 | interval_underbar - action_underbar - 1); |
356 | 0 | pcmk__mem_assert(*op_type); |
357 | 0 | } |
358 | 0 | if (interval_ms != NULL) { |
359 | 0 | *interval_ms = local_interval_ms; |
360 | 0 | } |
361 | 0 | return TRUE; |
362 | 0 | } |
363 | | |
364 | | char * |
365 | | pcmk__notify_key(const char *rsc_id, const char *notify_type, |
366 | | const char *op_type) |
367 | 0 | { |
368 | 0 | CRM_CHECK(rsc_id != NULL, return NULL); |
369 | 0 | CRM_CHECK(op_type != NULL, return NULL); |
370 | 0 | CRM_CHECK(notify_type != NULL, return NULL); |
371 | 0 | return pcmk__assert_asprintf("%s_%s_notify_%s_0", |
372 | 0 | rsc_id, notify_type, op_type); |
373 | 0 | } |
374 | | |
375 | | /*! |
376 | | * \brief Parse a transition magic string into its constituent parts |
377 | | * |
378 | | * \param[in] magic Magic string to parse (must be non-NULL) |
379 | | * \param[out] uuid If non-NULL, where to store copy of parsed UUID |
380 | | * \param[out] transition_id If non-NULL, where to store parsed transition ID |
381 | | * \param[out] action_id If non-NULL, where to store parsed action ID |
382 | | * \param[out] op_status If non-NULL, where to store parsed result status |
383 | | * \param[out] op_rc If non-NULL, where to store parsed actual rc |
384 | | * \param[out] target_rc If non-NULL, where to stored parsed target rc |
385 | | * |
386 | | * \return TRUE if key was valid, FALSE otherwise |
387 | | * \note If uuid is supplied and this returns TRUE, the caller is responsible |
388 | | * for freeing the memory for *uuid using free(). |
389 | | */ |
390 | | gboolean |
391 | | decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id, |
392 | | int *op_status, int *op_rc, int *target_rc) |
393 | 0 | { |
394 | 0 | int res = 0; |
395 | 0 | char *key = NULL; |
396 | 0 | gboolean result = TRUE; |
397 | 0 | int local_op_status = -1; |
398 | 0 | int local_op_rc = -1; |
399 | |
|
400 | 0 | CRM_CHECK(magic != NULL, return FALSE); |
401 | | |
402 | | #ifdef HAVE_SSCANF_M |
403 | | res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key); |
404 | | #else |
405 | | // magic must have >=4 other characters |
406 | 0 | key = pcmk__assert_alloc(strlen(magic) - 3, sizeof(char)); |
407 | 0 | res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key); |
408 | 0 | #endif |
409 | 0 | if (res == EOF) { |
410 | 0 | pcmk__err("Could not decode transition information '%s': %s", magic, |
411 | 0 | pcmk_rc_str(errno)); |
412 | 0 | result = FALSE; |
413 | 0 | } else if (res < 3) { |
414 | 0 | pcmk__warn("Transition information '%s' incomplete (%d of 3 expected " |
415 | 0 | "items)", |
416 | 0 | magic, res); |
417 | 0 | result = FALSE; |
418 | 0 | } else { |
419 | 0 | if (op_status) { |
420 | 0 | *op_status = local_op_status; |
421 | 0 | } |
422 | 0 | if (op_rc) { |
423 | 0 | *op_rc = local_op_rc; |
424 | 0 | } |
425 | 0 | result = decode_transition_key(key, uuid, transition_id, action_id, |
426 | 0 | target_rc); |
427 | 0 | } |
428 | 0 | free(key); |
429 | 0 | return result; |
430 | 0 | } |
431 | | |
432 | | char * |
433 | | pcmk__transition_key(int transition_id, int action_id, int target_rc, |
434 | | const char *node) |
435 | 0 | { |
436 | 0 | CRM_CHECK(node != NULL, return NULL); |
437 | 0 | return pcmk__assert_asprintf("%d:%d:%d:%-*s", |
438 | 0 | action_id, transition_id, target_rc, 36, node); |
439 | 0 | } |
440 | | |
441 | | /*! |
442 | | * \brief Parse a transition key into its constituent parts |
443 | | * |
444 | | * \param[in] key Transition key to parse (must be non-NULL) |
445 | | * \param[out] uuid If non-NULL, where to store copy of parsed UUID |
446 | | * \param[out] transition_id If non-NULL, where to store parsed transition ID |
447 | | * \param[out] action_id If non-NULL, where to store parsed action ID |
448 | | * \param[out] target_rc If non-NULL, where to stored parsed target rc |
449 | | * |
450 | | * \return TRUE if key was valid, FALSE otherwise |
451 | | * \note If uuid is supplied and this returns TRUE, the caller is responsible |
452 | | * for freeing the memory for *uuid using free(). |
453 | | */ |
454 | | gboolean |
455 | | decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id, |
456 | | int *target_rc) |
457 | 0 | { |
458 | 0 | int local_transition_id = -1; |
459 | 0 | int local_action_id = -1; |
460 | 0 | int local_target_rc = -1; |
461 | 0 | char local_uuid[37] = { '\0' }; |
462 | | |
463 | | // Initialize any supplied output arguments |
464 | 0 | if (uuid) { |
465 | 0 | *uuid = NULL; |
466 | 0 | } |
467 | 0 | if (transition_id) { |
468 | 0 | *transition_id = -1; |
469 | 0 | } |
470 | 0 | if (action_id) { |
471 | 0 | *action_id = -1; |
472 | 0 | } |
473 | 0 | if (target_rc) { |
474 | 0 | *target_rc = -1; |
475 | 0 | } |
476 | |
|
477 | 0 | CRM_CHECK(key != NULL, return FALSE); |
478 | 0 | if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id, |
479 | 0 | &local_target_rc, local_uuid) != 4) { |
480 | 0 | pcmk__err("Invalid transition key '%s'", key); |
481 | 0 | return FALSE; |
482 | 0 | } |
483 | 0 | if (strlen(local_uuid) != 36) { |
484 | 0 | pcmk__warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key); |
485 | 0 | } |
486 | 0 | if (uuid) { |
487 | 0 | *uuid = pcmk__str_copy(local_uuid); |
488 | 0 | } |
489 | 0 | if (transition_id) { |
490 | 0 | *transition_id = local_transition_id; |
491 | 0 | } |
492 | 0 | if (action_id) { |
493 | 0 | *action_id = local_action_id; |
494 | 0 | } |
495 | 0 | if (target_rc) { |
496 | 0 | *target_rc = local_target_rc; |
497 | 0 | } |
498 | 0 | return TRUE; |
499 | 0 | } |
500 | | |
501 | | int |
502 | | rsc_op_expected_rc(const lrmd_event_data_t *op) |
503 | 0 | { |
504 | 0 | int rc = 0; |
505 | |
|
506 | 0 | if (op && op->user_data) { |
507 | 0 | decode_transition_key(op->user_data, NULL, NULL, NULL, &rc); |
508 | 0 | } |
509 | 0 | return rc; |
510 | 0 | } |
511 | | |
512 | | gboolean |
513 | | did_rsc_op_fail(lrmd_event_data_t * op, int target_rc) |
514 | 0 | { |
515 | 0 | switch (op->op_status) { |
516 | 0 | case PCMK_EXEC_CANCELLED: |
517 | 0 | case PCMK_EXEC_PENDING: |
518 | 0 | return FALSE; |
519 | | |
520 | 0 | case PCMK_EXEC_NOT_SUPPORTED: |
521 | 0 | case PCMK_EXEC_TIMEOUT: |
522 | 0 | case PCMK_EXEC_ERROR: |
523 | 0 | case PCMK_EXEC_NOT_CONNECTED: |
524 | 0 | case PCMK_EXEC_NO_FENCE_DEVICE: |
525 | 0 | case PCMK_EXEC_NO_SECRETS: |
526 | 0 | case PCMK_EXEC_INVALID: |
527 | 0 | return TRUE; |
528 | | |
529 | 0 | default: |
530 | 0 | if (target_rc != op->rc) { |
531 | 0 | return TRUE; |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | 0 | return FALSE; |
536 | 0 | } |
537 | | |
538 | | /*! |
539 | | * \brief Create a CIB XML element for an operation |
540 | | * |
541 | | * \param[in,out] parent If not NULL, make new XML node a child of this |
542 | | * \param[in] prefix Generate an ID using this prefix |
543 | | * \param[in] task Operation task to set |
544 | | * \param[in] interval_spec Operation interval to set |
545 | | * \param[in] timeout If not NULL, operation timeout to set |
546 | | * |
547 | | * \return New XML object on success, NULL otherwise |
548 | | */ |
549 | | xmlNode * |
550 | | crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task, |
551 | | const char *interval_spec, const char *timeout) |
552 | 0 | { |
553 | 0 | xmlNode *xml_op; |
554 | |
|
555 | 0 | CRM_CHECK(prefix && task && interval_spec, return NULL); |
556 | | |
557 | 0 | xml_op = pcmk__xe_create(parent, PCMK_XE_OP); |
558 | 0 | pcmk__xe_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec); |
559 | 0 | pcmk__xe_set(xml_op, PCMK_META_INTERVAL, interval_spec); |
560 | 0 | pcmk__xe_set(xml_op, PCMK_XA_NAME, task); |
561 | 0 | if (timeout) { |
562 | 0 | pcmk__xe_set(xml_op, PCMK_META_TIMEOUT, timeout); |
563 | 0 | } |
564 | 0 | return xml_op; |
565 | 0 | } |
566 | | |
567 | | /*! |
568 | | * \brief Check whether an operation requires resource agent meta-data |
569 | | * |
570 | | * \param[in] rsc_class Resource agent class (or NULL to skip class check) |
571 | | * \param[in] op Operation action (or NULL to skip op check) |
572 | | * |
573 | | * \return true if operation needs meta-data, false otherwise |
574 | | * \note At least one of rsc_class and op must be specified. |
575 | | */ |
576 | | bool |
577 | | crm_op_needs_metadata(const char *rsc_class, const char *op) |
578 | 0 | { |
579 | | /* Agent metadata is used to determine whether an agent reload is possible, |
580 | | * so if this op is not relevant to that feature, we don't need metadata. |
581 | | */ |
582 | |
|
583 | 0 | CRM_CHECK((rsc_class != NULL) || (op != NULL), return false); |
584 | | |
585 | 0 | if ((rsc_class != NULL) |
586 | 0 | && !pcmk__is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) { |
587 | | // Metadata is needed only for resource classes that use parameters |
588 | 0 | return false; |
589 | 0 | } |
590 | 0 | if (op == NULL) { |
591 | 0 | return true; |
592 | 0 | } |
593 | | |
594 | | // Metadata is needed only for these actions |
595 | 0 | return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR, |
596 | 0 | PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE, |
597 | 0 | PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT, |
598 | 0 | PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, |
599 | 0 | PCMK_ACTION_NOTIFY, NULL); |
600 | 0 | } |
601 | | |
602 | | /*! |
603 | | * \internal |
604 | | * \brief Check whether an action name is for a fencing action |
605 | | * |
606 | | * \param[in] action Action name to check |
607 | | * |
608 | | * \return \c true if \p action is \c PCMK_ACTION_OFF or \c PCMK_ACTION_REBOOT, |
609 | | * or \c false otherwise |
610 | | */ |
611 | | bool |
612 | | pcmk__is_fencing_action(const char *action) |
613 | 0 | { |
614 | 0 | return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT, NULL); |
615 | 0 | } |