root/lib/pengine/pe_actions.c

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

DEFINITIONS

This source file includes following definitions.
  1. add_singleton
  2. lookup_singleton
  3. find_existing_action
  4. find_exact_action_config
  5. pcmk__find_action_config
  6. new_action
  7. pcmk__unpack_action_rsc_params
  8. update_action_optional
  9. effective_quorum_policy
  10. update_resource_action_runnable
  11. update_resource_flags_for_action
  12. valid_stop_on_fail
  13. validate_on_fail
  14. unpack_timeout
  15. unpack_interval_origin
  16. unpack_start_delay
  17. most_frequent_monitor
  18. pcmk__unpack_action_meta
  19. pcmk__action_requires
  20. pcmk__parse_on_fail
  21. pcmk__role_after_failure
  22. unpack_operation
  23. custom_action
  24. get_pseudo_op
  25. find_unfencing_devices
  26. node_priority_fencing_delay
  27. pe_fence_op
  28. pe_free_action
  29. get_complex_task
  30. find_first_action
  31. find_actions
  32. find_actions_exact
  33. pe__resource_actions
  34. pe__action2reason
  35. pe_action_set_reason
  36. pe__clear_resource_history
  37. pe__is_newer_op
  38. sort_op_by_callid
  39. pe__new_rsc_pseudo_action
  40. pe__add_action_expected_result

   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 #include <glib.h>
  13 #include <stdbool.h>
  14 
  15 #include <crm/crm.h>
  16 #include <crm/common/xml.h>
  17 #include <crm/common/scheduler_internal.h>
  18 #include <crm/pengine/internal.h>
  19 #include <crm/common/xml_internal.h>
  20 #include "pe_status_private.h"
  21 
  22 static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
  23                              guint interval_ms);
  24 
  25 static void
  26 add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
  27 {
  28     if (scheduler->singletons == NULL) {
  29         scheduler->singletons = pcmk__strkey_table(NULL, NULL);
  30     }
  31     g_hash_table_insert(scheduler->singletons, action->uuid, action);
  32 }
  33 
  34 static pcmk_action_t *
  35 lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid)
     /* [previous][next][first][last][top][bottom][index][help] */
  36 {
  37     if (scheduler->singletons == NULL) {
  38         return NULL;
  39     }
  40     return g_hash_table_lookup(scheduler->singletons, action_uuid);
  41 }
  42 
  43 /*!
  44  * \internal
  45  * \brief Find an existing action that matches arguments
  46  *
  47  * \param[in] key        Action key to match
  48  * \param[in] rsc        Resource to match (if any)
  49  * \param[in] node       Node to match (if any)
  50  * \param[in] scheduler  Scheduler data
  51  *
  52  * \return Existing action that matches arguments (or NULL if none)
  53  */
  54 static pcmk_action_t *
  55 find_existing_action(const char *key, const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
  56                      const pcmk_node_t *node, const pcmk_scheduler_t *scheduler)
  57 {
  58     GList *matches = NULL;
  59     pcmk_action_t *action = NULL;
  60 
  61     /* When rsc is NULL, it would be quicker to check scheduler->singletons,
  62      * but checking all scheduler->actions takes the node into account.
  63      */
  64     matches = find_actions(((rsc == NULL)? scheduler->actions : rsc->actions),
  65                            key, node);
  66     if (matches == NULL) {
  67         return NULL;
  68     }
  69     CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches));
  70 
  71     action = matches->data;
  72     g_list_free(matches);
  73     return action;
  74 }
  75 
  76 /*!
  77  * \internal
  78  * \brief Find the XML configuration corresponding to a specific action key
  79  *
  80  * \param[in] rsc               Resource to find action configuration for
  81  * \param[in] key               "RSC_ACTION_INTERVAL" of action to find
  82  * \param[in] include_disabled  If false, do not return disabled actions
  83  *
  84  * \return XML configuration of desired action if any, otherwise NULL
  85  */
  86 static xmlNode *
  87 find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name,
     /* [previous][next][first][last][top][bottom][index][help] */
  88                          guint interval_ms, bool include_disabled)
  89 {
  90     for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
  91                                                    NULL, NULL);
  92          operation != NULL; operation = pcmk__xe_next_same(operation)) {
  93 
  94         bool enabled = false;
  95         const char *config_name = NULL;
  96         const char *interval_spec = NULL;
  97         guint tmp_ms = 0U;
  98 
  99         // @TODO This does not consider meta-attributes, rules, defaults, etc.
 100         if (!include_disabled
 101             && (pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
 102                                        &enabled) == pcmk_rc_ok) && !enabled) {
 103             continue;
 104         }
 105 
 106         interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
 107         pcmk_parse_interval_spec(interval_spec, &tmp_ms);
 108         if (tmp_ms != interval_ms) {
 109             continue;
 110         }
 111 
 112         config_name = crm_element_value(operation, PCMK_XA_NAME);
 113         if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) {
 114             return operation;
 115         }
 116     }
 117     return NULL;
 118 }
 119 
 120 /*!
 121  * \internal
 122  * \brief Find the XML configuration of a resource action
 123  *
 124  * \param[in] rsc               Resource to find action configuration for
 125  * \param[in] action_name       Action name to search for
 126  * \param[in] interval_ms       Action interval (in milliseconds) to search for
 127  * \param[in] include_disabled  If false, do not return disabled actions
 128  *
 129  * \return XML configuration of desired action if any, otherwise NULL
 130  */
 131 xmlNode *
 132 pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 133                          guint interval_ms, bool include_disabled)
 134 {
 135     xmlNode *action_config = NULL;
 136 
 137     // Try requested action first
 138     action_config = find_exact_action_config(rsc, action_name, interval_ms,
 139                                              include_disabled);
 140 
 141     // For migrate_to and migrate_from actions, retry with "migrate"
 142     // @TODO This should be either documented or deprecated
 143     if ((action_config == NULL)
 144         && pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO,
 145                             PCMK_ACTION_MIGRATE_FROM, NULL)) {
 146         action_config = find_exact_action_config(rsc, "migrate", 0,
 147                                                  include_disabled);
 148     }
 149 
 150     return action_config;
 151 }
 152 
 153 /*!
 154  * \internal
 155  * \brief Create a new action object
 156  *
 157  * \param[in]     key        Action key
 158  * \param[in]     task       Action name
 159  * \param[in,out] rsc        Resource that action is for (if any)
 160  * \param[in]     node       Node that action is on (if any)
 161  * \param[in]     optional   Whether action should be considered optional
 162  * \param[in,out] scheduler  Scheduler data
 163  *
 164  * \return Newly allocated action
 165  * \note This function takes ownership of \p key. It is the caller's
 166  *       responsibility to free the return value with pe_free_action().
 167  */
 168 static pcmk_action_t *
 169 new_action(char *key, const char *task, pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 170            const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler)
 171 {
 172     pcmk_action_t *action = pcmk__assert_alloc(1, sizeof(pcmk_action_t));
 173 
 174     action->rsc = rsc;
 175     action->task = pcmk__str_copy(task);
 176     action->uuid = key;
 177 
 178     if (node) {
 179         action->node = pe__copy_node(node);
 180     }
 181 
 182     if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) {
 183         // Resource history deletion for a node can be done on the DC
 184         pcmk__set_action_flags(action, pcmk_action_on_dc);
 185     }
 186 
 187     pcmk__set_action_flags(action, pcmk_action_runnable);
 188     if (optional) {
 189         pcmk__set_action_flags(action, pcmk_action_optional);
 190     } else {
 191         pcmk__clear_action_flags(action, pcmk_action_optional);
 192     }
 193 
 194     if (rsc == NULL) {
 195         action->meta = pcmk__strkey_table(free, free);
 196     } else {
 197         guint interval_ms = 0;
 198 
 199         parse_op_key(key, NULL, NULL, &interval_ms);
 200         action->op_entry = pcmk__find_action_config(rsc, task, interval_ms,
 201                                                     true);
 202 
 203         /* If the given key is for one of the many notification pseudo-actions
 204          * (pre_notify_promote, etc.), the actual action name is "notify"
 205          */
 206         if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) {
 207             action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY,
 208                                                         0, true);
 209         }
 210 
 211         unpack_operation(action, action->op_entry, interval_ms);
 212     }
 213 
 214     pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s",
 215                     (optional? "optional" : "required"),
 216                     scheduler->action_id, key, task,
 217                     ((rsc == NULL)? "no resource" : rsc->id),
 218                     pcmk__node_name(node));
 219     action->id = scheduler->action_id++;
 220 
 221     scheduler->actions = g_list_prepend(scheduler->actions, action);
 222     if (rsc == NULL) {
 223         add_singleton(scheduler, action);
 224     } else {
 225         rsc->actions = g_list_prepend(rsc->actions, action);
 226     }
 227     return action;
 228 }
 229 
 230 /*!
 231  * \internal
 232  * \brief Unpack a resource's action-specific instance parameters
 233  *
 234  * \param[in]     action_xml  XML of action's configuration in CIB (if any)
 235  * \param[in,out] node_attrs  Table of node attributes (for rule evaluation)
 236  * \param[in,out] scheduler   Cluster working set (for rule evaluation)
 237  *
 238  * \return Newly allocated hash table of action-specific instance parameters
 239  */
 240 GHashTable *
 241 pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
     /* [previous][next][first][last][top][bottom][index][help] */
 242                                GHashTable *node_attrs,
 243                                pcmk_scheduler_t *scheduler)
 244 {
 245     GHashTable *params = pcmk__strkey_table(free, free);
 246 
 247     pe_rule_eval_data_t rule_data = {
 248         .node_hash = node_attrs,
 249         .now = scheduler->now,
 250         .match_data = NULL,
 251         .rsc_data = NULL,
 252         .op_data = NULL
 253     };
 254 
 255     pe__unpack_dataset_nvpairs(action_xml, PCMK_XE_INSTANCE_ATTRIBUTES,
 256                                &rule_data, params, NULL,
 257                                FALSE, scheduler);
 258     return params;
 259 }
 260 
 261 /*!
 262  * \internal
 263  * \brief Update an action's optional flag
 264  *
 265  * \param[in,out] action    Action to update
 266  * \param[in]     optional  Requested optional status
 267  */
 268 static void
 269 update_action_optional(pcmk_action_t *action, gboolean optional)
     /* [previous][next][first][last][top][bottom][index][help] */
 270 {
 271     // Force a non-recurring action to be optional if its resource is unmanaged
 272     if ((action->rsc != NULL) && (action->node != NULL)
 273         && !pcmk_is_set(action->flags, pcmk_action_pseudo)
 274         && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
 275         && (g_hash_table_lookup(action->meta, PCMK_META_INTERVAL) == NULL)) {
 276             pcmk__rsc_debug(action->rsc,
 277                             "%s on %s is optional (%s is unmanaged)",
 278                             action->uuid, pcmk__node_name(action->node),
 279                             action->rsc->id);
 280             pcmk__set_action_flags(action, pcmk_action_optional);
 281             // We shouldn't clear runnable here because ... something
 282 
 283     // Otherwise require the action if requested
 284     } else if (!optional) {
 285         pcmk__clear_action_flags(action, pcmk_action_optional);
 286     }
 287 }
 288 
 289 static enum pe_quorum_policy
 290 effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 291 {
 292     enum pe_quorum_policy policy = scheduler->no_quorum_policy;
 293 
 294     if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
 295         policy = pcmk_no_quorum_ignore;
 296 
 297     } else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) {
 298         switch (rsc->role) {
 299             case pcmk_role_promoted:
 300             case pcmk_role_unpromoted:
 301                 if (rsc->next_role > pcmk_role_unpromoted) {
 302                     pe__set_next_role(rsc, pcmk_role_unpromoted,
 303                                       PCMK_OPT_NO_QUORUM_POLICY "=demote");
 304                 }
 305                 policy = pcmk_no_quorum_ignore;
 306                 break;
 307             default:
 308                 policy = pcmk_no_quorum_stop;
 309                 break;
 310         }
 311     }
 312     return policy;
 313 }
 314 
 315 /*!
 316  * \internal
 317  * \brief Update a resource action's runnable flag
 318  *
 319  * \param[in,out] action     Action to update
 320  * \param[in,out] scheduler  Scheduler data
 321  *
 322  * \note This may also schedule fencing if a stop is unrunnable.
 323  */
 324 static void
 325 update_resource_action_runnable(pcmk_action_t *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 326                                 pcmk_scheduler_t *scheduler)
 327 {
 328     if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
 329         return;
 330     }
 331 
 332     if (action->node == NULL) {
 333         pcmk__rsc_trace(action->rsc, "%s is unrunnable (unallocated)",
 334                         action->uuid);
 335         pcmk__clear_action_flags(action, pcmk_action_runnable);
 336 
 337     } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
 338                && !(action->node->details->online)
 339                && (!pcmk__is_guest_or_bundle_node(action->node)
 340                    || action->node->details->remote_requires_reset)) {
 341         pcmk__clear_action_flags(action, pcmk_action_runnable);
 342         do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)",
 343                    action->uuid, pcmk__node_name(action->node));
 344         if (pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
 345             && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)
 346             && !(action->node->details->unclean)) {
 347             pe_fence_node(scheduler, action->node, "stop is unrunnable", false);
 348         }
 349 
 350     } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
 351                && action->node->details->pending) {
 352         pcmk__clear_action_flags(action, pcmk_action_runnable);
 353         do_crm_log(LOG_WARNING,
 354                    "Action %s on %s is unrunnable (node is pending)",
 355                    action->uuid, pcmk__node_name(action->node));
 356 
 357     } else if (action->needs == pcmk_requires_nothing) {
 358         pe_action_set_reason(action, NULL, TRUE);
 359         if (pcmk__is_guest_or_bundle_node(action->node)
 360             && !pe_can_fence(scheduler, action->node)) {
 361             /* An action that requires nothing usually does not require any
 362              * fencing in order to be runnable. However, there is an exception:
 363              * such an action cannot be completed if it is on a guest node whose
 364              * host is unclean and cannot be fenced.
 365              */
 366             pcmk__rsc_debug(action->rsc,
 367                             "%s on %s is unrunnable "
 368                             "(node's host cannot be fenced)",
 369                             action->uuid, pcmk__node_name(action->node));
 370             pcmk__clear_action_flags(action, pcmk_action_runnable);
 371         } else {
 372             pcmk__rsc_trace(action->rsc,
 373                             "%s on %s does not require fencing or quorum",
 374                             action->uuid, pcmk__node_name(action->node));
 375             pcmk__set_action_flags(action, pcmk_action_runnable);
 376         }
 377 
 378     } else {
 379         switch (effective_quorum_policy(action->rsc, scheduler)) {
 380             case pcmk_no_quorum_stop:
 381                 pcmk__rsc_debug(action->rsc,
 382                                 "%s on %s is unrunnable (no quorum)",
 383                                 action->uuid, pcmk__node_name(action->node));
 384                 pcmk__clear_action_flags(action, pcmk_action_runnable);
 385                 pe_action_set_reason(action, "no quorum", true);
 386                 break;
 387 
 388             case pcmk_no_quorum_freeze:
 389                 if (!action->rsc->fns->active(action->rsc, TRUE)
 390                     || (action->rsc->next_role > action->rsc->role)) {
 391                     pcmk__rsc_debug(action->rsc,
 392                                     "%s on %s is unrunnable (no quorum)",
 393                                     action->uuid,
 394                                     pcmk__node_name(action->node));
 395                     pcmk__clear_action_flags(action, pcmk_action_runnable);
 396                     pe_action_set_reason(action, "quorum freeze", true);
 397                 }
 398                 break;
 399 
 400             default:
 401                 //pe_action_set_reason(action, NULL, TRUE);
 402                 pcmk__set_action_flags(action, pcmk_action_runnable);
 403                 break;
 404         }
 405     }
 406 }
 407 
 408 /*!
 409  * \internal
 410  * \brief Update a resource object's flags for a new action on it
 411  *
 412  * \param[in,out] rsc     Resource that action is for (if any)
 413  * \param[in]     action  New action
 414  */
 415 static void
 416 update_resource_flags_for_action(pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 417                                  const pcmk_action_t *action)
 418 {
 419     /* @COMPAT pcmk_rsc_starting and pcmk_rsc_stopping are deprecated and unused
 420      * within Pacemaker, and will eventually be removed
 421      */
 422     if (pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)) {
 423         pcmk__set_rsc_flags(rsc, pcmk_rsc_stopping);
 424 
 425     } else if (pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_casei)) {
 426         if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
 427             pcmk__set_rsc_flags(rsc, pcmk_rsc_starting);
 428         } else {
 429             pcmk__clear_rsc_flags(rsc, pcmk_rsc_starting);
 430         }
 431     }
 432 }
 433 
 434 static bool
 435 valid_stop_on_fail(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 436 {
 437     return !pcmk__strcase_any_of(value,
 438                                  PCMK_VALUE_STANDBY, PCMK_VALUE_DEMOTE,
 439                                  PCMK_VALUE_STOP, NULL);
 440 }
 441 
 442 /*!
 443  * \internal
 444  * \brief Validate (and possibly reset) resource action's on_fail meta-attribute
 445  *
 446  * \param[in]     rsc            Resource that action is for
 447  * \param[in]     action_name    Action name
 448  * \param[in]     action_config  Action configuration XML from CIB (if any)
 449  * \param[in,out] meta           Table of action meta-attributes
 450  */
 451 static void
 452 validate_on_fail(const pcmk_resource_t *rsc, const char *action_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 453                  const xmlNode *action_config, GHashTable *meta)
 454 {
 455     const char *name = NULL;
 456     const char *role = NULL;
 457     const char *interval_spec = NULL;
 458     const char *value = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
 459     guint interval_ms = 0U;
 460 
 461     // Stop actions can only use certain on-fail values
 462     if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)
 463         && !valid_stop_on_fail(value)) {
 464 
 465         pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s stop "
 466                          "action to default value because '%s' is not "
 467                          "allowed for stop", rsc->id, value);
 468         g_hash_table_remove(meta, PCMK_META_ON_FAIL);
 469         return;
 470     }
 471 
 472     /* Demote actions default on-fail to the on-fail value for the first
 473      * recurring monitor for the promoted role (if any).
 474      */
 475     if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)
 476         && (value == NULL)) {
 477 
 478         /* @TODO This does not consider promote options set in a meta-attribute
 479          * block (which may have rules that need to be evaluated) rather than
 480          * XML properties.
 481          */
 482         for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
 483                                                        NULL, NULL);
 484              operation != NULL; operation = pcmk__xe_next_same(operation)) {
 485 
 486             bool enabled = false;
 487             const char *promote_on_fail = NULL;
 488 
 489             /* We only care about explicit on-fail (if promote uses default, so
 490              * can demote)
 491              */
 492             promote_on_fail = crm_element_value(operation, PCMK_META_ON_FAIL);
 493             if (promote_on_fail == NULL) {
 494                 continue;
 495             }
 496 
 497             // We only care about recurring monitors for the promoted role
 498             name = crm_element_value(operation, PCMK_XA_NAME);
 499             role = crm_element_value(operation, PCMK_XA_ROLE);
 500             if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
 501                 || !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
 502                                          PCMK__ROLE_PROMOTED_LEGACY, NULL)) {
 503                 continue;
 504             }
 505             interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
 506             pcmk_parse_interval_spec(interval_spec, &interval_ms);
 507             if (interval_ms == 0U) {
 508                 continue;
 509             }
 510 
 511             // We only care about enabled monitors
 512             if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
 513                                         &enabled) == pcmk_rc_ok) && !enabled) {
 514                 continue;
 515             }
 516 
 517             /* Demote actions can't default to
 518              * PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE
 519              */
 520             if (pcmk__str_eq(promote_on_fail, PCMK_VALUE_DEMOTE,
 521                              pcmk__str_casei)) {
 522                 continue;
 523             }
 524 
 525             // Use value from first applicable promote action found
 526             pcmk__insert_dup(meta, PCMK_META_ON_FAIL, promote_on_fail);
 527         }
 528         return;
 529     }
 530 
 531     if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none)
 532         && !pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) {
 533 
 534         pcmk__insert_dup(meta, PCMK_META_ON_FAIL, PCMK_VALUE_IGNORE);
 535         return;
 536     }
 537 
 538     // PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE is allowed only for certain actions
 539     if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
 540         name = crm_element_value(action_config, PCMK_XA_NAME);
 541         role = crm_element_value(action_config, PCMK_XA_ROLE);
 542         interval_spec = crm_element_value(action_config, PCMK_META_INTERVAL);
 543         pcmk_parse_interval_spec(interval_spec, &interval_ms);
 544 
 545         if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none)
 546             && ((interval_ms == 0U)
 547                 || !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
 548                 || !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
 549                                          PCMK__ROLE_PROMOTED_LEGACY, NULL))) {
 550 
 551             pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s %s "
 552                              "action to default value because 'demote' is not "
 553                              "allowed for it", rsc->id, name);
 554             g_hash_table_remove(meta, PCMK_META_ON_FAIL);
 555             return;
 556         }
 557     }
 558 }
 559 
 560 static int
 561 unpack_timeout(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 562 {
 563     long long timeout_ms = crm_get_msec(value);
 564 
 565     if (timeout_ms <= 0) {
 566         timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
 567     }
 568     return (int) QB_MIN(timeout_ms, INT_MAX);
 569 }
 570 
 571 // true if value contains valid, non-NULL interval origin for recurring op
 572 static bool
 573 unpack_interval_origin(const char *value, const xmlNode *xml_obj,
     /* [previous][next][first][last][top][bottom][index][help] */
 574                        guint interval_ms, const crm_time_t *now,
 575                        long long *start_delay)
 576 {
 577     long long result = 0;
 578     guint interval_sec = interval_ms / 1000;
 579     crm_time_t *origin = NULL;
 580 
 581     // Ignore unspecified values and non-recurring operations
 582     if ((value == NULL) || (interval_ms == 0) || (now == NULL)) {
 583         return false;
 584     }
 585 
 586     // Parse interval origin from text
 587     origin = crm_time_new(value);
 588     if (origin == NULL) {
 589         pcmk__config_err("Ignoring '" PCMK_META_INTERVAL_ORIGIN "' for "
 590                          "operation '%s' because '%s' is not valid",
 591                          pcmk__s(pcmk__xe_id(xml_obj), "(missing ID)"), value);
 592         return false;
 593     }
 594 
 595     // Get seconds since origin (negative if origin is in the future)
 596     result = crm_time_get_seconds(now) - crm_time_get_seconds(origin);
 597     crm_time_free(origin);
 598 
 599     // Calculate seconds from closest interval to now
 600     result = result % interval_sec;
 601 
 602     // Calculate seconds remaining until next interval
 603     result = ((result <= 0)? 0 : interval_sec) - result;
 604     crm_info("Calculated a start delay of %llds for operation '%s'",
 605              result, pcmk__s(pcmk__xe_id(xml_obj), "(unspecified)"));
 606 
 607     if (start_delay != NULL) {
 608         *start_delay = result * 1000; // milliseconds
 609     }
 610     return true;
 611 }
 612 
 613 static int
 614 unpack_start_delay(const char *value, GHashTable *meta)
     /* [previous][next][first][last][top][bottom][index][help] */
 615 {
 616     long long start_delay_ms = 0;
 617 
 618     if (value == NULL) {
 619         return 0;
 620     }
 621 
 622     start_delay_ms = crm_get_msec(value);
 623     start_delay_ms = QB_MIN(start_delay_ms, INT_MAX);
 624     if (start_delay_ms < 0) {
 625         start_delay_ms = 0;
 626     }
 627 
 628     if (meta != NULL) {
 629         g_hash_table_replace(meta, strdup(PCMK_META_START_DELAY),
 630                              pcmk__itoa(start_delay_ms));
 631     }
 632 
 633     return (int) start_delay_ms;
 634 }
 635 
 636 /*!
 637  * \internal
 638  * \brief Find a resource's most frequent recurring monitor
 639  *
 640  * \param[in] rsc  Resource to check
 641  *
 642  * \return Operation XML configured for most frequent recurring monitor for
 643  *         \p rsc (if any)
 644  */
 645 static xmlNode *
 646 most_frequent_monitor(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 647 {
 648     guint min_interval_ms = G_MAXUINT;
 649     xmlNode *op = NULL;
 650 
 651     for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
 652                                                    NULL, NULL);
 653          operation != NULL; operation = pcmk__xe_next_same(operation)) {
 654 
 655         bool enabled = false;
 656         guint interval_ms = 0U;
 657         const char *interval_spec = crm_element_value(operation,
 658                                                       PCMK_META_INTERVAL);
 659 
 660         // We only care about enabled recurring monitors
 661         if (!pcmk__str_eq(crm_element_value(operation, PCMK_XA_NAME),
 662                           PCMK_ACTION_MONITOR, pcmk__str_none)) {
 663             continue;
 664         }
 665 
 666         pcmk_parse_interval_spec(interval_spec, &interval_ms);
 667         if (interval_ms == 0U) {
 668             continue;
 669         }
 670 
 671         // @TODO This does not consider meta-attributes, rules, defaults, etc.
 672         if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
 673                                     &enabled) == pcmk_rc_ok) && !enabled) {
 674             continue;
 675         }
 676 
 677         if (interval_ms < min_interval_ms) {
 678             min_interval_ms = interval_ms;
 679             op = operation;
 680         }
 681     }
 682     return op;
 683 }
 684 
 685 /*!
 686  * \internal
 687  * \brief Unpack action meta-attributes
 688  *
 689  * \param[in,out] rsc            Resource that action is for
 690  * \param[in]     node           Node that action is on
 691  * \param[in]     action_name    Action name
 692  * \param[in]     interval_ms    Action interval (in milliseconds)
 693  * \param[in]     action_config  Action XML configuration from CIB (if any)
 694  *
 695  * Unpack a resource action's meta-attributes (normalizing the interval,
 696  * timeout, and start delay values as integer milliseconds) from its CIB XML
 697  * configuration (including defaults).
 698  *
 699  * \return Newly allocated hash table with normalized action meta-attributes
 700  */
 701 GHashTable *
 702 pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 703                          const char *action_name, guint interval_ms,
 704                          const xmlNode *action_config)
 705 {
 706     GHashTable *meta = NULL;
 707     const char *timeout_spec = NULL;
 708     const char *str = NULL;
 709 
 710     pe_rsc_eval_data_t rsc_rule_data = {
 711         .standard = crm_element_value(rsc->xml, PCMK_XA_CLASS),
 712         .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
 713         .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE),
 714     };
 715 
 716     pe_op_eval_data_t op_rule_data = {
 717         .op_name = action_name,
 718         .interval = interval_ms,
 719     };
 720 
 721     pe_rule_eval_data_t rule_data = {
 722         /* @COMPAT Support for node attribute expressions in operation
 723          * meta-attributes (whether in the operation configuration or operation
 724          * defaults) is deprecated. When we can break behavioral backward
 725          * compatibility, drop this line.
 726          */
 727         .node_hash = (node == NULL)? NULL : node->details->attrs,
 728 
 729         .now = rsc->cluster->now,
 730         .match_data = NULL,
 731         .rsc_data = &rsc_rule_data,
 732         .op_data = &op_rule_data,
 733     };
 734 
 735     meta = pcmk__strkey_table(free, free);
 736 
 737     // Cluster-wide <op_defaults> <meta_attributes>
 738     pe__unpack_dataset_nvpairs(rsc->cluster->op_defaults,
 739                                PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL,
 740                                FALSE, rsc->cluster);
 741 
 742     // Derive default timeout for probes from recurring monitor timeouts
 743     if (pcmk_is_probe(action_name, interval_ms)) {
 744         xmlNode *min_interval_mon = most_frequent_monitor(rsc);
 745 
 746         if (min_interval_mon != NULL) {
 747             /* @TODO This does not consider timeouts set in
 748              * PCMK_XE_META_ATTRIBUTES blocks (which may also have rules that
 749              * need to be evaluated).
 750              */
 751             timeout_spec = crm_element_value(min_interval_mon,
 752                                              PCMK_META_TIMEOUT);
 753             if (timeout_spec != NULL) {
 754                 pcmk__rsc_trace(rsc,
 755                                 "Setting default timeout for %s probe to "
 756                                 "most frequent monitor's timeout '%s'",
 757                                 rsc->id, timeout_spec);
 758                 pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
 759             }
 760         }
 761     }
 762 
 763     if (action_config != NULL) {
 764         // <op> <meta_attributes> take precedence over defaults
 765         pe__unpack_dataset_nvpairs(action_config, PCMK_XE_META_ATTRIBUTES,
 766                                    &rule_data, meta, NULL, TRUE, rsc->cluster);
 767 
 768         /* Anything set as an <op> XML property has highest precedence.
 769          * This ensures we use the name and interval from the <op> tag.
 770          * (See below for the only exception, fence device start/probe timeout.)
 771          */
 772         for (xmlAttrPtr attr = action_config->properties;
 773              attr != NULL; attr = attr->next) {
 774             pcmk__insert_dup(meta, (const char *) attr->name,
 775                              pcmk__xml_attr_value(attr));
 776         }
 777     }
 778 
 779     g_hash_table_remove(meta, PCMK_XA_ID);
 780 
 781     // Normalize interval to milliseconds
 782     if (interval_ms > 0) {
 783         g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_INTERVAL),
 784                             crm_strdup_printf("%u", interval_ms));
 785     } else {
 786         g_hash_table_remove(meta, PCMK_META_INTERVAL);
 787     }
 788 
 789     /* Timeout order of precedence (highest to lowest):
 790      *   1. pcmk_monitor_timeout resource parameter (only for starts and probes
 791      *      when rsc has pcmk_ra_cap_fence_params; this gets used for recurring
 792      *      monitors via the executor instead)
 793      *   2. timeout configured in <op> (with <op timeout> taking precedence over
 794      *      <op> <meta_attributes>)
 795      *   3. timeout configured in <op_defaults> <meta_attributes>
 796      *   4. PCMK_DEFAULT_ACTION_TIMEOUT_MS
 797      */
 798 
 799     // Check for pcmk_monitor_timeout
 800     if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard),
 801                     pcmk_ra_cap_fence_params)
 802         && (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)
 803             || pcmk_is_probe(action_name, interval_ms))) {
 804 
 805         GHashTable *params = pe_rsc_params(rsc, node, rsc->cluster);
 806 
 807         timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout");
 808         if (timeout_spec != NULL) {
 809             pcmk__rsc_trace(rsc,
 810                             "Setting timeout for %s %s to "
 811                             "pcmk_monitor_timeout (%s)",
 812                             rsc->id, action_name, timeout_spec);
 813             pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
 814         }
 815     }
 816 
 817     // Normalize timeout to positive milliseconds
 818     timeout_spec = g_hash_table_lookup(meta, PCMK_META_TIMEOUT);
 819     g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_TIMEOUT),
 820                         pcmk__itoa(unpack_timeout(timeout_spec)));
 821 
 822     // Ensure on-fail has a valid value
 823     validate_on_fail(rsc, action_name, action_config, meta);
 824 
 825     // Normalize PCMK_META_START_DELAY
 826     str = g_hash_table_lookup(meta, PCMK_META_START_DELAY);
 827     if (str != NULL) {
 828         unpack_start_delay(str, meta);
 829     } else {
 830         long long start_delay = 0;
 831 
 832         str = g_hash_table_lookup(meta, PCMK_META_INTERVAL_ORIGIN);
 833         if (unpack_interval_origin(str, action_config, interval_ms,
 834                                    rsc->cluster->now, &start_delay)) {
 835             g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_START_DELAY),
 836                                 crm_strdup_printf("%lld", start_delay));
 837         }
 838     }
 839     return meta;
 840 }
 841 
 842 /*!
 843  * \internal
 844  * \brief Determine an action's quorum and fencing dependency
 845  *
 846  * \param[in] rsc          Resource that action is for
 847  * \param[in] action_name  Name of action being unpacked
 848  *
 849  * \return Quorum and fencing dependency appropriate to action
 850  */
 851 enum rsc_start_requirement
 852 pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 853 {
 854     const char *value = NULL;
 855     enum rsc_start_requirement requires = pcmk_requires_nothing;
 856 
 857     CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires);
 858 
 859     if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START,
 860                               PCMK_ACTION_PROMOTE, NULL)) {
 861         value = "nothing (not start or promote)";
 862 
 863     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
 864         requires = pcmk_requires_fencing;
 865         value = "fencing";
 866 
 867     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_quorum)) {
 868         requires = pcmk_requires_quorum;
 869         value = "quorum";
 870 
 871     } else {
 872         value = "nothing";
 873     }
 874     pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value);
 875     return requires;
 876 }
 877 
 878 /*!
 879  * \internal
 880  * \brief Parse action failure response from a user-provided string
 881  *
 882  * \param[in] rsc          Resource that action is for
 883  * \param[in] action_name  Name of action
 884  * \param[in] interval_ms  Action interval (in milliseconds)
 885  * \param[in] value        User-provided configuration value for on-fail
 886  *
 887  * \return Action failure response parsed from \p text
 888  */
 889 enum action_fail_response
 890 pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 891                     guint interval_ms, const char *value)
 892 {
 893     const char *desc = NULL;
 894     bool needs_remote_reset = false;
 895     enum action_fail_response on_fail = pcmk_on_fail_ignore;
 896 
 897     // There's no enum value for unknown or invalid, so assert
 898     pcmk__assert((rsc != NULL) && (action_name != NULL));
 899 
 900     if (value == NULL) {
 901         // Use default
 902 
 903     } else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
 904         on_fail = pcmk_on_fail_block;
 905         desc = "block";
 906 
 907     } else if (pcmk__str_eq(value, PCMK_VALUE_FENCE, pcmk__str_casei)) {
 908         if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
 909             on_fail = pcmk_on_fail_fence_node;
 910             desc = "node fencing";
 911         } else {
 912             pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for "
 913                              "%s of %s to 'stop' because 'fence' is not "
 914                              "valid when fencing is disabled",
 915                              action_name, rsc->id);
 916             on_fail = pcmk_on_fail_stop;
 917             desc = "stop resource";
 918         }
 919 
 920     } else if (pcmk__str_eq(value, PCMK_VALUE_STANDBY, pcmk__str_casei)) {
 921         on_fail = pcmk_on_fail_standby_node;
 922         desc = "node standby";
 923 
 924     } else if (pcmk__strcase_any_of(value,
 925                                     PCMK_VALUE_IGNORE, PCMK_VALUE_NOTHING,
 926                                     NULL)) {
 927         desc = "ignore";
 928 
 929     } else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) {
 930         on_fail = pcmk_on_fail_ban;
 931         desc = "force migration";
 932 
 933     } else if (pcmk__str_eq(value, PCMK_VALUE_STOP, pcmk__str_casei)) {
 934         on_fail = pcmk_on_fail_stop;
 935         desc = "stop resource";
 936 
 937     } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
 938         on_fail = pcmk_on_fail_restart;
 939         desc = "restart (and possibly migrate)";
 940 
 941     } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART_CONTAINER,
 942                             pcmk__str_casei)) {
 943         if (rsc->container == NULL) {
 944             pcmk__rsc_debug(rsc,
 945                             "Using default " PCMK_META_ON_FAIL " for %s "
 946                             "of %s because it does not have a container",
 947                             action_name, rsc->id);
 948         } else {
 949             on_fail = pcmk_on_fail_restart_container;
 950             desc = "restart container (and possibly migrate)";
 951         }
 952 
 953     } else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
 954         on_fail = pcmk_on_fail_demote;
 955         desc = "demote instance";
 956 
 957     } else {
 958         pcmk__config_err("Using default '" PCMK_META_ON_FAIL "' for "
 959                          "%s of %s because '%s' is not valid",
 960                          action_name, rsc->id, value);
 961     }
 962 
 963     /* Remote node connections are handled specially. Failures that result
 964      * in dropping an active connection must result in fencing. The only
 965      * failures that don't are probes and starts. The user can explicitly set
 966      * PCMK_META_ON_FAIL=PCMK_VALUE_FENCE to fence after start failures.
 967      */
 968     if (rsc->is_remote_node
 969         && pcmk__is_remote_node(pcmk_find_node(rsc->cluster, rsc->id))
 970         && !pcmk_is_probe(action_name, interval_ms)
 971         && !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
 972         needs_remote_reset = true;
 973         if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
 974             desc = NULL; // Force default for unmanaged connections
 975         }
 976     }
 977 
 978     if (desc != NULL) {
 979         // Explicit value used, default not needed
 980 
 981     } else if (rsc->container != NULL) {
 982         on_fail = pcmk_on_fail_restart_container;
 983         desc = "restart container (and possibly migrate) (default)";
 984 
 985     } else if (needs_remote_reset) {
 986         if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
 987             if (pcmk_is_set(rsc->cluster->flags,
 988                             pcmk_sched_fencing_enabled)) {
 989                 desc = "fence remote node (default)";
 990             } else {
 991                 desc = "recover remote node connection (default)";
 992             }
 993             on_fail = pcmk_on_fail_reset_remote;
 994         } else {
 995             on_fail = pcmk_on_fail_stop;
 996             desc = "stop unmanaged remote node (enforcing default)";
 997         }
 998 
 999     } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
