root/lib/pacemaker/pcmk_sched_recurring.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. xe_interval
  2. is_op_dup
  3. op_cannot_recur
  4. is_recurring_history
  5. active_recurring_should_be_optional
  6. recurring_op_for_active
  7. cancel_if_running
  8. order_after_probes
  9. order_after_stops
  10. recurring_op_for_inactive
  11. pcmk__create_recurring_actions
  12. pcmk__new_cancel_action
  13. pcmk__schedule_cancel
  14. pcmk__reschedule_recurring
  15. pcmk__action_is_recurring

   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 xe_interval(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  42 {
  43     guint interval_ms = 0U;
  44 
  45     pcmk_parse_interval_spec(crm_element_value(xml, PCMK_META_INTERVAL),
  46                              &interval_ms);
  47     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 is_op_dup(const pcmk_resource_t *rsc, const char *name, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
  63 {
  64     const char *id = NULL;
  65 
  66     for (xmlNode *op = pcmk__xe_first_child(rsc->priv->ops_xml, PCMK_XE_OP,
  67                                             NULL, NULL);
  68          op != NULL; op = pcmk__xe_next(op, PCMK_XE_OP)) {
  69 
  70         // Check whether action name and interval match
  71         if (!pcmk__str_eq(crm_element_value(op, PCMK_XA_NAME), name,
  72                           pcmk__str_none)
  73             || (xe_interval(op) != interval_ms)) {
  74             continue;
  75         }
  76 
  77         if (pcmk__xe_id(op) == NULL) {
  78             continue; // Shouldn't be possible
  79         }
  80 
  81         if (id == NULL) {
  82             id = pcmk__xe_id(op); // First matching op
  83         } else {
  84             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             return true;
  88         }
  89     }
  90     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 op_cannot_recur(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 111 {
 112     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 is_recurring_history(const pcmk_resource_t *rsc, const xmlNode *xml,
     /* [previous][next][first][last][top][bottom][index][help] */
 131                      struct op_history *op)
 132 {
 133     const char *role = NULL;
 134 
 135     op->interval_ms = xe_interval(xml);
 136     if (op->interval_ms == 0) {
 137         return false; // Not recurring
 138     }
 139 
 140     op->id = pcmk__xe_id(xml);
 141     if (pcmk__str_empty(op->id)) {
 142         pcmk__config_err("Ignoring resource history entry without ID");
 143         return false; // Shouldn't be possible (unless CIB was manually edited)
 144     }
 145 
 146     op->name = crm_element_value(xml, PCMK_XA_NAME);
 147     if (op_cannot_recur(op->name)) {
 148         pcmk__config_err("Ignoring %s because %s action cannot be recurring",
 149                          op->id, pcmk__s(op->name, "unnamed"));
 150         return false;
 151     }
 152 
 153     // There should only be one recurring operation per action/interval
 154     if (is_op_dup(rsc, op->name, op->interval_ms)) {
 155         return false;
 156     }
 157 
 158     // Ensure role is valid if specified
 159     role = crm_element_value(xml, PCMK_XA_ROLE);
 160     if (role == NULL) {
 161         op->role = pcmk_role_unknown;
 162     } else {
 163         op->role = pcmk_parse_role(role);
 164         if (op->role == pcmk_role_unknown) {
 165             pcmk__config_err("Ignoring %s role because %s is not a valid role",
 166                              op->id, role);
 167             return false;
 168         }
 169     }
 170 
 171     // Only actions that are still configured and enabled matter
 172     if (pcmk__find_action_config(rsc, op->name, op->interval_ms,
 173                                  false) == NULL) {
 174         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         return false;
 180     }
 181 
 182     op->key = pcmk__op_key(rsc->id, op->name, op->interval_ms);
 183     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 active_recurring_should_be_optional(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 199                                     const pcmk_node_t *node, const char *key,
 200                                     pcmk_action_t *start)
 201 {
 202     GList *possible_matches = NULL;
 203 
 204     if (node == NULL) { // Should only be possible if unmanaged and stopped
 205         pcmk__rsc_trace(rsc,
 206                         "%s will be mandatory because resource is unmanaged",
 207                         key);
 208         return false;
 209     }
 210 
 211     if (!pcmk_is_set(rsc->priv->cmds->action_flags(start, NULL),
 212                      pcmk__action_optional)) {
 213         pcmk__rsc_trace(rsc, "%s will be mandatory because %s is",
 214                         key, start->uuid);
 215         return false;
 216     }
 217 
 218     possible_matches = find_actions_exact(rsc->priv->actions, key, node);
 219     if (possible_matches == NULL) {
 220         pcmk__rsc_trace(rsc,
 221                         "%s will be mandatory because it is not active on %s",
 222                         key, pcmk__node_name(node));
 223         return false;
 224     }
 225 
 226     for (const GList *iter = possible_matches;
 227          iter != NULL; iter = iter->next) {
 228 
 229         const pcmk_action_t *op = (const pcmk_action_t *) iter->data;
 230 
 231         if (pcmk_is_set(op->flags, pcmk__action_reschedule)) {
 232             pcmk__rsc_trace(rsc,
 233                             "%s will be mandatory because "
 234                             "it needs to be rescheduled", key);
 235             g_list_free(possible_matches);
 236             return false;
 237         }
 238     }
 239 
 240     g_list_free(possible_matches);
 241     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 recurring_op_for_active(pcmk_resource_t *rsc, pcmk_action_t *start,
     /* [previous][next][first][last][top][bottom][index][help] */
 255                         const pcmk_node_t *node, const struct op_history *op)
 256 {
 257     pcmk_action_t *mon = NULL;
 258     bool is_optional = true;
 259     bool role_match = false;
 260     enum rsc_role_e monitor_role = op->role;
 261 
 262     // We're only interested in recurring actions for active roles
 263     if (monitor_role == pcmk_role_stopped) {
 264         return;
 265     }
 266 
 267     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     if (monitor_role == pcmk_role_unknown) {
 272         monitor_role = pcmk_role_unpromoted;
 273         role_match = (rsc->priv->next_role != pcmk_role_promoted);
 274     } else {
 275         role_match = (rsc->priv->next_role == monitor_role);
 276     }
 277 
 278     if (!role_match) {
 279         if (is_optional) { // It's running, so cancel it
 280             char *after_key = NULL;
 281             pcmk_action_t *cancel_op = pcmk__new_cancel_action(rsc, op->name,
 282                                                                op->interval_ms,
 283                                                                node);
 284 
 285             switch (rsc->priv->orig_role) {
 286                 case pcmk_role_unpromoted:
 287                 case pcmk_role_started:
 288                     if (rsc->priv->next_role == pcmk_role_promoted) {
 289                         after_key = promote_key(rsc);
 290 
 291                     } else if (rsc->priv->next_role == pcmk_role_stopped) {
 292                         after_key = stop_key(rsc);
 293                     }
 294 
 295                     break;
 296                 case pcmk_role_promoted:
 297                     after_key = demote_key(rsc);
 298                     break;
 299                 default:
 300                     break;
 301             }
 302 
 303             if (after_key) {
 304                 pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL,
 305                                    pcmk__ar_unrunnable_first_blocks,
 306                                    rsc->priv->scheduler);
 307             }
 308         }
 309 
 310         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->priv->next_role));
 316         return;
 317     }
 318 
 319     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->priv->next_role),
 323                     pcmk__node_name(node));
 324 
 325     mon = custom_action(rsc, strdup(op->key), op->name, node, is_optional,
 326                         rsc->priv->scheduler);
 327 
 328     if (!pcmk_is_set(start->flags, pcmk__action_runnable)) {
 329         pcmk__rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid);
 330         pcmk__clear_action_flags(mon, pcmk__action_runnable);
 331 
 332     } else if ((node == NULL) || !node->details->online
 333                || node->details->unclean) {
 334         pcmk__rsc_trace(rsc, "%s is unrunnable because no node is available",
 335                         mon->uuid);
 336         pcmk__clear_action_flags(mon, pcmk__action_runnable);
 337 
 338     } else if (!pcmk_is_set(mon->flags, pcmk__action_optional)) {
 339         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     if (rsc->priv->next_role == pcmk_role_promoted) {
 345         pe__add_action_expected_result(mon, CRM_EX_PROMOTED);
 346     }
 347 
 348     // Order monitor relative to other actions
 349     if ((node == NULL) || pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 350         pcmk__new_ordering(rsc, start_key(rsc), NULL,
 351                            NULL, strdup(mon->uuid), mon,
 352                            pcmk__ar_first_implies_then
 353                            |pcmk__ar_unrunnable_first_blocks,
 354                            rsc->priv->scheduler);
 355 
 356         pcmk__new_ordering(rsc, reload_key(rsc), NULL,
 357                            NULL, strdup(mon->uuid), mon,
 358                            pcmk__ar_first_implies_then
 359                            |pcmk__ar_unrunnable_first_blocks,
 360                            rsc->priv->scheduler);
 361 
 362         if (rsc->priv->next_role == pcmk_role_promoted) {
 363             pcmk__new_ordering(rsc, promote_key(rsc), NULL,
 364                                rsc, NULL, mon,
 365                                pcmk__ar_ordered
 366                                |pcmk__ar_unrunnable_first_blocks,
 367                                rsc->priv->scheduler);
 368 
 369         } else if (rsc->priv->orig_role == pcmk_role_promoted) {
 370             pcmk__new_ordering(rsc, demote_key(rsc), NULL,
 371                                rsc, NULL, mon,
 372                                pcmk__ar_ordered
 373                                |pcmk__ar_unrunnable_first_blocks,
 374                                rsc->priv->scheduler);
 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 cancel_if_running(pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 391                   const char *key, const char *name, guint interval_ms)
 392 {
 393     GList *possible_matches = find_actions_exact(rsc->priv->actions, key,
 394                                                  node);
 395     pcmk_action_t *cancel_op = NULL;
 396 
 397     if (possible_matches == NULL) {
 398         return; // Recurring action isn't running on this node
 399     }
 400     g_list_free(possible_matches);
 401 
 402     cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node);
 403 
 404     switch (rsc->priv->next_role) {
 405         case pcmk_role_started:
 406         case pcmk_role_unpromoted:
 407             /* Order starts after cancel. If the current role is
 408              * stopped, this cancels the monitor before the resource
 409              * starts; if the current role is started, then this cancels
 410              * the monitor on a migration target before starting there.
 411              */
 412             pcmk__new_ordering(rsc, NULL, cancel_op,
 413                                rsc, start_key(rsc), NULL,
 414                                pcmk__ar_unrunnable_first_blocks,
 415                                rsc->priv->scheduler);
 416             break;
 417         default:
 418             break;
 419     }
 420     pcmk__rsc_info(rsc,
 421                    "Cancelling %s-interval %s action for %s on %s because "
 422                    "configured for " PCMK_ROLE_STOPPED " role (not %s)",
 423                    pcmk__readable_interval(interval_ms), name, rsc->id,
 424                    pcmk__node_name(node),
 425                    pcmk_role_text(rsc->priv->next_role));
 426 }
 427 
 428 /*!
 429  * \internal
 430  * \brief Order an action after all probes of a resource on a node
 431  *
 432  * \param[in,out] rsc     Resource to check for probes
 433  * \param[in]     node    Node to check for probes of \p rsc
 434  * \param[in,out] action  Action to order after probes of \p rsc on \p node
 435  */
 436 static void
 437 order_after_probes(pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 438                    pcmk_action_t *action)
 439 {
 440     GList *probes = pe__resource_actions(rsc, node, PCMK_ACTION_MONITOR, FALSE);
 441 
 442     for (GList *iter = probes; iter != NULL; iter = iter->next) {
 443         order_actions((pcmk_action_t *) iter->data, action,
 444                       pcmk__ar_unrunnable_first_blocks);
 445     }
 446     g_list_free(probes);
 447 }
 448 
 449 /*!
 450  * \internal
 451  * \brief Order an action after all stops of a resource on a node
 452  *
 453  * \param[in,out] rsc     Resource to check for stops
 454  * \param[in]     node    Node to check for stops of \p rsc
 455  * \param[in,out] action  Action to order after stops of \p rsc on \p node
 456  */
 457 static void
 458 order_after_stops(pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 459                   pcmk_action_t *action)
 460 {
 461     GList *stop_ops = pe__resource_actions(rsc, node, PCMK_ACTION_STOP, TRUE);
 462 
 463     for (GList *iter = stop_ops; iter != NULL; iter = iter->next) {
 464         pcmk_action_t *stop = (pcmk_action_t *) iter->data;
 465 
 466         if (!pcmk_is_set(stop->flags, pcmk__action_optional)
 467             && !pcmk_is_set(action->flags, pcmk__action_optional)
 468             && !pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 469             pcmk__rsc_trace(rsc, "%s optional on %s: unmanaged",
 470                             action->uuid, pcmk__node_name(node));
 471             pcmk__set_action_flags(action, pcmk__action_optional);
 472         }
 473 
 474         if (!pcmk_is_set(stop->flags, pcmk__action_runnable)) {
 475             crm_debug("%s unrunnable on %s: stop is unrunnable",
 476                       action->uuid, pcmk__node_name(node));
 477             pcmk__clear_action_flags(action, pcmk__action_runnable);
 478         }
 479 
 480         if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 481             pcmk__new_ordering(rsc, stop_key(rsc), stop,
 482                                NULL, NULL, action,
 483                                pcmk__ar_first_implies_then
 484                                |pcmk__ar_unrunnable_first_blocks,
 485                                rsc->priv->scheduler);
 486         }
 487     }
 488     g_list_free(stop_ops);
 489 }
 490 
 491 /*!
 492  * \internal
 493  * \brief Create recurring action from resource history entry for inactive role
 494  *
 495  * \param[in,out] rsc    Resource that resource history is for
 496  * \param[in]     node   Node that resource will be active on (if any)
 497  * \param[in]     op     Resource history entry
 498  */
 499 static void
 500 recurring_op_for_inactive(pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 501                           const struct op_history *op)
 502 {
 503     GList *possible_matches = NULL;
 504 
 505     // We're only interested in recurring actions for the inactive role
 506     if (op->role != pcmk_role_stopped) {
 507         return;
 508     }
 509 
 510     if (!pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
 511         crm_notice("Ignoring %s (recurring monitors for " PCMK_ROLE_STOPPED
 512                    " role are not supported for anonymous clones)", op->id);
 513         return; // @TODO add support
 514     }
 515 
 516     pcmk__rsc_trace(rsc,
 517                     "Creating recurring action %s for %s on nodes "
 518                     "where it should not be running", op->id, rsc->id);
 519 
 520     for (GList *iter = rsc->priv->scheduler->nodes;
 521          iter != NULL; iter = iter->next) {
 522 
 523         pcmk_node_t *stop_node = (pcmk_node_t *) iter->data;
 524 
 525         bool is_optional = true;
 526         pcmk_action_t *stopped_mon = NULL;
 527 
 528         // Cancel action on node where resource will be active
 529         if ((node != NULL)
 530             && pcmk__str_eq(stop_node->priv->name, node->priv->name,
 531                             pcmk__str_casei)) {
 532             cancel_if_running(rsc, node, op->key, op->name, op->interval_ms);
 533             continue;
 534         }
 535 
 536         // Recurring action on this node is optional if it's already active here
 537         possible_matches = find_actions_exact(rsc->priv->actions, op->key,
 538                                               stop_node);
 539         is_optional = (possible_matches != NULL);
 540         g_list_free(possible_matches);
 541 
 542         pcmk__rsc_trace(rsc,
 543                         "Creating %s recurring action %s for %s (%s "
 544                         PCMK_ROLE_STOPPED " on %s)",
 545                         (is_optional? "optional" : "mandatory"),
 546                         op->key, op->id, rsc->id, pcmk__node_name(stop_node));
 547 
 548         stopped_mon = custom_action(rsc, strdup(op->key), op->name, stop_node,
 549                                     is_optional, rsc->priv->scheduler);
 550 
 551         pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING);
 552 
 553         if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 554             order_after_probes(rsc, stop_node, stopped_mon);
 555         }
 556 
 557         /* The recurring action is for the inactive role, so it shouldn't be
 558          * performed until the resource is inactive.
 559          */
 560         order_after_stops(rsc, stop_node, stopped_mon);
 561 
 562         if (!stop_node->details->online || stop_node->details->unclean) {
 563             pcmk__rsc_debug(rsc, "%s unrunnable on %s: node unavailable)",
 564                             stopped_mon->uuid, pcmk__node_name(stop_node));
 565             pcmk__clear_action_flags(stopped_mon, pcmk__action_runnable);
 566         }
 567 
 568         if (pcmk_is_set(stopped_mon->flags, pcmk__action_runnable)
 569             && !pcmk_is_set(stopped_mon->flags, pcmk__action_optional)) {
 570             crm_notice("Start recurring %s-interval %s for "
 571                        PCMK_ROLE_STOPPED " %s on %s",
 572                        pcmk__readable_interval(op->interval_ms),
 573                        stopped_mon->task, rsc->id, pcmk__node_name(stop_node));
 574         }
 575     }
 576 }
 577 
 578 /*!
 579  * \internal
 580  * \brief Create recurring actions for a resource
 581  *
 582  * \param[in,out] rsc  Resource to create recurring actions for
 583  */
 584 void
 585 pcmk__create_recurring_actions(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 586 {
 587     pcmk_action_t *start = NULL;
 588 
 589     if (pcmk_is_set(rsc->flags, pcmk__rsc_blocked)) {
 590         pcmk__rsc_trace(rsc,
 591                         "Skipping recurring actions for blocked resource %s",
 592                         rsc->id);
 593         return;
 594     }
 595 
 596     if (pcmk_is_set(rsc->flags, pcmk__rsc_maintenance)) {
 597         pcmk__rsc_trace(rsc,
 598                         "Skipping recurring actions for %s "
 599                         "in maintenance mode", rsc->id);
 600         return;
 601     }
 602 
 603     if (rsc->priv->assigned_node == NULL) {
 604         // Recurring actions for active roles not needed
 605 
 606     } else if (rsc->priv->assigned_node->details->maintenance) {
 607         pcmk__rsc_trace(rsc,
 608                         "Skipping recurring actions for %s on %s "
 609                         "in maintenance mode",
 610                         rsc->id, pcmk__node_name(rsc->priv->assigned_node));
 611 
 612     } else if ((rsc->priv->next_role != pcmk_role_stopped)
 613                || !pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 614         // Recurring actions for active roles needed
 615         start = start_action(rsc, rsc->priv->assigned_node, TRUE);
 616     }
 617 
 618     pcmk__rsc_trace(rsc, "Creating any recurring actions needed for %s",
 619                     rsc->id);
 620 
 621     for (xmlNode *op = pcmk__xe_first_child(rsc->priv->ops_xml, PCMK_XE_OP,
 622                                             NULL, NULL);
 623          op != NULL; op = pcmk__xe_next(op, PCMK_XE_OP)) {
 624 
 625         struct op_history op_history = { NULL, };
 626 
 627         if (!is_recurring_history(rsc, op, &op_history)) {
 628             continue;
 629         }
 630 
 631         if (start != NULL) {
 632             recurring_op_for_active(rsc, start, rsc->priv->assigned_node,
 633                                     &op_history);
 634         }
 635         recurring_op_for_inactive(rsc, rsc->priv->assigned_node,
 636                                   &op_history);
 637 
 638         free(op_history.key);
 639     }
 640 }
 641 
 642 /*!
 643  * \internal
 644  * \brief Create an executor cancel action
 645  *
 646  * \param[in,out] rsc          Resource of action to cancel
 647  * \param[in]     task         Name of action to cancel
 648  * \param[in]     interval_ms  Interval of action to cancel
 649  * \param[in]     node         Node of action to cancel
 650  *
 651  * \return Created op
 652  */
 653 pcmk_action_t *
 654 pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
 655                         guint interval_ms, const pcmk_node_t *node)
 656 {
 657     pcmk_action_t *cancel_op = NULL;
 658     char *key = NULL;
 659     char *interval_ms_s = NULL;
 660 
 661     pcmk__assert((rsc != NULL) && (task != NULL) && (node != NULL));
 662 
 663     key = pcmk__op_key(rsc->id, task, interval_ms);
 664 
 665     /* This finds an existing action by key, so custom_action() does not change
 666      * cancel_op->task.
 667      */
 668     cancel_op = custom_action(rsc, key, PCMK_ACTION_CANCEL, node, FALSE,
 669                               rsc->priv->scheduler);
 670 
 671     pcmk__str_update(&(cancel_op->task), PCMK_ACTION_CANCEL);
 672     pcmk__str_update(&(cancel_op->cancel_task), task);
 673 
 674     interval_ms_s = crm_strdup_printf("%u", interval_ms);
 675     pcmk__insert_meta(cancel_op, PCMK_XA_OPERATION, task);
 676     pcmk__insert_meta(cancel_op, PCMK_META_INTERVAL, interval_ms_s);
 677     free(interval_ms_s);
 678 
 679     return cancel_op;
 680 }
 681 
 682 /*!
 683  * \internal
 684  * \brief Schedule cancellation of a recurring action
 685  *
 686  * \param[in,out] rsc          Resource that action is for
 687  * \param[in]     call_id      Action's call ID from history
 688  * \param[in]     task         Action name
 689  * \param[in]     interval_ms  Action interval
 690  * \param[in]     node         Node that history entry is for
 691  * \param[in]     reason       Short description of why action is cancelled
 692  */
 693 void
 694 pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 695                       const char *task, guint interval_ms,
 696                       const pcmk_node_t *node, const char *reason)
 697 {
 698     pcmk_action_t *cancel = NULL;
 699 
 700     CRM_CHECK((rsc != NULL) && (task != NULL)
 701               && (node != NULL) && (reason != NULL),
 702               return);
 703 
 704     crm_info("Recurring %s-interval %s for %s will be stopped on %s: %s",
 705              pcmk__readable_interval(interval_ms), task, rsc->id,
 706              pcmk__node_name(node), reason);
 707     cancel = pcmk__new_cancel_action(rsc, task, interval_ms, node);
 708     pcmk__insert_meta(cancel, PCMK__XA_CALL_ID, call_id);
 709 
 710     // Cancellations happen after stops
 711     pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel,
 712                        pcmk__ar_ordered, rsc->priv->scheduler);
 713 }
 714 
 715 /*!
 716  * \internal
 717  * \brief Create a recurring action marked as needing rescheduling if active
 718  *
 719  * \param[in,out] rsc          Resource that action is for
 720  * \param[in]     task         Name of action being rescheduled
 721  * \param[in]     interval_ms  Action interval (in milliseconds)
 722  * \param[in,out] node         Node where action should be rescheduled
 723  */
 724 void
 725 pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
 726                            guint interval_ms, pcmk_node_t *node)
 727 {
 728     pcmk_action_t *op = NULL;
 729 
 730     trigger_unfencing(rsc, node, "Device parameters changed (reschedule)",
 731                       NULL, rsc->priv->scheduler);
 732     op = custom_action(rsc, pcmk__op_key(rsc->id, task, interval_ms),
 733                        task, node, TRUE, rsc->priv->scheduler);
 734     pcmk__set_action_flags(op, pcmk__action_reschedule);
 735 }
 736 
 737 /*!
 738  * \internal
 739  * \brief Check whether an action is recurring
 740  *
 741  * \param[in] action  Action to check
 742  *
 743  * \return true if \p action has a nonzero interval, otherwise false
 744  */
 745 bool
 746 pcmk__action_is_recurring(const pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 747 {
 748     guint interval_ms = 0;
 749 
 750     if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
 751                               &interval_ms) != pcmk_rc_ok) {
 752         return false;
 753     }
 754     return (interval_ms > 0);
 755 }

/* [previous][next][first][last][top][bottom][index][help] */