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