1000         if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
1001             on_fail = pcmk_on_fail_fence_node;
1002             desc = "resource fence (default)";
1003         } else {
1004             on_fail = pcmk_on_fail_block;
1005             desc = "resource block (default)";
1006         }
1007 
1008     } else {
1009         on_fail = pcmk_on_fail_restart;
1010         desc = "restart (and possibly migrate) (default)";
1011     }
1012 
1013     pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s",
1014                     pcmk__readable_interval(interval_ms), action_name,
1015                     rsc->id, desc);
1016     return on_fail;
1017 }
1018 
1019 /*!
1020  * \internal
1021  * \brief Determine a resource's role after failure of an action
1022  *
1023  * \param[in] rsc          Resource that action is for
1024  * \param[in] action_name  Action name
1025  * \param[in] on_fail      Failure handling for action
1026  * \param[in] meta         Unpacked action meta-attributes
1027  *
1028  * \return Resource role that results from failure of action
1029  */
1030 enum rsc_role_e
1031 pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1032                          enum action_fail_response on_fail, GHashTable *meta)
1033 {
1034     const char *value = NULL;
1035     enum rsc_role_e role = pcmk_role_unknown;
1036 
1037     // Set default for role after failure specially in certain circumstances
1038     switch (on_fail) {
1039         case pcmk_on_fail_stop:
1040             role = pcmk_role_stopped;
1041             break;
1042 
1043         case pcmk_on_fail_reset_remote:
1044             if (rsc->remote_reconnect_ms != 0) {
1045                 role = pcmk_role_stopped;
1046             }
1047             break;
1048 
1049         default:
1050             break;
1051     }
1052 
1053     // @COMPAT Check for explicitly configured role (deprecated)
1054     value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE);
1055     if (value != NULL) {
1056         pcmk__warn_once(pcmk__wo_role_after,
1057                         "Support for " PCMK__META_ROLE_AFTER_FAILURE " is "
1058                         "deprecated and will be removed in a future release");
1059         if (role == pcmk_role_unknown) {
1060             role = pcmk_parse_role(value);
1061             if (role == pcmk_role_unknown) {
1062                 pcmk__config_err("Ignoring invalid value %s for "
1063                                  PCMK__META_ROLE_AFTER_FAILURE,
1064                                  value);
1065             }
1066         }
1067     }
1068 
1069     if (role == pcmk_role_unknown) {
1070         // Use default
1071         if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
1072             role = pcmk_role_unpromoted;
1073         } else {
1074             role = pcmk_role_started;
1075         }
1076     }
1077     pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s",
1078                     rsc->id, action_name, pcmk_role_text(role));
1079     return role;
1080 }
1081 
1082 /*!
1083  * \internal
1084  * \brief Unpack action configuration
1085  *
1086  * Unpack a resource action's meta-attributes (normalizing the interval,
1087  * timeout, and start delay values as integer milliseconds), requirements, and
1088  * failure policy from its CIB XML configuration (including defaults).
1089  *
1090  * \param[in,out] action       Resource action to unpack into
1091  * \param[in]     xml_obj      Action configuration XML (NULL for defaults only)
1092  * \param[in]     interval_ms  How frequently to perform the operation
1093  */
1094 static void
1095 unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
     /* [previous][next][first][last][top][bottom][index][help] */
