LCOV - code coverage report
Current view: top level - pacemaker - pcmk_sched_ordering.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 598 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 25 0.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2004-2024 the Pacemaker project contributors
       3             :  *
       4             :  * The version control history for this file may have further details.
       5             :  *
       6             :  * This source code is licensed under the GNU General Public License version 2
       7             :  * or later (GPLv2+) WITHOUT ANY WARRANTY.
       8             :  */
       9             : 
      10             : #include <crm_internal.h>
      11             : 
      12             : #include <inttypes.h>               // PRIx32
      13             : #include <stdbool.h>
      14             : #include <glib.h>
      15             : 
      16             : #include <crm/crm.h>
      17             : #include <pacemaker-internal.h>
      18             : #include "libpacemaker_private.h"
      19             : 
      20             : enum pe_order_kind {
      21             :     pe_order_kind_optional,
      22             :     pe_order_kind_mandatory,
      23             :     pe_order_kind_serialize,
      24             : };
      25             : 
      26             : enum ordering_symmetry {
      27             :     ordering_asymmetric,        // the only relation in an asymmetric ordering
      28             :     ordering_symmetric,         // the normal relation in a symmetric ordering
      29             :     ordering_symmetric_inverse, // the inverse relation in a symmetric ordering
      30             : };
      31             : 
      32             : #define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do {                  \
      33             :         __rsc = pcmk__find_constraint_resource(scheduler->resources,        \
      34             :                                                __name);                     \
      35             :         if (__rsc == NULL) {                                                \
      36             :             pcmk__config_err("%s: No resource found for %s", __set, __name);\
      37             :             return pcmk_rc_unpack_error;                                    \
      38             :         }                                                                   \
      39             :     } while (0)
      40             : 
      41             : static const char *
      42           0 : invert_action(const char *action)
      43             : {
      44           0 :     if (pcmk__str_eq(action, PCMK_ACTION_START, pcmk__str_none)) {
      45           0 :         return PCMK_ACTION_STOP;
      46             : 
      47           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_STOP, pcmk__str_none)) {
      48           0 :         return PCMK_ACTION_START;
      49             : 
      50           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
      51           0 :         return PCMK_ACTION_DEMOTE;
      52             : 
      53           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
      54           0 :         return PCMK_ACTION_PROMOTE;
      55             : 
      56           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_PROMOTED, pcmk__str_none)) {
      57           0 :         return PCMK_ACTION_DEMOTED;
      58             : 
      59           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_DEMOTED, pcmk__str_none)) {
      60           0 :         return PCMK_ACTION_PROMOTED;
      61             : 
      62           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_RUNNING, pcmk__str_none)) {
      63           0 :         return PCMK_ACTION_STOPPED;
      64             : 
      65           0 :     } else if (pcmk__str_eq(action, PCMK_ACTION_STOPPED, pcmk__str_none)) {
      66           0 :         return PCMK_ACTION_RUNNING;
      67             :     }
      68           0 :     pcmk__config_warn("Unknown action '%s' specified in order constraint",
      69             :                       action);
      70           0 :     return NULL;
      71             : }
      72             : 
      73             : static enum pe_order_kind
      74           0 : get_ordering_type(const xmlNode *xml_obj)
      75             : {
      76           0 :     enum pe_order_kind kind_e = pe_order_kind_mandatory;
      77           0 :     const char *kind = crm_element_value(xml_obj, PCMK_XA_KIND);
      78             : 
      79           0 :     if (kind == NULL) {
      80           0 :         const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
      81             : 
      82           0 :         kind_e = pe_order_kind_mandatory;
      83             : 
      84           0 :         if (score) {
      85             :             // @COMPAT deprecated informally since 1.0.7, formally since 2.0.1
      86           0 :             int score_i = char2score(score);
      87             : 
      88           0 :             if (score_i == 0) {
      89           0 :                 kind_e = pe_order_kind_optional;
      90             :             }
      91           0 :             pcmk__warn_once(pcmk__wo_order_score,
      92             :                             "Support for '" PCMK_XA_SCORE "' in "
      93             :                             PCMK_XE_RSC_ORDER " is deprecated and will be "
      94             :                             "removed in a future release "
      95             :                             "(use '" PCMK_XA_KIND "' instead)");
      96             :         }
      97             : 
      98           0 :     } else if (pcmk__str_eq(kind, PCMK_VALUE_MANDATORY, pcmk__str_none)) {
      99           0 :         kind_e = pe_order_kind_mandatory;
     100             : 
     101           0 :     } else if (pcmk__str_eq(kind, PCMK_VALUE_OPTIONAL, pcmk__str_none)) {
     102           0 :         kind_e = pe_order_kind_optional;
     103             : 
     104           0 :     } else if (pcmk__str_eq(kind, PCMK_VALUE_SERIALIZE, pcmk__str_none)) {
     105           0 :         kind_e = pe_order_kind_serialize;
     106             : 
     107             :     } else {
     108           0 :         pcmk__config_err("Resetting '" PCMK_XA_KIND "' for constraint %s to "
     109             :                          "'" PCMK_VALUE_MANDATORY "' because '%s' is not valid",
     110             :                          pcmk__s(pcmk__xe_id(xml_obj), "missing ID"), kind);
     111             :     }
     112           0 :     return kind_e;
     113             : }
     114             : 
     115             : /*!
     116             :  * \internal
     117             :  * \brief Get ordering symmetry from XML
     118             :  *
     119             :  * \param[in] xml_obj               Ordering XML
     120             :  * \param[in] parent_kind           Default ordering kind
     121             :  * \param[in] parent_symmetrical_s  Parent element's \c PCMK_XA_SYMMETRICAL
     122             :  *                                  setting, if any
     123             :  *
     124             :  * \retval ordering_symmetric   Ordering is symmetric
     125             :  * \retval ordering_asymmetric  Ordering is asymmetric
     126             :  */
     127             : static enum ordering_symmetry
     128           0 : get_ordering_symmetry(const xmlNode *xml_obj, enum pe_order_kind parent_kind,
     129             :                       const char *parent_symmetrical_s)
     130             : {
     131           0 :     int rc = pcmk_rc_ok;
     132           0 :     bool symmetric = false;
     133           0 :     enum pe_order_kind kind = parent_kind; // Default to parent's kind
     134             : 
     135             :     // Check ordering XML for explicit kind
     136           0 :     if ((crm_element_value(xml_obj, PCMK_XA_KIND) != NULL)
     137           0 :         || (crm_element_value(xml_obj, PCMK_XA_SCORE) != NULL)) {
     138           0 :         kind = get_ordering_type(xml_obj);
     139             :     }
     140             : 
     141             :     // Check ordering XML (and parent) for explicit PCMK_XA_SYMMETRICAL setting
     142           0 :     rc = pcmk__xe_get_bool_attr(xml_obj, PCMK_XA_SYMMETRICAL, &symmetric);
     143             : 
     144           0 :     if (rc != pcmk_rc_ok && parent_symmetrical_s != NULL) {
     145           0 :         symmetric = crm_is_true(parent_symmetrical_s);
     146           0 :         rc = pcmk_rc_ok;
     147             :     }
     148             : 
     149           0 :     if (rc == pcmk_rc_ok) {
     150           0 :         if (symmetric) {
     151           0 :             if (kind == pe_order_kind_serialize) {
     152           0 :                 pcmk__config_warn("Ignoring " PCMK_XA_SYMMETRICAL
     153             :                                   " for '%s' because not valid with "
     154             :                                   PCMK_XA_KIND " of '" PCMK_VALUE_SERIALIZE "'",
     155             :                                   pcmk__xe_id(xml_obj));
     156             :             } else {
     157           0 :                 return ordering_symmetric;
     158             :             }
     159             :         }
     160           0 :         return ordering_asymmetric;
     161             :     }
     162             : 
     163             :     // Use default symmetry
     164           0 :     if (kind == pe_order_kind_serialize) {
     165           0 :         return ordering_asymmetric;
     166             :     }
     167           0 :     return ordering_symmetric;
     168             : }
     169             : 
     170             : /*!
     171             :  * \internal
     172             :  * \brief Get ordering flags appropriate to ordering kind
     173             :  *
     174             :  * \param[in] kind      Ordering kind
     175             :  * \param[in] first     Action name for 'first' action
     176             :  * \param[in] symmetry  This ordering's symmetry role
     177             :  *
     178             :  * \return Minimal ordering flags appropriate to \p kind
     179             :  */
     180             : static uint32_t
     181           0 : ordering_flags_for_kind(enum pe_order_kind kind, const char *first,
     182             :                         enum ordering_symmetry symmetry)
     183             : {
     184           0 :     uint32_t flags = pcmk__ar_none; // so we trace-log all flags set
     185             : 
     186           0 :     switch (kind) {
     187           0 :         case pe_order_kind_optional:
     188           0 :             pcmk__set_relation_flags(flags, pcmk__ar_ordered);
     189           0 :             break;
     190             : 
     191           0 :         case pe_order_kind_serialize:
     192             :             /* This flag is not used anywhere directly but means the relation
     193             :              * will not match an equality comparison against pcmk__ar_none or
     194             :              * pcmk__ar_ordered.
     195             :              */
     196           0 :             pcmk__set_relation_flags(flags, pcmk__ar_serialize);
     197           0 :             break;
     198             : 
     199           0 :         case pe_order_kind_mandatory:
     200           0 :             pcmk__set_relation_flags(flags, pcmk__ar_ordered);
     201             :             switch (symmetry) {
     202           0 :                 case ordering_asymmetric:
     203           0 :                     pcmk__set_relation_flags(flags, pcmk__ar_asymmetric);
     204           0 :                     break;
     205             : 
     206           0 :                 case ordering_symmetric:
     207           0 :                     pcmk__set_relation_flags(flags,
     208             :                                              pcmk__ar_first_implies_then);
     209           0 :                     if (pcmk__strcase_any_of(first, PCMK_ACTION_START,
     210             :                                              PCMK_ACTION_PROMOTE, NULL)) {
     211           0 :                         pcmk__set_relation_flags(flags,
     212             :                                                  pcmk__ar_unrunnable_first_blocks);
     213             :                     }
     214           0 :                     break;
     215             : 
     216           0 :                 case ordering_symmetric_inverse:
     217           0 :                     pcmk__set_relation_flags(flags,
     218             :                                              pcmk__ar_then_implies_first);
     219           0 :                     break;
     220             :             }
     221           0 :             break;
     222             :     }
     223           0 :     return flags;
     224             : }
     225             : 
     226             : /*!
     227             :  * \internal
     228             :  * \brief Find resource corresponding to ID specified in ordering
     229             :  *
     230             :  * \param[in] xml            Ordering XML
     231             :  * \param[in] resource_attr  XML attribute name for resource ID
     232             :  * \param[in] instance_attr  XML attribute name for instance number.
     233             :  *                           This option is deprecated and will be removed in a
     234             :  *                           future release.
     235             :  * \param[in] scheduler      Scheduler data
     236             :  *
     237             :  * \return Resource corresponding to \p id, or NULL if none
     238             :  */
     239             : static pcmk_resource_t *
     240           0 : get_ordering_resource(const xmlNode *xml, const char *resource_attr,
     241             :                       const char *instance_attr,
     242             :                       const pcmk_scheduler_t *scheduler)
     243             : {
     244             :     // @COMPAT: instance_attr and instance_id variables deprecated since 2.1.5
     245           0 :     pcmk_resource_t *rsc = NULL;
     246           0 :     const char *rsc_id = crm_element_value(xml, resource_attr);
     247           0 :     const char *instance_id = crm_element_value(xml, instance_attr);
     248             : 
     249           0 :     if (rsc_id == NULL) {
     250           0 :         pcmk__config_err("Ignoring constraint '%s' without %s",
     251             :                          pcmk__xe_id(xml), resource_attr);
     252           0 :         return NULL;
     253             :     }
     254             : 
     255           0 :     rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
     256           0 :     if (rsc == NULL) {
     257           0 :         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
     258             :                          "does not exist", pcmk__xe_id(xml), rsc_id);
     259           0 :         return NULL;
     260             :     }
     261             : 
     262           0 :     if (instance_id != NULL) {
     263           0 :         pcmk__warn_once(pcmk__wo_order_inst,
     264             :                         "Support for " PCMK__XA_FIRST_INSTANCE " and "
     265             :                         PCMK__XA_THEN_INSTANCE " is deprecated and will be "
     266             :                         "removed in a future release.");
     267             : 
     268           0 :         if (!pcmk__is_clone(rsc)) {
     269           0 :             pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
     270             :                              "is not a clone but instance '%s' was requested",
     271             :                              pcmk__xe_id(xml), rsc_id, instance_id);
     272           0 :             return NULL;
     273             :         }
     274           0 :         rsc = find_clone_instance(rsc, instance_id);
     275           0 :         if (rsc == NULL) {
     276           0 :             pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
     277             :                              "does not have an instance '%s'",
     278             :                              pcmk__xe_id(xml), rsc_id, instance_id);
     279           0 :             return NULL;
     280             :         }
     281             :     }
     282           0 :     return rsc;
     283             : }
     284             : 
     285             : /*!
     286             :  * \internal
     287             :  * \brief Determine minimum number of 'first' instances required in ordering
     288             :  *
     289             :  * \param[in] rsc  'First' resource in ordering
     290             :  * \param[in] xml  Ordering XML
     291             :  *
     292             :  * \return Minimum 'first' instances required (or 0 if not applicable)
     293             :  */
     294             : static int
     295           0 : get_minimum_first_instances(const pcmk_resource_t *rsc, const xmlNode *xml)
     296             : {
     297           0 :     const char *clone_min = NULL;
     298           0 :     bool require_all = false;
     299             : 
     300           0 :     if (!pcmk__is_clone(rsc)) {
     301           0 :         return 0;
     302             :     }
     303             : 
     304           0 :     clone_min = g_hash_table_lookup(rsc->meta, PCMK_META_CLONE_MIN);
     305           0 :     if (clone_min != NULL) {
     306           0 :         int clone_min_int = 0;
     307             : 
     308           0 :         pcmk__scan_min_int(clone_min, &clone_min_int, 0);
     309           0 :         return clone_min_int;
     310             :     }
     311             : 
     312             :     /* @COMPAT 1.1.13:
     313             :      * PCMK_XA_REQUIRE_ALL=PCMK_VALUE_FALSE is deprecated equivalent of
     314             :      * PCMK_META_CLONE_MIN=1
     315             :      */
     316           0 :     if (pcmk__xe_get_bool_attr(xml, PCMK_XA_REQUIRE_ALL,
     317             :                                &require_all) != ENODATA) {
     318           0 :         pcmk__warn_once(pcmk__wo_require_all,
     319             :                         "Support for " PCMK_XA_REQUIRE_ALL " in ordering "
     320             :                         "constraints is deprecated and will be removed in a "
     321             :                         "future release (use " PCMK_META_CLONE_MIN " clone "
     322             :                         "meta-attribute instead)");
     323           0 :         if (!require_all) {
     324           0 :             return 1;
     325             :         }
     326             :     }
     327             : 
     328           0 :     return 0;
     329             : }
     330             : 
     331             : /*!
     332             :  * \internal
     333             :  * \brief Create orderings for a constraint with \c PCMK_META_CLONE_MIN > 0
     334             :  *
     335             :  * \param[in]     id            Ordering ID
     336             :  * \param[in,out] rsc_first     'First' resource in ordering (a clone)
     337             :  * \param[in]     action_first  'First' action in ordering
     338             :  * \param[in]     rsc_then      'Then' resource in ordering
     339             :  * \param[in]     action_then   'Then' action in ordering
     340             :  * \param[in]     flags         Ordering flags
     341             :  * \param[in]     clone_min     Minimum required instances of 'first'
     342             :  */
     343             : static void
     344           0 : clone_min_ordering(const char *id,
     345             :                    pcmk_resource_t *rsc_first, const char *action_first,
     346             :                    pcmk_resource_t *rsc_then, const char *action_then,
     347             :                    uint32_t flags, int clone_min)
     348             : {
     349             :     // Create a pseudo-action for when the minimum instances are active
     350           0 :     char *task = crm_strdup_printf(PCMK_ACTION_CLONE_ONE_OR_MORE ":%s", id);
     351           0 :     pcmk_action_t *clone_min_met = get_pseudo_op(task, rsc_first->cluster);
     352             : 
     353           0 :     free(task);
     354             : 
     355             :     /* Require the pseudo-action to have the required number of actions to be
     356             :      * considered runnable before allowing the pseudo-action to be runnable.
     357             :      */
     358           0 :     clone_min_met->required_runnable_before = clone_min;
     359           0 :     pcmk__set_action_flags(clone_min_met, pcmk_action_min_runnable);
     360             : 
     361             :     // Order the actions for each clone instance before the pseudo-action
     362           0 :     for (GList *iter = rsc_first->children; iter != NULL; iter = iter->next) {
     363           0 :         pcmk_resource_t *child = iter->data;
     364             : 
     365           0 :         pcmk__new_ordering(child, pcmk__op_key(child->id, action_first, 0),
     366             :                            NULL, NULL, NULL, clone_min_met,
     367             :                            pcmk__ar_min_runnable
     368             :                            |pcmk__ar_first_implies_then_graphed,
     369             :                            rsc_first->cluster);
     370             :     }
     371             : 
     372             :     // Order "then" action after the pseudo-action (if runnable)
     373           0 :     pcmk__new_ordering(NULL, NULL, clone_min_met, rsc_then,
     374           0 :                        pcmk__op_key(rsc_then->id, action_then, 0),
     375             :                        NULL, flags|pcmk__ar_unrunnable_first_blocks,
     376             :                        rsc_first->cluster);
     377           0 : }
     378             : 
     379             : /*!
     380             :  * \internal
     381             :  * \brief Update ordering flags for restart-type=restart
     382             :  *
     383             :  * \param[in]     rsc    'Then' resource in ordering
     384             :  * \param[in]     kind   Ordering kind
     385             :  * \param[in]     flag   Ordering flag to set (when applicable)
     386             :  * \param[in,out] flags  Ordering flag set to update
     387             :  *
     388             :  * \compat The \c PCMK__META_RESTART_TYPE resource meta-attribute is deprecated.
     389             :  *         Eventually, it will be removed, and \c pe_restart_ignore will be the
     390             :  *         only behavior, at which time this can just be removed entirely.
     391             :  */
     392             : #define handle_restart_type(rsc, kind, flag, flags) do {        \
     393             :         if (((kind) == pe_order_kind_optional)                  \
     394             :             && ((rsc)->restart_type == pe_restart_restart)) {   \
     395             :             pcmk__set_relation_flags((flags), (flag));          \
     396             :         }                                                       \
     397             :     } while (0)
     398             : 
     399             : /*!
     400             :  * \internal
     401             :  * \brief Create new ordering for inverse of symmetric constraint
     402             :  *
     403             :  * \param[in]     id            Ordering ID (for logging only)
     404             :  * \param[in]     kind          Ordering kind
     405             :  * \param[in]     rsc_first     'First' resource in ordering (a clone)
     406             :  * \param[in]     action_first  'First' action in ordering
     407             :  * \param[in,out] rsc_then      'Then' resource in ordering
     408             :  * \param[in]     action_then   'Then' action in ordering
     409             :  */
     410             : static void
     411           0 : inverse_ordering(const char *id, enum pe_order_kind kind,
     412             :                  pcmk_resource_t *rsc_first, const char *action_first,
     413             :                  pcmk_resource_t *rsc_then, const char *action_then)
     414             : {
     415           0 :     action_then = invert_action(action_then);
     416           0 :     action_first = invert_action(action_first);
     417           0 :     if ((action_then == NULL) || (action_first == NULL)) {
     418           0 :         pcmk__config_warn("Cannot invert constraint '%s' "
     419             :                           "(please specify inverse manually)", id);
     420             :     } else {
     421           0 :         uint32_t flags = ordering_flags_for_kind(kind, action_first,
     422             :                                                  ordering_symmetric_inverse);
     423             : 
     424           0 :         handle_restart_type(rsc_then, kind, pcmk__ar_then_implies_first, flags);
     425           0 :         pcmk__order_resource_actions(rsc_then, action_then, rsc_first,
     426             :                                      action_first, flags);
     427             :     }
     428           0 : }
     429             : 
     430             : static void
     431           0 : unpack_simple_rsc_order(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     432             : {
     433           0 :     pcmk_resource_t *rsc_then = NULL;
     434           0 :     pcmk_resource_t *rsc_first = NULL;
     435           0 :     int min_required_before = 0;
     436           0 :     enum pe_order_kind kind = pe_order_kind_mandatory;
     437           0 :     uint32_t flags = pcmk__ar_none;
     438             :     enum ordering_symmetry symmetry;
     439             : 
     440           0 :     const char *action_then = NULL;
     441           0 :     const char *action_first = NULL;
     442           0 :     const char *id = NULL;
     443             : 
     444           0 :     CRM_CHECK(xml_obj != NULL, return);
     445             : 
     446           0 :     id = crm_element_value(xml_obj, PCMK_XA_ID);
     447           0 :     if (id == NULL) {
     448           0 :         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
     449             :                          xml_obj->name);
     450           0 :         return;
     451             :     }
     452             : 
     453           0 :     rsc_first = get_ordering_resource(xml_obj, PCMK_XA_FIRST,
     454             :                                       PCMK__XA_FIRST_INSTANCE, scheduler);
     455           0 :     if (rsc_first == NULL) {
     456           0 :         return;
     457             :     }
     458             : 
     459           0 :     rsc_then = get_ordering_resource(xml_obj, PCMK_XA_THEN,
     460             :                                      PCMK__XA_THEN_INSTANCE, scheduler);
     461           0 :     if (rsc_then == NULL) {
     462           0 :         return;
     463             :     }
     464             : 
     465           0 :     action_first = crm_element_value(xml_obj, PCMK_XA_FIRST_ACTION);
     466           0 :     if (action_first == NULL) {
     467           0 :         action_first = PCMK_ACTION_START;
     468             :     }
     469             : 
     470           0 :     action_then = crm_element_value(xml_obj, PCMK_XA_THEN_ACTION);
     471           0 :     if (action_then == NULL) {
     472           0 :         action_then = action_first;
     473             :     }
     474             : 
     475           0 :     kind = get_ordering_type(xml_obj);
     476             : 
     477           0 :     symmetry = get_ordering_symmetry(xml_obj, kind, NULL);
     478           0 :     flags = ordering_flags_for_kind(kind, action_first, symmetry);
     479             : 
     480           0 :     handle_restart_type(rsc_then, kind, pcmk__ar_first_implies_then, flags);
     481             : 
     482             :     /* If there is a minimum number of instances that must be runnable before
     483             :      * the 'then' action is runnable, we use a pseudo-action for convenience:
     484             :      * minimum number of clone instances have runnable actions ->
     485             :      * pseudo-action is runnable -> dependency is runnable.
     486             :      */
     487           0 :     min_required_before = get_minimum_first_instances(rsc_first, xml_obj);
     488           0 :     if (min_required_before > 0) {
     489           0 :         clone_min_ordering(id, rsc_first, action_first, rsc_then, action_then,
     490             :                            flags, min_required_before);
     491             :     } else {
     492           0 :         pcmk__order_resource_actions(rsc_first, action_first, rsc_then,
     493             :                                      action_then, flags);
     494             :     }
     495             : 
     496           0 :     if (symmetry == ordering_symmetric) {
     497           0 :         inverse_ordering(id, kind, rsc_first, action_first,
     498             :                          rsc_then, action_then);
     499             :     }
     500             : }
     501             : 
     502             : /*!
     503             :  * \internal
     504             :  * \brief Create a new ordering between two actions
     505             :  *
     506             :  * \param[in,out] first_rsc          Resource for 'first' action (if NULL and
     507             :  *                                   \p first_action is a resource action, that
     508             :  *                                   resource will be used)
     509             :  * \param[in,out] first_action_task  Action key for 'first' action (if NULL and
     510             :  *                                   \p first_action is not NULL, its UUID will
     511             :  *                                   be used)
     512             :  * \param[in,out] first_action       'first' action (if NULL, \p first_rsc and
     513             :  *                                   \p first_action_task must be set)
     514             :  *
     515             :  * \param[in]     then_rsc           Resource for 'then' action (if NULL and
     516             :  *                                   \p then_action is a resource action, that
     517             :  *                                   resource will be used)
     518             :  * \param[in,out] then_action_task   Action key for 'then' action (if NULL and
     519             :  *                                   \p then_action is not NULL, its UUID will
     520             :  *                                   be used)
     521             :  * \param[in]     then_action        'then' action (if NULL, \p then_rsc and
     522             :  *                                   \p then_action_task must be set)
     523             :  *
     524             :  * \param[in]     flags              Group of enum pcmk__action_relation_flags
     525             :  * \param[in,out] sched              Scheduler data to add ordering to
     526             :  *
     527             :  * \note This function takes ownership of first_action_task and
     528             :  *       then_action_task, which do not need to be freed by the caller.
     529             :  */
     530             : void
     531           0 : pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_action_task,
     532             :                    pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
     533             :                    char *then_action_task, pcmk_action_t *then_action,
     534             :                    uint32_t flags, pcmk_scheduler_t *sched)
     535             : {
     536           0 :     pcmk__action_relation_t *order = NULL;
     537             : 
     538             :     // One of action or resource must be specified for each side
     539           0 :     CRM_CHECK(((first_action != NULL) || (first_rsc != NULL))
     540             :               && ((then_action != NULL) || (then_rsc != NULL)),
     541             :               free(first_action_task); free(then_action_task); return);
     542             : 
     543           0 :     if ((first_rsc == NULL) && (first_action != NULL)) {
     544           0 :         first_rsc = first_action->rsc;
     545             :     }
     546           0 :     if ((then_rsc == NULL) && (then_action != NULL)) {
     547           0 :         then_rsc = then_action->rsc;
     548             :     }
     549             : 
     550           0 :     order = pcmk__assert_alloc(1, sizeof(pcmk__action_relation_t));
     551             : 
     552           0 :     order->id = sched->order_id++;
     553           0 :     order->flags = flags;
     554           0 :     order->rsc1 = first_rsc;
     555           0 :     order->rsc2 = then_rsc;
     556           0 :     order->action1 = first_action;
     557           0 :     order->action2 = then_action;
     558           0 :     order->task1 = first_action_task;
     559           0 :     order->task2 = then_action_task;
     560             : 
     561           0 :     if ((order->task1 == NULL) && (first_action != NULL)) {
     562           0 :         order->task1 = strdup(first_action->uuid);
     563             :     }
     564             : 
     565           0 :     if ((order->task2 == NULL) && (then_action != NULL)) {
     566           0 :         order->task2 = strdup(then_action->uuid);
     567             :     }
     568             : 
     569           0 :     if ((order->rsc1 == NULL) && (first_action != NULL)) {
     570           0 :         order->rsc1 = first_action->rsc;
     571             :     }
     572             : 
     573           0 :     if ((order->rsc2 == NULL) && (then_action != NULL)) {
     574           0 :         order->rsc2 = then_action->rsc;
     575             :     }
     576             : 
     577           0 :     pcmk__rsc_trace(first_rsc, "Created ordering %d for %s then %s",
     578             :                     (sched->order_id - 1),
     579             :                     pcmk__s(order->task1, "an underspecified action"),
     580             :                     pcmk__s(order->task2, "an underspecified action"));
     581             : 
     582           0 :     sched->ordering_constraints = g_list_prepend(sched->ordering_constraints,
     583             :                                                  order);
     584           0 :     pcmk__order_migration_equivalents(order);
     585             : }
     586             : 
     587             : /*!
     588             :  * \brief Unpack a set in an ordering constraint
     589             :  *
     590             :  * \param[in]     set                   Set XML to unpack
     591             :  * \param[in]     parent_kind           \c PCMK_XE_RSC_ORDER XML \c PCMK_XA_KIND
     592             :  *                                      attribute
     593             :  * \param[in]     parent_symmetrical_s  \c PCMK_XE_RSC_ORDER XML
     594             :  *                                      \c PCMK_XA_SYMMETRICAL attribute
     595             :  * \param[in,out] scheduler             Scheduler data
     596             :  *
     597             :  * \return Standard Pacemaker return code
     598             :  */
     599             : static int
     600           0 : unpack_order_set(const xmlNode *set, enum pe_order_kind parent_kind,
     601             :                  const char *parent_symmetrical_s, pcmk_scheduler_t *scheduler)
     602             : {
     603           0 :     GList *set_iter = NULL;
     604           0 :     GList *resources = NULL;
     605             : 
     606           0 :     pcmk_resource_t *last = NULL;
     607           0 :     pcmk_resource_t *resource = NULL;
     608             : 
     609           0 :     int local_kind = parent_kind;
     610           0 :     bool sequential = false;
     611           0 :     uint32_t flags = pcmk__ar_ordered;
     612             :     enum ordering_symmetry symmetry;
     613             : 
     614           0 :     char *key = NULL;
     615           0 :     const char *id = pcmk__xe_id(set);
     616           0 :     const char *action = crm_element_value(set, PCMK_XA_ACTION);
     617           0 :     const char *sequential_s = crm_element_value(set, PCMK_XA_SEQUENTIAL);
     618           0 :     const char *kind_s = crm_element_value(set, PCMK_XA_KIND);
     619             : 
     620           0 :     if (action == NULL) {
     621           0 :         action = PCMK_ACTION_START;
     622             :     }
     623             : 
     624           0 :     if (kind_s) {
     625           0 :         local_kind = get_ordering_type(set);
     626             :     }
     627           0 :     if (sequential_s == NULL) {
     628           0 :         sequential_s = "1";
     629             :     }
     630             : 
     631           0 :     sequential = crm_is_true(sequential_s);
     632             : 
     633           0 :     symmetry = get_ordering_symmetry(set, parent_kind, parent_symmetrical_s);
     634           0 :     flags = ordering_flags_for_kind(local_kind, action, symmetry);
     635             : 
     636           0 :     for (const xmlNode *xml_rsc = pcmk__xe_first_child(set,
     637             :                                                        PCMK_XE_RESOURCE_REF,
     638             :                                                        NULL, NULL);
     639           0 :          xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     640             : 
     641           0 :         EXPAND_CONSTRAINT_IDREF(id, resource, pcmk__xe_id(xml_rsc));
     642           0 :         resources = g_list_append(resources, resource);
     643             :     }
     644             : 
     645           0 :     if (pcmk__list_of_1(resources)) {
     646           0 :         crm_trace("Single set: %s", id);
     647           0 :         goto done;
     648             :     }
     649             : 
     650           0 :     set_iter = resources;
     651           0 :     while (set_iter != NULL) {
     652           0 :         resource = (pcmk_resource_t *) set_iter->data;
     653           0 :         set_iter = set_iter->next;
     654             : 
     655           0 :         key = pcmk__op_key(resource->id, action, 0);
     656             : 
     657           0 :         if (local_kind == pe_order_kind_serialize) {
     658             :             /* Serialize before everything that comes after */
     659             : 
     660           0 :             for (GList *iter = set_iter; iter != NULL; iter = iter->next) {
     661           0 :                 pcmk_resource_t *then_rsc = iter->data;
     662           0 :                 char *then_key = pcmk__op_key(then_rsc->id, action, 0);
     663             : 
     664           0 :                 pcmk__new_ordering(resource, strdup(key), NULL, then_rsc,
     665             :                                    then_key, NULL, flags, scheduler);
     666             :             }
     667             : 
     668           0 :         } else if (sequential) {
     669           0 :             if (last != NULL) {
     670           0 :                 pcmk__order_resource_actions(last, action, resource, action,
     671             :                                              flags);
     672             :             }
     673           0 :             last = resource;
     674             :         }
     675           0 :         free(key);
     676             :     }
     677             : 
     678           0 :     if (symmetry == ordering_asymmetric) {
     679           0 :         goto done;
     680             :     }
     681             : 
     682           0 :     last = NULL;
     683           0 :     action = invert_action(action);
     684             : 
     685           0 :     flags = ordering_flags_for_kind(local_kind, action,
     686             :                                     ordering_symmetric_inverse);
     687             : 
     688           0 :     set_iter = resources;
     689           0 :     while (set_iter != NULL) {
     690           0 :         resource = (pcmk_resource_t *) set_iter->data;
     691           0 :         set_iter = set_iter->next;
     692             : 
     693           0 :         if (sequential) {
     694           0 :             if (last != NULL) {
     695           0 :                 pcmk__order_resource_actions(resource, action, last, action,
     696             :                                              flags);
     697             :             }
     698           0 :             last = resource;
     699             :         }
     700             :     }
     701             : 
     702           0 :   done:
     703           0 :     g_list_free(resources);
     704           0 :     return pcmk_rc_ok;
     705             : }
     706             : 
     707             : /*!
     708             :  * \brief Order two resource sets relative to each other
     709             :  *
     710             :  * \param[in]     id         Ordering ID (for logging)
     711             :  * \param[in]     set1       First listed set
     712             :  * \param[in]     set2       Second listed set
     713             :  * \param[in]     kind       Ordering kind
     714             :  * \param[in,out] scheduler  Scheduler data
     715             :  * \param[in]     symmetry   Which ordering symmetry applies to this relation
     716             :  *
     717             :  * \return Standard Pacemaker return code
     718             :  */
     719             : static int
     720           0 : order_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
     721             :                enum pe_order_kind kind, pcmk_scheduler_t *scheduler,
     722             :                enum ordering_symmetry symmetry)
     723             : {
     724             : 
     725           0 :     const xmlNode *xml_rsc = NULL;
     726           0 :     const xmlNode *xml_rsc_2 = NULL;
     727             : 
     728           0 :     pcmk_resource_t *rsc_1 = NULL;
     729           0 :     pcmk_resource_t *rsc_2 = NULL;
     730             : 
     731           0 :     const char *action_1 = crm_element_value(set1, PCMK_XA_ACTION);
     732           0 :     const char *action_2 = crm_element_value(set2, PCMK_XA_ACTION);
     733             : 
     734           0 :     uint32_t flags = pcmk__ar_none;
     735             : 
     736           0 :     bool require_all = true;
     737             : 
     738           0 :     (void) pcmk__xe_get_bool_attr(set1, PCMK_XA_REQUIRE_ALL, &require_all);
     739             : 
     740           0 :     if (action_1 == NULL) {
     741           0 :         action_1 = PCMK_ACTION_START;
     742             :     }
     743             : 
     744           0 :     if (action_2 == NULL) {
     745           0 :         action_2 = PCMK_ACTION_START;
     746             :     }
     747             : 
     748           0 :     if (symmetry == ordering_symmetric_inverse) {
     749           0 :         action_1 = invert_action(action_1);
     750           0 :         action_2 = invert_action(action_2);
     751             :     }
     752             : 
     753           0 :     if (pcmk__str_eq(PCMK_ACTION_STOP, action_1, pcmk__str_none)
     754           0 :         || pcmk__str_eq(PCMK_ACTION_DEMOTE, action_1, pcmk__str_none)) {
     755             :         /* Assuming: A -> ( B || C) -> D
     756             :          * The one-or-more logic only applies during the start/promote phase.
     757             :          * During shutdown neither B nor can shutdown until D is down, so simply
     758             :          * turn require_all back on.
     759             :          */
     760           0 :         require_all = true;
     761             :     }
     762             : 
     763           0 :     flags = ordering_flags_for_kind(kind, action_1, symmetry);
     764             : 
     765             :     /* If we have an unordered set1, whether it is sequential or not is
     766             :      * irrelevant in regards to set2.
     767             :      */
     768           0 :     if (!require_all) {
     769           0 :         char *task = crm_strdup_printf(PCMK_ACTION_ONE_OR_MORE ":%s",
     770             :                                        pcmk__xe_id(set1));
     771           0 :         pcmk_action_t *unordered_action = get_pseudo_op(task, scheduler);
     772             : 
     773           0 :         free(task);
     774           0 :         pcmk__set_action_flags(unordered_action, pcmk_action_min_runnable);
     775             : 
     776           0 :         for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
     777             :                                             NULL);
     778           0 :              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     779             : 
     780           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
     781             : 
     782             :             /* Add an ordering constraint between every element in set1 and the
     783             :              * pseudo action. If any action in set1 is runnable the pseudo
     784             :              * action will be runnable.
     785             :              */
     786           0 :             pcmk__new_ordering(rsc_1, pcmk__op_key(rsc_1->id, action_1, 0),
     787             :                                NULL, NULL, NULL, unordered_action,
     788             :                                pcmk__ar_min_runnable
     789             :                                |pcmk__ar_first_implies_then_graphed,
     790             :                                scheduler);
     791             :         }
     792           0 :         for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
     793             :                                               NULL);
     794           0 :              xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
     795             : 
     796           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc_2));
     797             : 
     798             :             /* Add an ordering constraint between the pseudo-action and every
     799             :              * element in set2. If the pseudo-action is runnable, every action
     800             :              * in set2 will be runnable.
     801             :              */
     802           0 :             pcmk__new_ordering(NULL, NULL, unordered_action,
     803           0 :                                rsc_2, pcmk__op_key(rsc_2->id, action_2, 0),
     804             :                                NULL, flags|pcmk__ar_unrunnable_first_blocks,
     805             :                                scheduler);
     806             :         }
     807             : 
     808           0 :         return pcmk_rc_ok;
     809             :     }
     810             : 
     811           0 :     if (pcmk__xe_attr_is_true(set1, PCMK_XA_SEQUENTIAL)) {
     812           0 :         if (symmetry == ordering_symmetric_inverse) {
     813             :             // Get the first one
     814           0 :             xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
     815             :                                            NULL);
     816           0 :             if (xml_rsc != NULL) {
     817           0 :                 EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
     818             :             }
     819             : 
     820             :         } else {
     821             :             // Get the last one
     822           0 :             const char *rid = NULL;
     823             : 
     824           0 :             for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF,
     825             :                                                 NULL, NULL);
     826           0 :                  xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     827             : 
     828           0 :                 rid = pcmk__xe_id(xml_rsc);
     829             :             }
     830           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_1, rid);
     831             :         }
     832             :     }
     833             : 
     834           0 :     if (pcmk__xe_attr_is_true(set2, PCMK_XA_SEQUENTIAL)) {
     835           0 :         if (symmetry == ordering_symmetric_inverse) {
     836             :             // Get the last one
     837           0 :             const char *rid = NULL;
     838             : 
     839           0 :             for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF,
     840             :                                                 NULL, NULL);
     841           0 :                  xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     842             : 
     843           0 :                 rid = pcmk__xe_id(xml_rsc);
     844             :             }
     845           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
     846             : 
     847             :         } else {
     848             :             // Get the first one
     849           0 :             xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
     850             :                                            NULL);
     851           0 :             if (xml_rsc != NULL) {
     852           0 :                 EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc));
     853             :             }
     854             :         }
     855             :     }
     856             : 
     857           0 :     if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
     858           0 :         pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2, flags);
     859             : 
     860           0 :     } else if (rsc_1 != NULL) {
     861           0 :         for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
     862             :                                             NULL);
     863           0 :              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     864             : 
     865           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc));
     866           0 :             pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
     867             :                                          flags);
     868             :         }
     869             : 
     870           0 :     } else if (rsc_2 != NULL) {
     871           0 :         for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
     872             :                                             NULL);
     873           0 :              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     874             : 
     875           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
     876           0 :             pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
     877             :                                          flags);
     878             :         }
     879             : 
     880             :     } else {
     881           0 :         for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
     882             :                                             NULL);
     883           0 :              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
     884             : 
     885           0 :             EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
     886             : 
     887           0 :             for (xmlNode *xml_rsc_2 = pcmk__xe_first_child(set2,
     888             :                                                            PCMK_XE_RESOURCE_REF,
     889             :                                                            NULL, NULL);
     890           0 :                  xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
     891             : 
     892           0 :                 EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc_2));
     893           0 :                 pcmk__order_resource_actions(rsc_1, action_1, rsc_2,
     894             :                                              action_2, flags);
     895             :             }
     896             :         }
     897             :     }
     898             : 
     899           0 :     return pcmk_rc_ok;
     900             : }
     901             : 
     902             : /*!
     903             :  * \internal
     904             :  * \brief If an ordering constraint uses resource tags, expand them
     905             :  *
     906             :  * \param[in,out] xml_obj       Ordering constraint XML
     907             :  * \param[out]    expanded_xml  Equivalent XML with tags expanded
     908             :  * \param[in]     scheduler     Scheduler data
     909             :  *
     910             :  * \return Standard Pacemaker return code (specifically, pcmk_rc_ok on success,
     911             :  *         and pcmk_rc_unpack_error on invalid configuration)
     912             :  */
     913             : static int
     914           0 : unpack_order_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
     915             :                   const pcmk_scheduler_t *scheduler)
     916             : {
     917           0 :     const char *id_first = NULL;
     918           0 :     const char *id_then = NULL;
     919           0 :     const char *action_first = NULL;
     920           0 :     const char *action_then = NULL;
     921             : 
     922           0 :     pcmk_resource_t *rsc_first = NULL;
     923           0 :     pcmk_resource_t *rsc_then = NULL;
     924           0 :     pcmk_tag_t *tag_first = NULL;
     925           0 :     pcmk_tag_t *tag_then = NULL;
     926             : 
     927           0 :     xmlNode *rsc_set_first = NULL;
     928           0 :     xmlNode *rsc_set_then = NULL;
     929           0 :     bool any_sets = false;
     930             : 
     931             :     // Check whether there are any resource sets with template or tag references
     932           0 :     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
     933           0 :     if (*expanded_xml != NULL) {
     934           0 :         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_ORDER);
     935           0 :         return pcmk_rc_ok;
     936             :     }
     937             : 
     938           0 :     id_first = crm_element_value(xml_obj, PCMK_XA_FIRST);
     939           0 :     id_then = crm_element_value(xml_obj, PCMK_XA_THEN);
     940           0 :     if ((id_first == NULL) || (id_then == NULL)) {
     941           0 :         return pcmk_rc_ok;
     942             :     }
     943             : 
     944           0 :     if (!pcmk__valid_resource_or_tag(scheduler, id_first, &rsc_first,
     945             :                                      &tag_first)) {
     946           0 :         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
     947             :                          "valid resource or tag",
     948             :                          pcmk__xe_id(xml_obj), id_first);
     949           0 :         return pcmk_rc_unpack_error;
     950             :     }
     951             : 
     952           0 :     if (!pcmk__valid_resource_or_tag(scheduler, id_then, &rsc_then,
     953             :                                      &tag_then)) {
     954           0 :         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
     955             :                          "valid resource or tag",
     956             :                          pcmk__xe_id(xml_obj), id_then);
     957           0 :         return pcmk_rc_unpack_error;
     958             :     }
     959             : 
     960           0 :     if ((rsc_first != NULL) && (rsc_then != NULL)) {
     961             :         // Neither side references a template or tag
     962           0 :         return pcmk_rc_ok;
     963             :     }
     964             : 
     965           0 :     action_first = crm_element_value(xml_obj, PCMK_XA_FIRST_ACTION);
     966           0 :     action_then = crm_element_value(xml_obj, PCMK_XA_THEN_ACTION);
     967             : 
     968           0 :     *expanded_xml = pcmk__xml_copy(NULL, xml_obj);
     969             : 
     970             :     /* Convert template/tag reference in PCMK_XA_FIRST into constraint
     971             :      * PCMK_XE_RESOURCE_SET
     972             :      */
     973           0 :     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_first, PCMK_XA_FIRST, true,
     974             :                           scheduler)) {
     975           0 :         free_xml(*expanded_xml);
     976           0 :         *expanded_xml = NULL;
     977           0 :         return pcmk_rc_unpack_error;
     978             :     }
     979             : 
     980           0 :     if (rsc_set_first != NULL) {
     981           0 :         if (action_first != NULL) {
     982             :             /* Move PCMK_XA_FIRST_ACTION into converted PCMK_XE_RESOURCE_SET as
     983             :              * PCMK_XA_ACTION
     984             :              */
     985           0 :             crm_xml_add(rsc_set_first, PCMK_XA_ACTION, action_first);
     986           0 :             pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_FIRST_ACTION);
     987             :         }
     988           0 :         any_sets = true;
     989             :     }
     990             : 
     991             :     /* Convert template/tag reference in PCMK_XA_THEN into constraint
     992             :      * PCMK_XE_RESOURCE_SET
     993             :      */
     994           0 :     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_then, PCMK_XA_THEN, true,
     995             :                           scheduler)) {
     996           0 :         free_xml(*expanded_xml);
     997           0 :         *expanded_xml = NULL;
     998           0 :         return pcmk_rc_unpack_error;
     999             :     }
    1000             : 
    1001           0 :     if (rsc_set_then != NULL) {
    1002           0 :         if (action_then != NULL) {
    1003             :             /* Move PCMK_XA_THEN_ACTION into converted PCMK_XE_RESOURCE_SET as
    1004             :              * PCMK_XA_ACTION
    1005             :              */
    1006           0 :             crm_xml_add(rsc_set_then, PCMK_XA_ACTION, action_then);
    1007           0 :             pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_THEN_ACTION);
    1008             :         }
    1009           0 :         any_sets = true;
    1010             :     }
    1011             : 
    1012           0 :     if (any_sets) {
    1013           0 :         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_ORDER);
    1014             :     } else {
    1015           0 :         free_xml(*expanded_xml);
    1016           0 :         *expanded_xml = NULL;
    1017             :     }
    1018             : 
    1019           0 :     return pcmk_rc_ok;
    1020             : }
    1021             : 
    1022             : /*!
    1023             :  * \internal
    1024             :  * \brief Unpack ordering constraint XML
    1025             :  *
    1026             :  * \param[in,out] xml_obj    Ordering constraint XML to unpack
    1027             :  * \param[in,out] scheduler  Scheduler data
    1028             :  */
    1029             : void
    1030           0 : pcmk__unpack_ordering(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
    1031             : {
    1032           0 :     xmlNode *set = NULL;
    1033           0 :     xmlNode *last = NULL;
    1034             : 
    1035           0 :     xmlNode *orig_xml = NULL;
    1036           0 :     xmlNode *expanded_xml = NULL;
    1037             : 
    1038           0 :     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
    1039           0 :     const char *invert = crm_element_value(xml_obj, PCMK_XA_SYMMETRICAL);
    1040           0 :     enum pe_order_kind kind = get_ordering_type(xml_obj);
    1041             : 
    1042           0 :     enum ordering_symmetry symmetry = get_ordering_symmetry(xml_obj, kind,
    1043             :                                                             NULL);
    1044             : 
    1045             :     // Expand any resource tags in the constraint XML
    1046           0 :     if (unpack_order_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) {
    1047           0 :         return;
    1048             :     }
    1049           0 :     if (expanded_xml != NULL) {
    1050           0 :         orig_xml = xml_obj;
    1051           0 :         xml_obj = expanded_xml;
    1052             :     }
    1053             : 
    1054             :     // If the constraint has resource sets, unpack them
    1055           0 :     for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
    1056           0 :          set != NULL; set = pcmk__xe_next_same(set)) {
    1057             : 
    1058           0 :         set = expand_idref(set, scheduler->input);
    1059           0 :         if ((set == NULL) // Configuration error, message already logged
    1060           0 :             || (unpack_order_set(set, kind, invert, scheduler) != pcmk_rc_ok)) {
    1061             : 
    1062           0 :             if (expanded_xml != NULL) {
    1063           0 :                 free_xml(expanded_xml);
    1064             :             }
    1065           0 :             return;
    1066             :         }
    1067             : 
    1068           0 :         if (last != NULL) {
    1069             : 
    1070           0 :             if (order_rsc_sets(id, last, set, kind, scheduler,
    1071             :                                symmetry) != pcmk_rc_ok) {
    1072           0 :                 if (expanded_xml != NULL) {
    1073           0 :                     free_xml(expanded_xml);
    1074             :                 }
    1075           0 :                 return;
    1076             :             }
    1077             : 
    1078           0 :             if ((symmetry == ordering_symmetric)
    1079           0 :                 && (order_rsc_sets(id, set, last, kind, scheduler,
    1080             :                                    ordering_symmetric_inverse) != pcmk_rc_ok)) {
    1081           0 :                 if (expanded_xml != NULL) {
    1082           0 :                     free_xml(expanded_xml);
    1083             :                 }
    1084           0 :                 return;
    1085             :             }
    1086             : 
    1087             :         }
    1088           0 :         last = set;
    1089             :     }
    1090             : 
    1091           0 :     if (expanded_xml) {
    1092           0 :         free_xml(expanded_xml);
    1093           0 :         xml_obj = orig_xml;
    1094             :     }
    1095             : 
    1096             :     // If the constraint has no resource sets, unpack it as a simple ordering
    1097           0 :     if (last == NULL) {
    1098           0 :         return unpack_simple_rsc_order(xml_obj, scheduler);
    1099             :     }
    1100             : }
    1101             : 
    1102             : static bool
    1103           0 : ordering_is_invalid(pcmk_action_t *action, pcmk__related_action_t *input)
    1104             : {
    1105             :     /* Prevent user-defined ordering constraints between resources
    1106             :      * running in a guest node and the resource that defines that node.
    1107             :      */
    1108           0 :     if (!pcmk_is_set(input->type, pcmk__ar_guest_allowed)
    1109           0 :         && (input->action->rsc != NULL)
    1110           0 :         && pcmk__rsc_corresponds_to_guest(action->rsc, input->action->node)) {
    1111             : 
    1112           0 :         pcmk__config_warn("Invalid ordering constraint between %s and %s",
    1113             :                           input->action->rsc->id, action->rsc->id);
    1114           0 :         return true;
    1115             :     }
    1116             : 
    1117             :     /* If there's an order like
    1118             :      * "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
    1119             :      *
    1120             :      * then rscA is being migrated from node1 to node2, while rscB is being
    1121             :      * migrated from node2 to node1. If there would be a graph loop,
    1122             :      * break the order "load_stopped_node2" -> "rscA_migrate_to node1".
    1123             :      */
    1124           0 :     if (((uint32_t) input->type == pcmk__ar_if_on_same_node_or_target)
    1125           0 :         && (action->rsc != NULL)
    1126           0 :         && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO, pcmk__str_none)
    1127           0 :         && pcmk__graph_has_loop(action, action, input)) {
    1128           0 :         return true;
    1129             :     }
    1130             : 
    1131           0 :     return false;
    1132             : }
    1133             : 
    1134             : void
    1135           0 : pcmk__disable_invalid_orderings(pcmk_scheduler_t *scheduler)
    1136             : {
    1137           0 :     for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) {
    1138           0 :         pcmk_action_t *action = (pcmk_action_t *) iter->data;
    1139           0 :         pcmk__related_action_t *input = NULL;
    1140             : 
    1141           0 :         for (GList *input_iter = action->actions_before;
    1142           0 :              input_iter != NULL; input_iter = input_iter->next) {
    1143             : 
    1144           0 :             input = input_iter->data;
    1145           0 :             if (ordering_is_invalid(action, input)) {
    1146           0 :                 input->type = (enum pe_ordering) pcmk__ar_none;
    1147             :             }
    1148             :         }
    1149             :     }
    1150           0 : }
    1151             : 
    1152             : /*!
    1153             :  * \internal
    1154             :  * \brief Order stops on a node before the node's shutdown
    1155             :  *
    1156             :  * \param[in,out] node         Node being shut down
    1157             :  * \param[in]     shutdown_op  Shutdown action for node
    1158             :  */
    1159             : void
    1160           0 : pcmk__order_stops_before_shutdown(pcmk_node_t *node, pcmk_action_t *shutdown_op)
    1161             : {
    1162           0 :     for (GList *iter = node->details->data_set->actions;
    1163           0 :          iter != NULL; iter = iter->next) {
    1164             : 
    1165           0 :         pcmk_action_t *action = (pcmk_action_t *) iter->data;
    1166             : 
    1167             :         // Only stops on the node shutting down are relevant
    1168           0 :         if (!pcmk__same_node(action->node, node)
    1169           0 :             || !pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
    1170           0 :             continue;
    1171             :         }
    1172             : 
    1173             :         // Resources and nodes in maintenance mode won't be touched
    1174             : 
    1175           0 :         if (pcmk_is_set(action->rsc->flags, pcmk_rsc_maintenance)) {
    1176           0 :             pcmk__rsc_trace(action->rsc,
    1177             :                             "Not ordering %s before shutdown of %s because "
    1178             :                             "resource in maintenance mode",
    1179             :                             action->uuid, pcmk__node_name(node));
    1180           0 :             continue;
    1181             : 
    1182           0 :         } else if (node->details->maintenance) {
    1183           0 :             pcmk__rsc_trace(action->rsc,
    1184             :                             "Not ordering %s before shutdown of %s because "
    1185             :                             "node in maintenance mode",
    1186             :                             action->uuid, pcmk__node_name(node));
    1187           0 :             continue;
    1188             :         }
    1189             : 
    1190             :         /* Don't touch a resource that is unmanaged or blocked, to avoid
    1191             :          * blocking the shutdown (though if another action depends on this one,
    1192             :          * we may still end up blocking)
    1193             :          */
    1194           0 :         if (!pcmk_any_flags_set(action->rsc->flags,
    1195             :                                 pcmk_rsc_managed|pcmk_rsc_blocked)) {
    1196           0 :             pcmk__rsc_trace(action->rsc,
    1197             :                             "Not ordering %s before shutdown of %s because "
    1198             :                             "resource is unmanaged or blocked",
    1199             :                             action->uuid, pcmk__node_name(node));
    1200           0 :             continue;
    1201             :         }
    1202             : 
    1203           0 :         pcmk__rsc_trace(action->rsc, "Ordering %s before shutdown of %s",
    1204             :                         action->uuid, pcmk__node_name(node));
    1205           0 :         pcmk__clear_action_flags(action, pcmk_action_optional);
    1206           0 :         pcmk__new_ordering(action->rsc, NULL, action, NULL,
    1207             :                            strdup(PCMK_ACTION_DO_SHUTDOWN), shutdown_op,
    1208             :                            pcmk__ar_ordered|pcmk__ar_unrunnable_first_blocks,
    1209           0 :                            node->details->data_set);
    1210             :     }
    1211           0 : }
    1212             : 
    1213             : /*!
    1214             :  * \brief Find resource actions matching directly or as child
    1215             :  *
    1216             :  * \param[in] rsc           Resource to check
    1217             :  * \param[in] original_key  Action key to search for (possibly referencing
    1218             :  *                          parent of \rsc)
    1219             :  *
    1220             :  * \return Newly allocated list of matching actions
    1221             :  * \note It is the caller's responsibility to free the result with g_list_free()
    1222             :  */
    1223             : static GList *
    1224           0 : find_actions_by_task(const pcmk_resource_t *rsc, const char *original_key)
    1225             : {
    1226             :     // Search under given task key directly
    1227           0 :     GList *list = find_actions(rsc->actions, original_key, NULL);
    1228             : 
    1229           0 :     if (list == NULL) {
    1230             :         // Search again using this resource's ID
    1231           0 :         char *key = NULL;
    1232           0 :         char *task = NULL;
    1233           0 :         guint interval_ms = 0;
    1234             : 
    1235           0 :         CRM_CHECK(parse_op_key(original_key, NULL, &task, &interval_ms),
    1236             :                   return NULL);
    1237           0 :         key = pcmk__op_key(rsc->id, task, interval_ms);
    1238           0 :         list = find_actions(rsc->actions, key, NULL);
    1239           0 :         free(key);
    1240           0 :         free(task);
    1241             :     }
    1242           0 :     return list;
    1243             : }
    1244             : 
    1245             : /*!
    1246             :  * \internal
    1247             :  * \brief Order relevant resource actions after a given action
    1248             :  *
    1249             :  * \param[in,out] first_action  Action to order after (or NULL if none runnable)
    1250             :  * \param[in]     rsc           Resource whose actions should be ordered
    1251             :  * \param[in,out] order         Ordering constraint being applied
    1252             :  */
    1253             : static void
    1254           0 : order_resource_actions_after(pcmk_action_t *first_action,
    1255             :                              const pcmk_resource_t *rsc,
    1256             :                              pcmk__action_relation_t *order)
    1257             : {
    1258           0 :     GList *then_actions = NULL;
    1259           0 :     uint32_t flags = pcmk__ar_none;
    1260             : 
    1261           0 :     CRM_CHECK((rsc != NULL) && (order != NULL), return);
    1262             : 
    1263           0 :     flags = order->flags;
    1264           0 :     pcmk__rsc_trace(rsc, "Applying ordering %d for 'then' resource %s",
    1265             :                     order->id, rsc->id);
    1266             : 
    1267           0 :     if (order->action2 != NULL) {
    1268           0 :         then_actions = g_list_prepend(NULL, order->action2);
    1269             : 
    1270             :     } else {
    1271           0 :         then_actions = find_actions_by_task(rsc, order->task2);
    1272             :     }
    1273             : 
    1274           0 :     if (then_actions == NULL) {
    1275           0 :         pcmk__rsc_trace(rsc, "Ignoring ordering %d: no %s actions found for %s",
    1276             :                         order->id, order->task2, rsc->id);
    1277           0 :         return;
    1278             :     }
    1279             : 
    1280           0 :     if ((first_action != NULL) && (first_action->rsc == rsc)
    1281           0 :         && pcmk_is_set(first_action->flags, pcmk_action_migration_abort)) {
    1282             : 
    1283           0 :         pcmk__rsc_trace(rsc,
    1284             :                         "Detected dangling migration ordering (%s then %s %s)",
    1285             :                         first_action->uuid, order->task2, rsc->id);
    1286           0 :         pcmk__clear_relation_flags(flags, pcmk__ar_first_implies_then);
    1287             :     }
    1288             : 
    1289           0 :     if ((first_action == NULL)
    1290           0 :         && !pcmk_is_set(flags, pcmk__ar_first_implies_then)) {
    1291             : 
    1292           0 :         pcmk__rsc_debug(rsc,
    1293             :                         "Ignoring ordering %d for %s: No first action found",
    1294             :                         order->id, rsc->id);
    1295           0 :         g_list_free(then_actions);
    1296           0 :         return;
    1297             :     }
    1298             : 
    1299           0 :     for (GList *iter = then_actions; iter != NULL; iter = iter->next) {
    1300           0 :         pcmk_action_t *then_action_iter = (pcmk_action_t *) iter->data;
    1301             : 
    1302           0 :         if (first_action != NULL) {
    1303           0 :             order_actions(first_action, then_action_iter, flags);
    1304             :         } else {
    1305           0 :             pcmk__clear_action_flags(then_action_iter, pcmk_action_runnable);
    1306           0 :             crm_warn("%s of %s is unrunnable because there is no %s of %s "
    1307             :                      "to order it after", then_action_iter->task, rsc->id,
    1308             :                      order->task1, order->rsc1->id);
    1309             :         }
    1310             :     }
    1311             : 
    1312           0 :     g_list_free(then_actions);
    1313             : }
    1314             : 
    1315             : static void
    1316           0 : rsc_order_first(pcmk_resource_t *first_rsc, pcmk__action_relation_t *order)
    1317             : {
    1318           0 :     GList *first_actions = NULL;
    1319           0 :     pcmk_action_t *first_action = order->action1;
    1320           0 :     pcmk_resource_t *then_rsc = order->rsc2;
    1321             : 
    1322           0 :     CRM_ASSERT(first_rsc != NULL);
    1323           0 :     pcmk__rsc_trace(first_rsc, "Applying ordering constraint %d (first: %s)",
    1324             :                     order->id, first_rsc->id);
    1325             : 
    1326           0 :     if (first_action != NULL) {
    1327           0 :         first_actions = g_list_prepend(NULL, first_action);
    1328             : 
    1329             :     } else {
    1330           0 :         first_actions = find_actions_by_task(first_rsc, order->task1);
    1331             :     }
    1332             : 
    1333           0 :     if ((first_actions == NULL) && (first_rsc == then_rsc)) {
    1334           0 :         pcmk__rsc_trace(first_rsc,
    1335             :                         "Ignoring constraint %d: first (%s for %s) not found",
    1336             :                         order->id, order->task1, first_rsc->id);
    1337             : 
    1338           0 :     } else if (first_actions == NULL) {
    1339           0 :         char *key = NULL;
    1340           0 :         char *op_type = NULL;
    1341           0 :         guint interval_ms = 0;
    1342             : 
    1343           0 :         parse_op_key(order->task1, NULL, &op_type, &interval_ms);
    1344           0 :         key = pcmk__op_key(first_rsc->id, op_type, interval_ms);
    1345             : 
    1346           0 :         if ((first_rsc->fns->state(first_rsc, TRUE) == pcmk_role_stopped)
    1347           0 :             && pcmk__str_eq(op_type, PCMK_ACTION_STOP, pcmk__str_none)) {
    1348           0 :             free(key);
    1349           0 :             pcmk__rsc_trace(first_rsc,
    1350             :                             "Ignoring constraint %d: first (%s for %s) "
    1351             :                             "not found",
    1352             :                             order->id, order->task1, first_rsc->id);
    1353             : 
    1354           0 :         } else if ((first_rsc->fns->state(first_rsc,
    1355             :                                           TRUE) == pcmk_role_unpromoted)
    1356           0 :                    && pcmk__str_eq(op_type, PCMK_ACTION_DEMOTE,
    1357             :                                    pcmk__str_none)) {
    1358           0 :             free(key);
    1359           0 :             pcmk__rsc_trace(first_rsc,
    1360             :                             "Ignoring constraint %d: first (%s for %s) "
    1361             :                             "not found",
    1362             :                             order->id, order->task1, first_rsc->id);
    1363             : 
    1364             :         } else {
    1365           0 :             pcmk__rsc_trace(first_rsc,
    1366             :                             "Creating first (%s for %s) for constraint %d ",
    1367             :                             order->task1, first_rsc->id, order->id);
    1368           0 :             first_action = custom_action(first_rsc, key, op_type, NULL, TRUE,
    1369             :                                          first_rsc->cluster);
    1370           0 :             first_actions = g_list_prepend(NULL, first_action);
    1371             :         }
    1372             : 
    1373           0 :         free(op_type);
    1374             :     }
    1375             : 
    1376           0 :     if (then_rsc == NULL) {
    1377           0 :         if (order->action2 == NULL) {
    1378           0 :             pcmk__rsc_trace(first_rsc, "Ignoring constraint %d: then not found",
    1379             :                             order->id);
    1380           0 :             return;
    1381             :         }
    1382           0 :         then_rsc = order->action2->rsc;
    1383             :     }
    1384           0 :     for (GList *iter = first_actions; iter != NULL; iter = iter->next) {
    1385           0 :         first_action = iter->data;
    1386             : 
    1387           0 :         if (then_rsc == NULL) {
    1388           0 :             order_actions(first_action, order->action2, order->flags);
    1389             : 
    1390             :         } else {
    1391           0 :             order_resource_actions_after(first_action, then_rsc, order);
    1392             :         }
    1393             :     }
    1394             : 
    1395           0 :     g_list_free(first_actions);
    1396             : }
    1397             : 
    1398             : // GFunc to call pcmk__block_colocation_dependents()
    1399             : static void
    1400           0 : block_colocation_dependents(gpointer data, gpointer user_data)
    1401             : {
    1402           0 :     pcmk__block_colocation_dependents(data);
    1403           0 : }
    1404             : 
    1405             : // GFunc to call pcmk__update_action_for_orderings()
    1406             : static void
    1407           0 : update_action_for_orderings(gpointer data, gpointer user_data)
    1408             : {
    1409           0 :     pcmk__update_action_for_orderings((pcmk_action_t *) data,
    1410             :                                       (pcmk_scheduler_t *) user_data);
    1411           0 : }
    1412             : 
    1413             : /*!
    1414             :  * \internal
    1415             :  * \brief Apply all ordering constraints
    1416             :  *
    1417             :  * \param[in,out] sched  Scheduler data
    1418             :  */
    1419             : void
    1420           0 : pcmk__apply_orderings(pcmk_scheduler_t *sched)
    1421             : {
    1422           0 :     crm_trace("Applying ordering constraints");
    1423             : 
    1424             :     /* Ordering constraints need to be processed in the order they were created.
    1425             :      * rsc_order_first() and order_resource_actions_after() require the relevant
    1426             :      * actions to already exist in some cases, but rsc_order_first() will create
    1427             :      * the 'first' action in certain cases. Thus calling rsc_order_first() can
    1428             :      * change the behavior of later-created orderings.
    1429             :      *
    1430             :      * Also, g_list_append() should be avoided for performance reasons, so we
    1431             :      * prepend orderings when creating them and reverse the list here.
    1432             :      *
    1433             :      * @TODO This is brittle and should be carefully redesigned so that the
    1434             :      * order of creation doesn't matter, and the reverse becomes unneeded.
    1435             :      */
    1436           0 :     sched->ordering_constraints = g_list_reverse(sched->ordering_constraints);
    1437             : 
    1438           0 :     for (GList *iter = sched->ordering_constraints;
    1439           0 :          iter != NULL; iter = iter->next) {
    1440             : 
    1441           0 :         pcmk__action_relation_t *order = iter->data;
    1442           0 :         pcmk_resource_t *rsc = order->rsc1;
    1443             : 
    1444           0 :         if (rsc != NULL) {
    1445           0 :             rsc_order_first(rsc, order);
    1446           0 :             continue;
    1447             :         }
    1448             : 
    1449           0 :         rsc = order->rsc2;
    1450           0 :         if (rsc != NULL) {
    1451           0 :             order_resource_actions_after(order->action1, rsc, order);
    1452             : 
    1453             :         } else {
    1454           0 :             crm_trace("Applying ordering constraint %d (non-resource actions)",
    1455             :                       order->id);
    1456           0 :             order_actions(order->action1, order->action2, order->flags);
    1457             :         }
    1458             :     }
    1459             : 
    1460           0 :     g_list_foreach(sched->actions, block_colocation_dependents, NULL);
    1461             : 
    1462           0 :     crm_trace("Ordering probes");
    1463           0 :     pcmk__order_probes(sched);
    1464             : 
    1465           0 :     crm_trace("Updating %d actions", g_list_length(sched->actions));
    1466           0 :     g_list_foreach(sched->actions, update_action_for_orderings, sched);
    1467             : 
    1468           0 :     pcmk__disable_invalid_orderings(sched);
    1469           0 : }
    1470             : 
    1471             : /*!
    1472             :  * \internal
    1473             :  * \brief Order a given action after each action in a given list
    1474             :  *
    1475             :  * \param[in,out] after  "After" action
    1476             :  * \param[in,out] list   List of "before" actions
    1477             :  */
    1478             : void
    1479           0 : pcmk__order_after_each(pcmk_action_t *after, GList *list)
    1480             : {
    1481           0 :     const char *after_desc = (after->task == NULL)? after->uuid : after->task;
    1482             : 
    1483           0 :     for (GList *iter = list; iter != NULL; iter = iter->next) {
    1484           0 :         pcmk_action_t *before = (pcmk_action_t *) iter->data;
    1485           0 :         const char *before_desc = before->task? before->task : before->uuid;
    1486             : 
    1487           0 :         crm_debug("Ordering %s on %s before %s on %s",
    1488             :                   before_desc, pcmk__node_name(before->node),
    1489             :                   after_desc, pcmk__node_name(after->node));
    1490           0 :         order_actions(before, after, pcmk__ar_ordered);
    1491             :     }
    1492           0 : }
    1493             : 
    1494             : /*!
    1495             :  * \internal
    1496             :  * \brief Order promotions and demotions for restarts of a clone or bundle
    1497             :  *
    1498             :  * \param[in,out] rsc  Clone or bundle to order
    1499             :  */
    1500             : void
    1501           0 : pcmk__promotable_restart_ordering(pcmk_resource_t *rsc)
    1502             : {
    1503             :     // Order start and promote after all instances are stopped
    1504           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
    1505             :                                  rsc, PCMK_ACTION_START,
    1506             :                                  pcmk__ar_ordered);
    1507           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
    1508             :                                  rsc, PCMK_ACTION_PROMOTE,
    1509             :                                  pcmk__ar_ordered);
    1510             : 
    1511             :     // Order stop, start, and promote after all instances are demoted
    1512           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
    1513             :                                  rsc, PCMK_ACTION_STOP,
    1514             :                                  pcmk__ar_ordered);
    1515           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
    1516             :                                  rsc, PCMK_ACTION_START,
    1517             :                                  pcmk__ar_ordered);
    1518           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
    1519             :                                  rsc, PCMK_ACTION_PROMOTE,
    1520             :                                  pcmk__ar_ordered);
    1521             : 
    1522             :     // Order promote after all instances are started
    1523           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_RUNNING,
    1524             :                                  rsc, PCMK_ACTION_PROMOTE,
    1525             :                                  pcmk__ar_ordered);
    1526             : 
    1527             :     // Order demote after all instances are demoted
    1528           0 :     pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTE,
    1529             :                                  rsc, PCMK_ACTION_DEMOTED,
    1530             :                                  pcmk__ar_ordered);
    1531           0 : }

Generated by: LCOV version 1.14