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 General Public License version 2
7 : * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <stdbool.h>
13 :
14 : #include <crm/common/xml.h>
15 : #include <crm/common/scheduler_internal.h>
16 : #include <pacemaker-internal.h>
17 :
18 : #include "libpacemaker_private.h"
19 :
20 : // Information parsed from an operation history entry in the CIB
21 : struct op_history {
22 : // XML attributes
23 : const char *id; // ID of history entry
24 : const char *name; // Action name
25 :
26 : // Parsed information
27 : char *key; // Operation key for action
28 : enum rsc_role_e role; // Action role (or pcmk_role_unknown for default)
29 : guint interval_ms; // Action interval
30 : };
31 :
32 : /*!
33 : * \internal
34 : * \brief Parse an interval from XML
35 : *
36 : * \param[in] xml XML containing an interval attribute
37 : *
38 : * \return Interval parsed from XML (or 0 as default)
39 : */
40 : static guint
41 0 : xe_interval(const xmlNode *xml)
42 : {
43 0 : guint interval_ms = 0U;
44 :
45 0 : pcmk_parse_interval_spec(crm_element_value(xml, PCMK_META_INTERVAL),
46 : &interval_ms);
47 0 : return interval_ms;
48 : }
49 :
50 : /*!
51 : * \internal
52 : * \brief Check whether an operation exists multiple times in resource history
53 : *
54 : * \param[in] rsc Resource with history to search
55 : * \param[in] name Name of action to search for
56 : * \param[in] interval_ms Interval (in milliseconds) of action to search for
57 : *
58 : * \return true if an operation with \p name and \p interval_ms exists more than
59 : * once in the operation history of \p rsc, otherwise false
60 : */
61 : static bool
62 0 : is_op_dup(const pcmk_resource_t *rsc, const char *name, guint interval_ms)
63 : {
64 0 : const char *id = NULL;
65 :
66 0 : for (xmlNode *op = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP, NULL,
67 : NULL);
68 0 : op != NULL; op = pcmk__xe_next_same(op)) {
69 :
70 : // Check whether action name and interval match
71 0 : if (!pcmk__str_eq(crm_element_value(op, PCMK_XA_NAME), name,
72 : pcmk__str_none)
73 0 : || (xe_interval(op) != interval_ms)) {
74 0 : continue;
75 : }
76 :
77 0 : if (pcmk__xe_id(op) == NULL) {
78 0 : continue; // Shouldn't be possible
79 : }
80 :
81 0 : if (id == NULL) {
82 0 : id = pcmk__xe_id(op); // First matching op
83 : } else {
84 0 : pcmk__config_err("Operation %s is duplicate of %s (do not use "
85 : "same name and interval combination more "
86 : "than once per resource)", pcmk__xe_id(op), id);
87 0 : return true;
88 : }
89 : }
90 0 : return false;
91 : }
92 :
93 : /*!
94 : * \internal
95 : * \brief Check whether an action name is one that can be recurring
96 : *
97 : * \param[in] name Action name to check
98 : *
99 : * \return true if \p name is an action known to be unsuitable as a recurring
100 : * operation, otherwise false
101 : *
102 : * \note Pacemaker's current philosophy is to allow users to configure recurring
103 : * operations except for a short list of actions known not to be suitable
104 : * for that (as opposed to allowing only actions known to be suitable,
105 : * which includes only monitor). Among other things, this approach allows
106 : * users to define their own custom operations and make them recurring,
107 : * though that use case is not well tested.
108 : */
109 : static bool
110 0 : op_cannot_recur(const char *name)
111 : {
112 0 : return pcmk__str_any_of(name, PCMK_ACTION_STOP, PCMK_ACTION_START,
113 : PCMK_ACTION_DEMOTE, PCMK_ACTION_PROMOTE,
114 : PCMK_ACTION_RELOAD_AGENT,
115 : PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
116 : NULL);
117 : }
118 :
119 : /*!
120 : * \internal
121 : * \brief Check whether a resource history entry is for a recurring action
122 : *
123 : * \param[in] rsc Resource that history entry is for
124 : * \param[in] xml XML of resource history entry to check
125 : * \param[out] op Where to store parsed info if recurring
126 : *
127 : * \return true if \p xml is for a recurring action, otherwise false
128 : */
129 : static bool
130 0 : is_recurring_history(const pcmk_resource_t *rsc, const xmlNode *xml,
131 : struct op_history *op)
132 : {
133 0 : const char *role = NULL;
134 :
135 0 : op->interval_ms = xe_interval(xml);
136 0 : if (op->interval_ms == 0) {
137 0 : return false; // Not recurring
138 : }
139 :
140 0 : op->id = pcmk__xe_id(xml);
141 0 : if (pcmk__str_empty(op->id)) {
142 0 : pcmk__config_err("Ignoring resource history entry without ID");
143 0 : return false; // Shouldn't be possible (unless CIB was manually edited)
144 : }
145 :
146 0 : op->name = crm_element_value(xml, PCMK_XA_NAME);
147 0 : if (op_cannot_recur(op->name)) {
148 0 : pcmk__config_err("Ignoring %s because %s action cannot be recurring",
149 : op->id, pcmk__s(op->name, "unnamed"));
150 0 : return false;
151 : }
152 :
153 : // There should only be one recurring operation per action/interval
154 0 : if (is_op_dup(rsc, op->name, op->interval_ms)) {
155 0 : return false;
156 : }
157 :
158 : // Ensure role is valid if specified
159 0 : role = crm_element_value(xml, PCMK_XA_ROLE);
160 0 : if (role == NULL) {
161 0 : op->role = pcmk_role_unknown;
162 : } else {
163 0 : op->role = pcmk_parse_role(role);
164 0 : if (op->role == pcmk_role_unknown) {
165 0 : pcmk__config_err("Ignoring %s role because %s is not a valid role",
166 : op->id, role);
167 0 : return false;
168 : }
169 : }
170 :
171 : // Only actions that are still configured and enabled matter
172 0 : if (pcmk__find_action_config(rsc, op->name, op->interval_ms,
173 : false) == NULL) {
174 0 : pcmk__rsc_trace(rsc,
175 : "Ignoring %s (%s-interval %s for %s) because it is "
176 : "disabled or no longer in configuration",
177 : op->id, pcmk__readable_interval(op->interval_ms),
178 : op->name, rsc->id);
179 0 : return false;
180 : }
181 :
182 0 : op->key = pcmk__op_key(rsc->id, op->name, op->interval_ms);
183 0 : return true;
184 : }
185 :
186 : /*!
187 : * \internal
188 : * \brief Check whether a recurring action for an active role should be optional
189 : *
190 : * \param[in] rsc Resource that recurring action is for
191 : * \param[in] node Node that \p rsc will be active on (if any)
192 : * \param[in] key Operation key for recurring action to check
193 : * \param[in,out] start Start action for \p rsc
194 : *
195 : * \return true if recurring action should be optional, otherwise false
196 : */
197 : static bool
198 0 : active_recurring_should_be_optional(const pcmk_resource_t *rsc,
199 : const pcmk_node_t *node, const char *key,
200 : pcmk_action_t *start)
201 : {
202 0 : GList *possible_matches = NULL;
203 :
204 0 : if (node == NULL) { // Should only be possible if unmanaged and stopped
205 0 : pcmk__rsc_trace(rsc,
206 : "%s will be mandatory because resource is unmanaged",
207 : key);
208 0 : return false;
209 : }
210 :
211 0 : if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL),
212 : pcmk_action_optional)) {
213 0 : pcmk__rsc_trace(rsc, "%s will be mandatory because %s is",
214 : key, start->uuid);
215 0 : return false;
216 : }
217 :
218 0 : possible_matches = find_actions_exact(rsc->actions, key, node);
219 0 : if (possible_matches == NULL) {
220 0 : pcmk__rsc_trace(rsc,
221 : "%s will be mandatory because it is not active on %s",
222 : key, pcmk__node_name(node));
223 0 : return false;
224 : }
225 :
226 0 : for (const GList *iter = possible_matches;
227 0 : iter != NULL; iter = iter->next) {
228 :
229 0 : const pcmk_action_t *op = (const pcmk_action_t *) iter->data;
230 :
231 0 : if (pcmk_is_set(op->flags, pcmk_action_reschedule)) {
232 0 : pcmk__rsc_trace(rsc,
233 : "%s will be mandatory because "
234 : "it needs to be rescheduled", key);
235 0 : g_list_free(possible_matches);
236 0 : return false;
237 : }
238 : }
239 :
240 0 : g_list_free(possible_matches);
241 0 : return true;
242 : }
243 :
244 : /*!
245 : * \internal
246 : * \brief Create recurring action from resource history entry for an active role
247 : *
248 : * \param[in,out] rsc Resource that resource history is for
249 : * \param[in,out] start Start action for \p rsc on \p node
250 : * \param[in] node Node that resource will be active on (if any)
251 : * \param[in] op Resource history entry
252 : */
253 : static void
254 0 : recurring_op_for_active(pcmk_resource_t *rsc, pcmk_action_t *start,
255 : const pcmk_node_t *node, const struct op_history *op)
256 : {
257 0 : pcmk_action_t *mon = NULL;
258 0 : bool is_optional = true;
259 0 : bool role_match = false;
260 0 : enum rsc_role_e monitor_role = op->role;
261 :
262 : // We're only interested in recurring actions for active roles
263 0 : if (monitor_role == pcmk_role_stopped) {
264 0 : return;
265 : }
266 :
267 0 : is_optional = active_recurring_should_be_optional(rsc, node, op->key,
268 : start);
269 :
270 : // Check whether monitor's role matches role resource will have
271 0 : if (monitor_role == pcmk_role_unknown) {
272 0 : monitor_role = pcmk_role_unpromoted;
273 0 : role_match = (rsc->next_role != pcmk_role_promoted);
274 : } else {
275 0 : role_match = (rsc->next_role == monitor_role);
276 : }
277 :
278 0 : if (!role_match) {
279 0 : if (is_optional) { // It's running, so cancel it
280 0 : char *after_key = NULL;
281 0 : pcmk_action_t *cancel_op = pcmk__new_cancel_action(rsc, op->name,
282 0 : op->interval_ms,
283 : node);
284 :
285 0 : switch (rsc->role) {
286 0 : case pcmk_role_unpromoted:
287 : case pcmk_role_started:
288 0 : if (rsc->next_role == pcmk_role_promoted) {
289 0 : after_key = promote_key(rsc);
290 :
291 0 : } else if (rsc->next_role == pcmk_role_stopped) {
292 0 : after_key = stop_key(rsc);
293 : }
294 :
295 0 : break;
296 0 : case pcmk_role_promoted:
297 0 : after_key = demote_key(rsc);
298 0 : break;
299 0 : default:
300 0 : break;
301 : }
302 :
303 0 : if (after_key) {
304 0 : pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL,
305 : pcmk__ar_unrunnable_first_blocks,
306 : rsc->cluster);
307 : }
308 : }
309 :
310 0 : do_crm_log((is_optional? LOG_INFO : LOG_TRACE),
311 : "%s recurring action %s because %s configured for %s role "
312 : "(not %s)",
313 : (is_optional? "Cancelling" : "Ignoring"), op->key, op->id,
314 : pcmk_role_text(monitor_role),
315 : pcmk_role_text(rsc->next_role));
316 0 : return;
317 : }
318 :
319 0 : pcmk__rsc_trace(rsc,
320 : "Creating %s recurring action %s for %s (%s %s on %s)",
321 : (is_optional? "optional" : "mandatory"), op->key,
322 : op->id, rsc->id, pcmk_role_text(rsc->next_role),
323 : pcmk__node_name(node));
324 :
325 0 : mon = custom_action(rsc, strdup(op->key), op->name, node, is_optional,
326 : rsc->cluster);
327 :
328 0 : if (!pcmk_is_set(start->flags, pcmk_action_runnable)) {
329 0 : pcmk__rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid);
330 0 : pcmk__clear_action_flags(mon, pcmk_action_runnable);
331 :
332 0 : } else if ((node == NULL) || !node->details->online
333 0 : || node->details->unclean) {
334 0 : pcmk__rsc_trace(rsc, "%s is unrunnable because no node is available",
335 : mon->uuid);
336 0 : pcmk__clear_action_flags(mon, pcmk_action_runnable);
337 :
338 0 : } else if (!pcmk_is_set(mon->flags, pcmk_action_optional)) {
339 0 : pcmk__rsc_info(rsc, "Start %s-interval %s for %s on %s",
340 : pcmk__readable_interval(op->interval_ms), mon->task,
341 : rsc->id, pcmk__node_name(node));
342 : }
343 :
344 0 : if (rsc->next_role == pcmk_role_promoted) {
345 0 : pe__add_action_expected_result(mon, CRM_EX_PROMOTED);
346 : }
347 :
348 : // Order monitor relative to other actions
349 0 : if ((node == NULL) || pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
350 0 : pcmk__new_ordering(rsc, start_key(rsc), NULL,
351 0 : NULL, strdup(mon->uuid), mon,
352 : pcmk__ar_first_implies_then
353 : |pcmk__ar_unrunnable_first_blocks,
354 : rsc->cluster);
355 :
356 0 : pcmk__new_ordering(rsc, reload_key(rsc), NULL,
357 0 : NULL, strdup(mon->uuid), mon,
358 : pcmk__ar_first_implies_then
359 : |pcmk__ar_unrunnable_first_blocks,
360 : rsc->cluster);
361 :
362 0 : if (rsc->next_role == pcmk_role_promoted) {
363 0 : pcmk__new_ordering(rsc, promote_key(rsc), NULL,
364 : rsc, NULL, mon,
365 : pcmk__ar_ordered
366 : |pcmk__ar_unrunnable_first_blocks,
367 : rsc->cluster);
368 :
369 0 : } else if (rsc->role == pcmk_role_promoted) {
370 0 : pcmk__new_ordering(rsc, demote_key(rsc), NULL,
371 : rsc, NULL, mon,
372 : pcmk__ar_ordered
373 : |pcmk__ar_unrunnable_first_blocks,
374 : rsc->cluster);
375 : }
376 : }
377 : }
378 :
379 : /*!
380 : * \internal
381 : * \brief Cancel a recurring action if running on a node
382 : *
383 : * \param[in,out] rsc Resource that action is for
384 : * \param[in] node Node to cancel action on
385 : * \param[in] key Operation key for action
386 : * \param[in] name Action name
387 : * \param[in] interval_ms Action interval (in milliseconds)
388 : */
389 : static void
390 0 : cancel_if_running(pcmk_resource_t *rsc, const pcmk_node_t *node,
391 : const char *key, const char *name, guint interval_ms)
392 : {
393 0 : GList *possible_matches = find_actions_exact(rsc->actions, key, node);
394 0 : pcmk_action_t *cancel_op = NULL;
395 :
396 0 : if (possible_matches == NULL) {
397 0 : return; // Recurring action isn't running on this node
398 : }
399 0 : g_list_free(possible_matches);
400 :
401 0 : cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node);
402 :
403 0 : switch (rsc->next_role) {
404 0 : case pcmk_role_started:
405 : case pcmk_role_unpromoted:
406 : /* Order starts after cancel. If the current role is
407 : * stopped, this cancels the monitor before the resource
408 : * starts; if the current role is started, then this cancels
409 : * the monitor on a migration target before starting there.
410 : */
411 0 : pcmk__new_ordering(rsc, NULL, cancel_op,
412 0 : rsc, start_key(rsc), NULL,
413 : pcmk__ar_unrunnable_first_blocks, rsc->cluster);
414 0 : break;
415 0 : default:
416 0 : break;
417 : }
418 0 : pcmk__rsc_info(rsc,
419 : "Cancelling %s-interval %s action for %s on %s because "
420 : "configured for " PCMK_ROLE_STOPPED " role (not %s)",
421 : pcmk__readable_interval(interval_ms), name, rsc->id,
422 : pcmk__node_name(node), pcmk_role_text(rsc->next_role));
423 : }
424 :
425 : /*!
426 : * \internal
427 : * \brief Order an action after all probes of a resource on a node
428 : *
429 : * \param[in,out] rsc Resource to check for probes
430 : * \param[in] node Node to check for probes of \p rsc
431 : * \param[in,out] action Action to order after probes of \p rsc on \p node
432 : */
433 : static void
434 0 : order_after_probes(pcmk_resource_t *rsc, const pcmk_node_t *node,
435 : pcmk_action_t *action)
436 : {
437 0 : GList *probes = pe__resource_actions(rsc, node, PCMK_ACTION_MONITOR, FALSE);
438 :
439 0 : for (GList *iter = probes; iter != NULL; iter = iter->next) {
440 0 : order_actions((pcmk_action_t *) iter->data, action,
441 : pcmk__ar_unrunnable_first_blocks);
442 : }
443 0 : g_list_free(probes);
444 0 : }
445 :
446 : /*!
447 : * \internal
448 : * \brief Order an action after all stops of a resource on a node
449 : *
450 : * \param[in,out] rsc Resource to check for stops
451 : * \param[in] node Node to check for stops of \p rsc
452 : * \param[in,out] action Action to order after stops of \p rsc on \p node
453 : */
454 : static void
455 0 : order_after_stops(pcmk_resource_t *rsc, const pcmk_node_t *node,
456 : pcmk_action_t *action)
457 : {
458 0 : GList *stop_ops = pe__resource_actions(rsc, node, PCMK_ACTION_STOP, TRUE);
459 :
460 0 : for (GList *iter = stop_ops; iter != NULL; iter = iter->next) {
461 0 : pcmk_action_t *stop = (pcmk_action_t *) iter->data;
462 :
463 0 : if (!pcmk_is_set(stop->flags, pcmk_action_optional)
464 0 : && !pcmk_is_set(action->flags, pcmk_action_optional)
465 0 : && !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
466 0 : pcmk__rsc_trace(rsc, "%s optional on %s: unmanaged",
467 : action->uuid, pcmk__node_name(node));
468 0 : pcmk__set_action_flags(action, pcmk_action_optional);
469 : }
470 :
471 0 : if (!pcmk_is_set(stop->flags, pcmk_action_runnable)) {
472 0 : crm_debug("%s unrunnable on %s: stop is unrunnable",
473 : action->uuid, pcmk__node_name(node));
474 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
475 : }
476 :
477 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
478 0 : pcmk__new_ordering(rsc, stop_key(rsc), stop,
479 : NULL, NULL, action,
480 : pcmk__ar_first_implies_then
481 : |pcmk__ar_unrunnable_first_blocks,
482 : rsc->cluster);
483 : }
484 : }
485 0 : g_list_free(stop_ops);
486 0 : }
487 :
488 : /*!
489 : * \internal
490 : * \brief Create recurring action from resource history entry for inactive role
491 : *
492 : * \param[in,out] rsc Resource that resource history is for
493 : * \param[in] node Node that resource will be active on (if any)
494 : * \param[in] op Resource history entry
495 : */
496 : static void
497 0 : recurring_op_for_inactive(pcmk_resource_t *rsc, const pcmk_node_t *node,
498 : const struct op_history *op)
499 : {
500 0 : GList *possible_matches = NULL;
501 :
502 : // We're only interested in recurring actions for the inactive role
503 0 : if (op->role != pcmk_role_stopped) {
504 0 : return;
505 : }
506 :
507 0 : if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
508 0 : crm_notice("Ignoring %s (recurring monitors for " PCMK_ROLE_STOPPED
509 : " role are not supported for anonymous clones)", op->id);
510 0 : return; // @TODO add support
511 : }
512 :
513 0 : pcmk__rsc_trace(rsc,
514 : "Creating recurring action %s for %s on nodes "
515 : "where it should not be running", op->id, rsc->id);
516 :
517 0 : for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) {
518 0 : pcmk_node_t *stop_node = (pcmk_node_t *) iter->data;
519 :
520 0 : bool is_optional = true;
521 0 : pcmk_action_t *stopped_mon = NULL;
522 :
523 : // Cancel action on node where resource will be active
524 0 : if ((node != NULL)
525 0 : && pcmk__str_eq(stop_node->details->uname, node->details->uname,
526 : pcmk__str_casei)) {
527 0 : cancel_if_running(rsc, node, op->key, op->name, op->interval_ms);
528 0 : continue;
529 : }
530 :
531 : // Recurring action on this node is optional if it's already active here
532 0 : possible_matches = find_actions_exact(rsc->actions, op->key, stop_node);
533 0 : is_optional = (possible_matches != NULL);
534 0 : g_list_free(possible_matches);
535 :
536 0 : pcmk__rsc_trace(rsc,
537 : "Creating %s recurring action %s for %s (%s "
538 : PCMK_ROLE_STOPPED " on %s)",
539 : (is_optional? "optional" : "mandatory"),
540 : op->key, op->id, rsc->id, pcmk__node_name(stop_node));
541 :
542 0 : stopped_mon = custom_action(rsc, strdup(op->key), op->name, stop_node,
543 : is_optional, rsc->cluster);
544 :
545 0 : pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING);
546 :
547 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
548 0 : order_after_probes(rsc, stop_node, stopped_mon);
549 : }
550 :
551 : /* The recurring action is for the inactive role, so it shouldn't be
552 : * performed until the resource is inactive.
553 : */
554 0 : order_after_stops(rsc, stop_node, stopped_mon);
555 :
556 0 : if (!stop_node->details->online || stop_node->details->unclean) {
557 0 : pcmk__rsc_debug(rsc, "%s unrunnable on %s: node unavailable)",
558 : stopped_mon->uuid, pcmk__node_name(stop_node));
559 0 : pcmk__clear_action_flags(stopped_mon, pcmk_action_runnable);
560 : }
561 :
562 0 : if (pcmk_is_set(stopped_mon->flags, pcmk_action_runnable)
563 0 : && !pcmk_is_set(stopped_mon->flags, pcmk_action_optional)) {
564 0 : crm_notice("Start recurring %s-interval %s for "
565 : PCMK_ROLE_STOPPED " %s on %s",
566 : pcmk__readable_interval(op->interval_ms),
567 : stopped_mon->task, rsc->id, pcmk__node_name(stop_node));
568 : }
569 : }
570 : }
571 :
572 : /*!
573 : * \internal
574 : * \brief Create recurring actions for a resource
575 : *
576 : * \param[in,out] rsc Resource to create recurring actions for
577 : */
578 : void
579 0 : pcmk__create_recurring_actions(pcmk_resource_t *rsc)
580 : {
581 0 : pcmk_action_t *start = NULL;
582 :
583 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
584 0 : pcmk__rsc_trace(rsc,
585 : "Skipping recurring actions for blocked resource %s",
586 : rsc->id);
587 0 : return;
588 : }
589 :
590 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) {
591 0 : pcmk__rsc_trace(rsc,
592 : "Skipping recurring actions for %s "
593 : "in maintenance mode", rsc->id);
594 0 : return;
595 : }
596 :
597 0 : if (rsc->allocated_to == NULL) {
598 : // Recurring actions for active roles not needed
599 :
600 0 : } else if (rsc->allocated_to->details->maintenance) {
601 0 : pcmk__rsc_trace(rsc,
602 : "Skipping recurring actions for %s on %s "
603 : "in maintenance mode",
604 : rsc->id, pcmk__node_name(rsc->allocated_to));
605 :
606 0 : } else if ((rsc->next_role != pcmk_role_stopped)
607 0 : || !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
608 : // Recurring actions for active roles needed
609 0 : start = start_action(rsc, rsc->allocated_to, TRUE);
610 : }
611 :
612 0 : pcmk__rsc_trace(rsc, "Creating any recurring actions needed for %s",
613 : rsc->id);
614 :
615 0 : for (xmlNode *op = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP, NULL,
616 : NULL);
617 0 : op != NULL; op = pcmk__xe_next_same(op)) {
618 :
619 0 : struct op_history op_history = { NULL, };
620 :
621 0 : if (!is_recurring_history(rsc, op, &op_history)) {
622 0 : continue;
623 : }
624 :
625 0 : if (start != NULL) {
626 0 : recurring_op_for_active(rsc, start, rsc->allocated_to, &op_history);
627 : }
628 0 : recurring_op_for_inactive(rsc, rsc->allocated_to, &op_history);
629 :
630 0 : free(op_history.key);
631 : }
632 : }
633 :
634 : /*!
635 : * \internal
636 : * \brief Create an executor cancel action
637 : *
638 : * \param[in,out] rsc Resource of action to cancel
639 : * \param[in] task Name of action to cancel
640 : * \param[in] interval_ms Interval of action to cancel
641 : * \param[in] node Node of action to cancel
642 : *
643 : * \return Created op
644 : */
645 : pcmk_action_t *
646 0 : pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *task,
647 : guint interval_ms, const pcmk_node_t *node)
648 : {
649 0 : pcmk_action_t *cancel_op = NULL;
650 0 : char *key = NULL;
651 0 : char *interval_ms_s = NULL;
652 :
653 0 : CRM_ASSERT((rsc != NULL) && (task != NULL) && (node != NULL));
654 :
655 : // @TODO dangerous if possible to schedule another action with this key
656 0 : key = pcmk__op_key(rsc->id, task, interval_ms);
657 :
658 0 : cancel_op = custom_action(rsc, key, PCMK_ACTION_CANCEL, node, FALSE,
659 : rsc->cluster);
660 :
661 0 : cancel_op->task = pcmk__str_copy(PCMK_ACTION_CANCEL);
662 0 : cancel_op->cancel_task = pcmk__str_copy(task);
663 :
664 0 : interval_ms_s = crm_strdup_printf("%u", interval_ms);
665 0 : pcmk__insert_meta(cancel_op, PCMK_XA_OPERATION, task);
666 0 : pcmk__insert_meta(cancel_op, PCMK_META_INTERVAL, interval_ms_s);
667 0 : free(interval_ms_s);
668 :
669 0 : return cancel_op;
670 : }
671 :
672 : /*!
673 : * \internal
674 : * \brief Schedule cancellation of a recurring action
675 : *
676 : * \param[in,out] rsc Resource that action is for
677 : * \param[in] call_id Action's call ID from history
678 : * \param[in] task Action name
679 : * \param[in] interval_ms Action interval
680 : * \param[in] node Node that history entry is for
681 : * \param[in] reason Short description of why action is cancelled
682 : */
683 : void
684 0 : pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id,
685 : const char *task, guint interval_ms,
686 : const pcmk_node_t *node, const char *reason)
687 : {
688 0 : pcmk_action_t *cancel = NULL;
689 :
690 0 : CRM_CHECK((rsc != NULL) && (task != NULL)
691 : && (node != NULL) && (reason != NULL),
692 : return);
693 :
694 0 : crm_info("Recurring %s-interval %s for %s will be stopped on %s: %s",
695 : pcmk__readable_interval(interval_ms), task, rsc->id,
696 : pcmk__node_name(node), reason);
697 0 : cancel = pcmk__new_cancel_action(rsc, task, interval_ms, node);
698 0 : pcmk__insert_meta(cancel, PCMK__XA_CALL_ID, call_id);
699 :
700 : // Cancellations happen after stops
701 0 : pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel,
702 : pcmk__ar_ordered, rsc->cluster);
703 : }
704 :
705 : /*!
706 : * \internal
707 : * \brief Create a recurring action marked as needing rescheduling if active
708 : *
709 : * \param[in,out] rsc Resource that action is for
710 : * \param[in] task Name of action being rescheduled
711 : * \param[in] interval_ms Action interval (in milliseconds)
712 : * \param[in,out] node Node where action should be rescheduled
713 : */
714 : void
715 0 : pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task,
716 : guint interval_ms, pcmk_node_t *node)
717 : {
718 0 : pcmk_action_t *op = NULL;
719 :
720 0 : trigger_unfencing(rsc, node, "Device parameters changed (reschedule)",
721 : NULL, rsc->cluster);
722 0 : op = custom_action(rsc, pcmk__op_key(rsc->id, task, interval_ms),
723 : task, node, TRUE, rsc->cluster);
724 0 : pcmk__set_action_flags(op, pcmk_action_reschedule);
725 0 : }
726 :
727 : /*!
728 : * \internal
729 : * \brief Check whether an action is recurring
730 : *
731 : * \param[in] action Action to check
732 : *
733 : * \return true if \p action has a nonzero interval, otherwise false
734 : */
735 : bool
736 0 : pcmk__action_is_recurring(const pcmk_action_t *action)
737 : {
738 0 : guint interval_ms = 0;
739 :
740 0 : if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
741 : &interval_ms) != pcmk_rc_ok) {
742 0 : return false;
743 : }
744 0 : return (interval_ms > 0);
745 : }
|