1096                  guint interval_ms)
1097 {
1098     const char *value = NULL;
1099 
1100     action->meta = pcmk__unpack_action_meta(action->rsc, action->node,
1101                                             action->task, interval_ms, xml_obj);
1102     action->needs = pcmk__action_requires(action->rsc, action->task);
1103 
1104     value = g_hash_table_lookup(action->meta, PCMK_META_ON_FAIL);
1105     action->on_fail = pcmk__parse_on_fail(action->rsc, action->task,
1106                                           interval_ms, value);
1107 
1108     action->fail_role = pcmk__role_after_failure(action->rsc, action->task,
1109                                                  action->on_fail, action->meta);
1110 }
1111 
1112 /*!
1113  * \brief Create or update an action object
1114  *
1115  * \param[in,out] rsc          Resource that action is for (if any)
1116  * \param[in,out] key          Action key (must be non-NULL)
1117  * \param[in]     task         Action name (must be non-NULL)
1118  * \param[in]     on_node      Node that action is on (if any)
1119  * \param[in]     optional     Whether action should be considered optional
1120  * \param[in,out] scheduler    Scheduler data
1121  *
1122  * \return Action object corresponding to arguments (guaranteed not to be
1123  *         \c NULL)
1124  * \note This function takes ownership of (and might free) \p key, and
1125  *       \p scheduler takes ownership of the returned action (the caller should
1126  *       not free it).
1127  */
1128 pcmk_action_t *
1129 custom_action(pcmk_resource_t *rsc, char *key, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
1130               const pcmk_node_t *on_node, gboolean optional,
1131               pcmk_scheduler_t *scheduler)
1132 {
1133     pcmk_action_t *action = NULL;
1134 
1135     pcmk__assert((key != NULL) && (task != NULL) && (scheduler != NULL));
1136 
1137     action = find_existing_action(key, rsc, on_node, scheduler);
1138     if (action == NULL) {
1139         action = new_action(key, task, rsc, on_node, optional, scheduler);
1140     } else {
1141         free(key);
1142     }
1143 
1144     update_action_optional(action, optional);
1145 
1146     if (rsc != NULL) {
1147         /* An action can be initially created with a NULL node, and later have
1148          * the node added via find_existing_action() (above) -> find_actions().
1149          * That is why the extra parameters are unpacked here rather than in
1150          * new_action().
1151          */
1152         if ((action->node != NULL) && (action->op_entry != NULL)
1153             && !pcmk_is_set(action->flags, pcmk_action_attrs_evaluated)) {
1154 
1155             GHashTable *attrs = action->node->details->attrs;
1156 
1157             if (action->extra != NULL) {
1158                 g_hash_table_destroy(action->extra);
1159             }
1160             action->extra = pcmk__unpack_action_rsc_params(action->op_entry,
1161                                                            attrs, scheduler);
1162             pcmk__set_action_flags(action, pcmk_action_attrs_evaluated);
1163         }
1164 
1165         update_resource_action_runnable(action, scheduler);
1166         update_resource_flags_for_action(rsc, action);
1167     }
1168 
1169     if (action->extra == NULL) {
1170         action->extra = pcmk__strkey_table(free, free);
1171     }
1172 
1173     return action;
1174 }
1175 
1176 pcmk_action_t *
1177 get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
1178 {
1179     pcmk_action_t *op = lookup_singleton(scheduler, name);
1180 
1181     if (op == NULL) {
1182         op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler);
1183         pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
1184     }
1185     return op;
1186 }
1187 
1188 static GList *
1189 find_unfencing_devices(GList *candidates, GList *matches) 
     /* [previous][next][first][last][top][bottom][index][help] */
1190 {
1191     for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) {
1192         pcmk_resource_t *candidate = gIter->data;
1193 
1194         if (candidate->children != NULL) {
1195             matches = find_unfencing_devices(candidate->children, matches);
1196 
1197         } else if (!pcmk_is_set(candidate->flags, pcmk_rsc_fence_device)) {
1198             continue;
1199 
1200         } else if (pcmk_is_set(candidate->flags, pcmk_rsc_needs_unfencing)) {
1201             matches = g_list_prepend(matches, candidate);
1202 
1203         } else if (pcmk__str_eq(g_hash_table_lookup(candidate->meta,
1204                                                     PCMK_STONITH_PROVIDES),
1205                                 PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
1206             matches = g_list_prepend(matches, candidate);
1207         }
1208     }
1209     return matches;
1210 }
1211 
1212 static int
1213 node_priority_fencing_delay(const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
1214                             const pcmk_scheduler_t *scheduler)
1215 {
1216     int member_count = 0;
1217     int online_count = 0;
1218     int top_priority = 0;
1219     int lowest_priority = 0;
1220     GList *gIter = NULL;
1221 
1222     // PCMK_OPT_PRIORITY_FENCING_DELAY is disabled
1223     if (scheduler->priority_fencing_delay <= 0) {
1224         return 0;
1225     }
1226 
1227     /* No need to request a delay if the fencing target is not a normal cluster
1228      * member, for example if it's a remote node or a guest node. */
1229     if (node->details->type != pcmk_node_variant_cluster) {
1230         return 0;
1231     }
1232 
1233     // No need to request a delay if the fencing target is in our partition
1234     if (node->details->online) {
1235         return 0;
1236     }
1237 
1238     for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
1239         pcmk_node_t *n = gIter->data;
1240 
1241         if (n->details->type != pcmk_node_variant_cluster) {
1242             continue;
1243         }
1244 
1245         member_count ++;
1246 
1247         if (n->details->online) {
1248             online_count++;
1249         }
1250 
1251         if (member_count == 1
1252             || n->details->priority > top_priority) {
1253             top_priority = n->details->priority;
1254         }
1255 
1256         if (member_count == 1
1257             || n->details->priority < lowest_priority) {
1258             lowest_priority = n->details->priority;
1259         }
1260     }
1261 
1262     // No need to delay if we have more than half of the cluster members
1263     if (online_count > member_count / 2) {
1264         return 0;
1265     }
1266 
1267     /* All the nodes have equal priority.
1268      * Any configured corresponding `pcmk_delay_base/max` will be applied. */
1269     if (lowest_priority == top_priority) {
1270         return 0;
1271     }
1272 
1273     if (node->details->priority < top_priority) {
1274         return 0;
1275     }
1276 
1277     return scheduler->priority_fencing_delay;
1278 }
1279 
1280 pcmk_action_t *
1281 pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
     /* [previous][next][first][last][top][bottom][index][help] */
1282             const char *reason, bool priority_delay,
1283             pcmk_scheduler_t *scheduler)
1284 {
1285     char *op_key = NULL;
1286     pcmk_action_t *stonith_op = NULL;
1287 
1288     if(op == NULL) {
1289         op = scheduler->stonith_action;
1290     }
1291 
1292     op_key = crm_strdup_printf("%s-%s-%s",
1293                                PCMK_ACTION_STONITH, node->details->uname, op);
1294 
1295     stonith_op = lookup_singleton(scheduler, op_key);
1296     if(stonith_op == NULL) {
1297         stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node,
1298                                    TRUE, scheduler);
1299 
1300         pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE, node->details->uname);
1301         pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE_UUID,
1302                           node->details->id);
1303         pcmk__insert_meta(stonith_op, PCMK__META_STONITH_ACTION, op);
1304 
1305         if (pcmk_is_set(scheduler->flags, pcmk_sched_enable_unfencing)) {
1306             /* Extra work to detect device changes
1307              */
1308             GString *digests_all = g_string_sized_new(1024);
1309             GString *digests_secure = g_string_sized_new(1024);
1310 
1311             GList *matches = find_unfencing_devices(scheduler->resources, NULL);
1312 
1313             for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) {
1314                 pcmk_resource_t *match = gIter->data;
1315                 const char *agent = g_hash_table_lookup(match->meta,
1316                                                         PCMK_XA_TYPE);
1317                 pcmk__op_digest_t *data = NULL;
1318 
1319                 data = pe__compare_fencing_digest(match, agent, node,
1320                                                   scheduler);
1321                 if (data->rc == pcmk__digest_mismatch) {
1322                     optional = FALSE;
1323                     crm_notice("Unfencing node %s because the definition of "
1324                                "%s changed", pcmk__node_name(node), match->id);
1325                     if (!pcmk__is_daemon && scheduler->priv != NULL) {
1326                         pcmk__output_t *out = scheduler->priv;
1327 
1328                         out->info(out,
1329                                   "notice: Unfencing node %s because the "
1330                                   "definition of %s changed",
1331                                   pcmk__node_name(node), match->id);
1332                     }
1333                 }
1334 
1335                 pcmk__g_strcat(digests_all,
1336                                match->id, ":", agent, ":",
1337                                data->digest_all_calc, ",", NULL);
1338                 pcmk__g_strcat(digests_secure,
1339                                match->id, ":", agent, ":",
1340                                data->digest_secure_calc, ",", NULL);
1341             }
1342             pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_ALL,
1343                              digests_all->str);
1344             g_string_free(digests_all, TRUE);
1345 
1346             pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_SECURE,
1347                              digests_secure->str);
1348             g_string_free(digests_secure, TRUE);
1349 
1350             g_list_free(matches);
1351         }
1352 
1353     } else {
1354         free(op_key);
1355     }
1356 
1357     if (scheduler->priority_fencing_delay > 0
1358 
1359             /* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY
1360              * applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as
1361              * an indicator.
1362              */
1363         && (priority_delay
1364 
1365             /* The priority delay needs to be recalculated if this function has
1366              * been called by schedule_fencing_and_shutdowns() after node
1367              * priority has already been calculated by native_add_running().
1368              */
1369             || g_hash_table_lookup(stonith_op->meta,
1370                                    PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) {
1371 
1372             /* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if
1373              * it's 0 for the targeting node. So that it takes precedence over
1374              * any possible `pcmk_delay_base/max`.
1375              */
1376             char *delay_s = pcmk__itoa(node_priority_fencing_delay(node,
1377                                                                    scheduler));
1378 
1379             g_hash_table_insert(stonith_op->meta,
1380                                 strdup(PCMK_OPT_PRIORITY_FENCING_DELAY),
1381                                 delay_s);
1382     }
1383 
1384     if(optional == FALSE && pe_can_fence(scheduler, node)) {
1385         pcmk__clear_action_flags(stonith_op, pcmk_action_optional);
1386         pe_action_set_reason(stonith_op, reason, false);
1387 
1388     } else if(reason && stonith_op->reason == NULL) {
1389         stonith_op->reason = strdup(reason);
1390     }
1391 
1392     return stonith_op;
1393 }
1394 
1395 void
1396 pe_free_action(pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1397 {
1398     if (action == NULL) {
1399         return;
1400     }
1401     g_list_free_full(action->actions_before, free);
1402     g_list_free_full(action->actions_after, free);
1403     if (action->extra) {
1404         g_hash_table_destroy(action->extra);
1405     }
1406     if (action->meta) {
1407         g_hash_table_destroy(action->meta);
1408     }
1409     free(action->cancel_task);
1410     free(action->reason);
1411     free(action->task);
1412     free(action->uuid);
1413     free(action->node);
1414     free(action);
1415 }
1416 
1417 enum action_tasks
1418 get_complex_task(const pcmk_resource_t *rsc, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1419 {
1420     enum action_tasks task = pcmk_parse_action(name);
1421 
1422     if (pcmk__is_primitive(rsc)) {
1423         switch (task) {
1424             case pcmk_action_stopped:
1425             case pcmk_action_started:
1426             case pcmk_action_demoted:
1427             case pcmk_action_promoted:
1428                 crm_trace("Folding %s back into its atomic counterpart for %s",
1429                           name, rsc->id);
1430                 --task;
1431                 break;
1432             default:
1433                 break;
1434         }
1435     }
1436     return task;
1437 }
1438 
1439 /*!
1440  * \internal
1441  * \brief Find first matching action in a list
1442  *
1443  * \param[in] input    List of actions to search
1444  * \param[in] uuid     If not NULL, action must have this UUID
1445  * \param[in] task     If not NULL, action must have this action name
1446  * \param[in] on_node  If not NULL, action must be on this node
1447  *
1448  * \return First action in list that matches criteria, or NULL if none
1449  */
1450 pcmk_action_t *
1451 find_first_action(const GList *input, const char *uuid, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
1452                   const pcmk_node_t *on_node)
1453 {
1454     CRM_CHECK(uuid || task, return NULL);
1455 
1456     for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) {
1457         pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1458 
1459         if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) {
1460             continue;
1461 
1462         } else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) {
1463             continue;
1464 
1465         } else if (on_node == NULL) {
1466             return action;
1467 
1468         } else if (action->node == NULL) {
1469             continue;
1470 
1471         } else if (pcmk__same_node(on_node, action->node)) {
1472             return action;
1473         }
1474     }
1475 
1476     return NULL;
1477 }
1478 
1479 GList *
1480 find_actions(GList *input, const char *key, const pcmk_node_t *on_node)
     /* [previous][next][first][last][top][bottom][index][help] */
1481 {
1482     GList *gIter = input;
1483     GList *result = NULL;
1484 
1485     CRM_CHECK(key != NULL, return NULL);
1486 
1487     for (; gIter != NULL; gIter = gIter->next) {
1488         pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1489 
1490         if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) {
1491             continue;
1492 
1493         } else if (on_node == NULL) {
1494             crm_trace("Action %s matches (ignoring node)", key);
1495             result = g_list_prepend(result, action);
1496 
1497         } else if (action->node == NULL) {
1498             crm_trace("Action %s matches (unallocated, assigning to %s)",
1499                       key, pcmk__node_name(on_node));
1500 
1501             action->node = pe__copy_node(on_node);
1502             result = g_list_prepend(result, action);
1503 
1504         } else if (pcmk__same_node(on_node, action->node)) {
1505             crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
1506             result = g_list_prepend(result, action);
1507         }
1508     }
1509 
1510     return result;
1511 }
1512 
1513 GList *
1514 find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node)
     /* [previous][next][first][last][top][bottom][index][help] */
1515 {
1516     GList *result = NULL;
1517 
1518     CRM_CHECK(key != NULL, return NULL);
1519 
1520     if (on_node == NULL) {
1521         return NULL;
1522     }
1523 
1524     for (GList *gIter = input; gIter != NULL; gIter = gIter->next) {
1525         pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1526 
1527         if ((action->node != NULL)
1528             && pcmk__str_eq(key, action->uuid, pcmk__str_casei)
1529             && pcmk__str_eq(on_node->details->id, action->node->details->id,
1530                             pcmk__str_casei)) {
1531 
1532             crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
1533             result = g_list_prepend(result, action);
1534         }
1535     }
1536 
1537     return result;
1538 }
1539 
1540 /*!
1541  * \brief Find all actions of given type for a resource
1542  *
1543  * \param[in] rsc           Resource to search
1544  * \param[in] node          Find only actions scheduled on this node
1545  * \param[in] task          Action name to search for
1546  * \param[in] require_node  If TRUE, NULL node or action node will not match
1547  *
1548  * \return List of actions found (or NULL if none)
1549  * \note If node is not NULL and require_node is FALSE, matching actions
1550  *       without a node will be assigned to node.
1551  */
1552 GList *
1553 pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
1554                      const char *task, bool require_node)
1555 {
1556     GList *result = NULL;
1557     char *key = pcmk__op_key(rsc->id, task, 0);
1558 
1559     if (require_node) {
1560         result = find_actions_exact(rsc->actions, key, node);
1561     } else {
1562         result = find_actions(rsc->actions, key, node);
1563     }
1564     free(key);
1565     return result;
1566 }
1567 
1568 /*!
1569  * \internal
1570  * \brief Create an action reason string based on the action itself
1571  *
1572  * \param[in] action  Action to create reason string for
1573  * \param[in] flag    Action flag that was cleared
1574  *
1575  * \return Newly allocated string suitable for use as action reason
1576  * \note It is the caller's responsibility to free() the result.
1577  */
1578 char *
1579 pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
1580 {
1581     const char *change = NULL;
1582 
1583     switch (flag) {
1584         case pcmk_action_runnable:
1585             change = "unrunnable";
1586             break;
1587         case pcmk_action_migratable:
1588             change = "unmigrateable";
1589             break;
1590         case pcmk_action_optional:
1591             change = "required";
1592             break;
1593         default:
1594             // Bug: caller passed unsupported flag
1595             CRM_CHECK(change != NULL, change = "");
1596             break;
1597     }
1598     return crm_strdup_printf("%s%s%s %s", change,
1599                              (action->rsc == NULL)? "" : " ",
1600                              (action->rsc == NULL)? "" : action->rsc->id,
1601                              action->task);
1602 }
1603 
1604 void pe_action_set_reason(pcmk_action_t *action, const char *reason,
     /* [previous][next][first][last][top][bottom][index][help] */
1605                           bool overwrite)
1606 {
1607     if (action->reason != NULL && overwrite) {
1608         pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'",
1609                         action->uuid, action->reason,
1610                         pcmk__s(reason, "(none)"));
1611     } else if (action->reason == NULL) {
1612         pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'",
1613                         action->uuid, pcmk__s(reason, "(none)"));
1614     } else {
1615         // crm_assert(action->reason != NULL && !overwrite);
1616         return;
1617     }
1618 
1619     pcmk__str_update(&action->reason, reason);
1620 }
1621 
1622 /*!
1623  * \internal
1624  * \brief Create an action to clear a resource's history from CIB
1625  *
1626  * \param[in,out] rsc       Resource to clear
1627  * \param[in]     node      Node to clear history on
1628  */
1629 void
1630 pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
1631 {
1632     pcmk__assert((rsc != NULL) && (node != NULL));
1633 
1634     custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
1635                   PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->cluster);
1636 }
1637 
1638 #define sort_return(an_int, why) do {                                   \
1639         free(a_uuid);                                           \
1640         free(b_uuid);                                           \
1641         crm_trace("%s (%d) %c %s (%d) : %s",                            \
1642                   a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=',   \
1643                   b_xml_id, b_call_id, why);                            \
1644         return an_int;                                                  \
1645     } while(0)
1646 
1647 int
1648 pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
     /* [previous][next][first][last][top][bottom][index][help] */
1649                 bool same_node_default)
1650 {
1651     int a_call_id = -1;
1652     int b_call_id = -1;
1653 
1654     char *a_uuid = NULL;
1655     char *b_uuid = NULL;
1656 
1657     const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID);
1658     const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID);
1659 
1660     const char *a_node = crm_element_value(xml_a, PCMK__META_ON_NODE);
1661     const char *b_node = crm_element_value(xml_b, PCMK__META_ON_NODE);
1662     bool same_node = true;
1663 
1664     /* @COMPAT The on_node attribute was added to last_failure as of 1.1.13 (via
1665      * 8b3ca1c) and the other entries as of 1.1.12 (via 0b07b5c).
1666      *
1667      * In case that any of the PCMK__XE_LRM_RSC_OP entries doesn't have on_node
1668      * attribute, we need to explicitly tell whether the two operations are on
1669      * the same node.
1670      */
1671     if (a_node == NULL || b_node == NULL) {
1672         same_node = same_node_default;
1673 
1674     } else {
1675         same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei);
1676     }
1677 
1678     if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) {
1679         /* We have duplicate PCMK__XE_LRM_RSC_OP entries in the status
1680          * section which is unlikely to be a good thing
1681          *    - we can handle it easily enough, but we need to get
1682          *    to the bottom of why it's happening.
1683          */
1684         pcmk__config_err("Duplicate " PCMK__XE_LRM_RSC_OP " entries named %s",
1685                          a_xml_id);
1686         sort_return(0, "duplicate");
1687     }
1688 
1689     crm_element_value_int(xml_a, PCMK__XA_CALL_ID, &a_call_id);
1690     crm_element_value_int(xml_b, PCMK__XA_CALL_ID, &b_call_id);
1691 
1692     if (a_call_id == -1 && b_call_id == -1) {
1693         /* both are pending ops so it doesn't matter since
1694          *   stops are never pending
1695          */
1696         sort_return(0, "pending");
1697 
1698     } else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) {
1699         sort_return(-1, "call id");
1700 
1701     } else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) {
1702         sort_return(1, "call id");
1703 
1704     } else if (a_call_id >= 0 && b_call_id >= 0
1705                && (!same_node || a_call_id == b_call_id)) {
1706         /* The op and last_failed_op are the same. Order on
1707          * PCMK_XA_LAST_RC_CHANGE.
1708          */
1709         time_t last_a = -1;
1710         time_t last_b = -1;
1711 
1712         crm_element_value_epoch(xml_a, PCMK_XA_LAST_RC_CHANGE, &last_a);
1713         crm_element_value_epoch(xml_b, PCMK_XA_LAST_RC_CHANGE, &last_b);
1714 
1715         crm_trace("rc-change: %lld vs %lld",
1716                   (long long) last_a, (long long) last_b);
1717         if (last_a >= 0 && last_a < last_b) {
1718             sort_return(-1, "rc-change");
1719 
1720         } else if (last_b >= 0 && last_a > last_b) {
1721             sort_return(1, "rc-change");
1722         }
1723         sort_return(0, "rc-change");
1724 
1725     } else {
1726         /* One of the inputs is a pending operation.
1727          * Attempt to use PCMK__XA_TRANSITION_MAGIC to determine its age relative
1728          * to the other.
1729          */
1730 
1731         int a_id = -1;
1732         int b_id = -1;
1733 
1734         const char *a_magic = crm_element_value(xml_a,
1735                                                 PCMK__XA_TRANSITION_MAGIC);
1736         const char *b_magic = crm_element_value(xml_b,
1737                                                 PCMK__XA_TRANSITION_MAGIC);
1738 
1739         CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
1740         if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL,
1741                                      NULL)) {
1742             sort_return(0, "bad magic a");
1743         }
1744         if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL,
1745                                      NULL)) {
1746             sort_return(0, "bad magic b");
1747         }
1748         /* try to determine the relative age of the operation...
1749          * some pending operations (e.g. a start) may have been superseded
1750          *   by a subsequent stop
1751          *
1752          * [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
1753          */
1754         if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) {
1755             /*
1756              * some of the logic in here may be redundant...
1757              *
1758              * if the UUID from the TE doesn't match then one better
1759              *   be a pending operation.
1760              * pending operations don't survive between elections and joins
1761              *   because we query the LRM directly
1762              */
1763 
1764             if (b_call_id == -1) {
1765                 sort_return(-1, "transition + call");
1766 
1767             } else if (a_call_id == -1) {
1768                 sort_return(1, "transition + call");
1769             }
1770 
1771         } else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
1772             sort_return(-1, "transition");
1773 
1774         } else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
1775             sort_return(1, "transition");
1776         }
1777     }
1778 
1779     /* we should never end up here */
1780     CRM_CHECK(FALSE, sort_return(0, "default"));
1781 }
1782 
1783 gint
1784 sort_op_by_callid(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
1785 {
1786     const xmlNode *xml_a = a;
1787     const xmlNode *xml_b = b;
1788 
1789     return pe__is_newer_op(xml_a, xml_b, true);
1790 }
1791 
1792 /*!
1793  * \internal
1794  * \brief Create a new pseudo-action for a resource
1795  *
1796  * \param[in,out] rsc       Resource to create action for
1797  * \param[in]     task      Action name
1798  * \param[in]     optional  Whether action should be considered optional
1799  * \param[in]     runnable  Whethe action should be considered runnable
1800  *
1801  * \return New action object corresponding to arguments
1802  */
1803 pcmk_action_t *
1804 pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional,
     /* [previous][next][first][last][top][bottom][index][help] */
1805                           bool runnable)
1806 {
1807     pcmk_action_t *action = NULL;
1808 
1809     pcmk__assert((rsc != NULL) && (task != NULL));
1810 
1811     action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL,
1812                            optional, rsc->cluster);
1813     pcmk__set_action_flags(action, pcmk_action_pseudo);
1814     if (runnable) {
1815         pcmk__set_action_flags(action, pcmk_action_runnable);
1816     }
1817     return action;
1818 }
1819 
1820 /*!
1821  * \internal
1822  * \brief Add the expected result to an action
1823  *
1824  * \param[in,out] action           Action to add expected result to
1825  * \param[in]     expected_result  Expected result to add
1826  *
1827  * \note This is more efficient than calling pcmk__insert_meta().
1828  */
1829 void
1830 pe__add_action_expected_result(pcmk_action_t *action, int expected_result)
     /* [previous][next][first][last][top][bottom][index][help] */
1831 {
1832     pcmk__assert((action != NULL) && (action->meta != NULL));
1833 
1834     g_hash_table_insert(action->meta, pcmk__str_copy(PCMK__META_OP_TARGET_RC),
1835                         pcmk__itoa(expected_result));
1836 }

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