root/lib/pacemaker/pcmk_sched_group.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__group_assign
  2. create_group_pseudo_op
  3. pcmk__group_create_actions
  4. member_internal_constraints
  5. pcmk__group_internal_constraints
  6. colocate_group_with
  7. colocate_with_group
  8. pcmk__group_apply_coloc_score
  9. pcmk__group_action_flags
  10. pcmk__group_update_ordered_actions
  11. pcmk__group_apply_location
  12. pcmk__group_colocated_resources
  13. pcmk__with_group_colocations
  14. pcmk__group_with_colocations
  15. pcmk__group_add_colocated_node_scores
  16. pcmk__group_add_utilization
  17. pcmk__group_shutdown_lock

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdbool.h>
  13 
  14 #include <qb/qbdefs.h>              // QB_ABS()
  15 
  16 #include <crm/common/xml.h>
  17 
  18 #include <pacemaker-internal.h>
  19 #include "libpacemaker_private.h"
  20 
  21 /*!
  22  * \internal
  23  * \brief Assign a group resource to a node
  24  *
  25  * \param[in,out] rsc           Group resource to assign to a node
  26  * \param[in]     prefer        Node to prefer, if all else is equal
  27  * \param[in]     stop_if_fail  If \c true and a child of \p rsc can't be
  28  *                              assigned to a node, set the child's next role to
  29  *                              stopped and update existing actions
  30  *
  31  * \return Node that \p rsc is assigned to, if assigned entirely to one node
  32  *
  33  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  34  *       completely undo the assignment. A successful assignment can be either
  35  *       undone or left alone as final. A failed assignment has the same effect
  36  *       as calling pcmk__unassign_resource(); there are no side effects on
  37  *       roles or actions.
  38  */
  39 pcmk_node_t *
  40 pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
     /* [previous][next][first][last][top][bottom][index][help] */
  41                    bool stop_if_fail)
  42 {
  43     pcmk_node_t *first_assigned_node = NULL;
  44     pcmk_resource_t *first_member = NULL;
  45 
  46     pcmk__assert(pcmk__is_group(rsc));
  47 
  48     if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
  49         return rsc->priv->assigned_node; // Assignment already done
  50     }
  51     if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) {
  52         pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s",
  53                         rsc->id);
  54         return NULL;
  55     }
  56 
  57     if (rsc->priv->children == NULL) {
  58         // No members to assign
  59         pcmk__clear_rsc_flags(rsc, pcmk__rsc_unassigned);
  60         return NULL;
  61     }
  62 
  63     pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
  64     first_member = (pcmk_resource_t *) rsc->priv->children->data;
  65     rsc->priv->orig_role = first_member->priv->orig_role;
  66 
  67     pe__show_node_scores(!pcmk_is_set(rsc->priv->scheduler->flags,
  68                                       pcmk__sched_output_scores),
  69                          rsc, __func__, rsc->priv->allowed_nodes,
  70                          rsc->priv->scheduler);
  71 
  72     for (GList *iter = rsc->priv->children;
  73          iter != NULL; iter = iter->next) {
  74 
  75         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
  76         pcmk_node_t *node = NULL;
  77 
  78         pcmk__rsc_trace(rsc, "Assigning group %s member %s",
  79                         rsc->id, member->id);
  80         node = member->priv->cmds->assign(member, prefer, stop_if_fail);
  81         if (first_assigned_node == NULL) {
  82             first_assigned_node = node;
  83         }
  84     }
  85 
  86     pe__set_next_role(rsc, first_member->priv->next_role,
  87                       "first group member");
  88     pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned);
  89 
  90     if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
  91         return NULL;
  92     }
  93     return first_assigned_node;
  94 }
  95 
  96 /*!
  97  * \internal
  98  * \brief Create a pseudo-operation for a group as an ordering point
  99  *
 100  * \param[in,out] group   Group resource to create action for
 101  * \param[in]     action  Action name
 102  *
 103  * \return Newly created pseudo-operation
 104  */
 105 static pcmk_action_t *
 106 create_group_pseudo_op(pcmk_resource_t *group, const char *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 107 {
 108     pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
 109                                       action, NULL, TRUE,
 110                                       group->priv->scheduler);
 111 
 112     pcmk__set_action_flags(op, pcmk__action_pseudo|pcmk__action_runnable);
 113     return op;
 114 }
 115 
 116 /*!
 117  * \internal
 118  * \brief Create all actions needed for a given group resource
 119  *
 120  * \param[in,out] rsc  Group resource to create actions for
 121  */
 122 void
 123 pcmk__group_create_actions(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 124 {
 125     pcmk__assert(pcmk__is_group(rsc));
 126 
 127     pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id);
 128 
 129     // Create actions for individual group members
 130     for (GList *iter = rsc->priv->children;
 131          iter != NULL; iter = iter->next) {
 132 
 133         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 134 
 135         member->priv->cmds->create_actions(member);
 136     }
 137 
 138     // Create pseudo-actions for group itself to serve as ordering points
 139     create_group_pseudo_op(rsc, PCMK_ACTION_START);
 140     create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING);
 141     create_group_pseudo_op(rsc, PCMK_ACTION_STOP);
 142     create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED);
 143     if (crm_is_true(g_hash_table_lookup(rsc->priv->meta,
 144                                         PCMK_META_PROMOTABLE))) {
 145         create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE);
 146         create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED);
 147         create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE);
 148         create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED);
 149     }
 150 }
 151 
 152 // User data for member_internal_constraints()
 153 struct member_data {
 154     // These could be derived from member but this avoids some function calls
 155     bool ordered;
 156     bool colocated;
 157     bool promotable;
 158 
 159     pcmk_resource_t *last_active;
 160     pcmk_resource_t *previous_member;
 161 };
 162 
 163 /*!
 164  * \internal
 165  * \brief Create implicit constraints needed for a group member
 166  *
 167  * \param[in,out] data       Group member to create implicit constraints for
 168  * \param[in,out] user_data  Member data (struct member_data *)
 169  */
 170 static void
 171 member_internal_constraints(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 172 {
 173     pcmk_resource_t *member = (pcmk_resource_t *) data;
 174     struct member_data *member_data = (struct member_data *) user_data;
 175 
 176     // For ordering demote vs demote or stop vs stop
 177     uint32_t down_flags = pcmk__ar_then_implies_first_graphed;
 178 
 179     // For ordering demote vs demoted or stop vs stopped
 180     uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed;
 181 
 182     // Create the individual member's implicit constraints
 183     member->priv->cmds->internal_constraints(member);
 184 
 185     if (member_data->previous_member == NULL) {
 186         // This is first member
 187         if (member_data->ordered) {
 188             pcmk__set_relation_flags(down_flags, pcmk__ar_ordered);
 189             post_down_flags = pcmk__ar_first_implies_then;
 190         }
 191 
 192     } else if (member_data->colocated) {
 193         uint32_t flags = pcmk__coloc_none;
 194 
 195         if (pcmk_is_set(member->flags, pcmk__rsc_critical)) {
 196             flags |= pcmk__coloc_influence;
 197         }
 198 
 199         // Colocate this member with the previous one
 200         pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY,
 201                              member, member_data->previous_member, NULL, NULL,
 202                              flags);
 203     }
 204 
 205     if (member_data->promotable) {
 206         // Demote group -> demote member -> group is demoted
 207         pcmk__order_resource_actions(member->priv->parent,
 208                                      PCMK_ACTION_DEMOTE,
 209                                      member, PCMK_ACTION_DEMOTE, down_flags);
 210         pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
 211                                      member->priv->parent,
 212                                      PCMK_ACTION_DEMOTED, post_down_flags);
 213 
 214         // Promote group -> promote member -> group is promoted
 215         pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE,
 216                                      member->priv->parent,
 217                                      PCMK_ACTION_PROMOTED,
 218                                      pcmk__ar_unrunnable_first_blocks
 219                                      |pcmk__ar_first_implies_then
 220                                      |pcmk__ar_first_implies_then_graphed);
 221         pcmk__order_resource_actions(member->priv->parent,
 222                                      PCMK_ACTION_PROMOTE,
 223                                      member, PCMK_ACTION_PROMOTE,
 224                                      pcmk__ar_then_implies_first_graphed);
 225     }
 226 
 227     // Stop group -> stop member -> group is stopped
 228     pcmk__order_stops(member->priv->parent, member, down_flags);
 229     pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
 230                                  member->priv->parent, PCMK_ACTION_STOPPED,
 231                                  post_down_flags);
 232 
 233     // Start group -> start member -> group is started
 234     pcmk__order_starts(member->priv->parent, member,
 235                        pcmk__ar_then_implies_first_graphed);
 236     pcmk__order_resource_actions(member, PCMK_ACTION_START,
 237                                  member->priv->parent, PCMK_ACTION_RUNNING,
 238                                  pcmk__ar_unrunnable_first_blocks
 239                                  |pcmk__ar_first_implies_then
 240                                  |pcmk__ar_first_implies_then_graphed);
 241 
 242     if (!member_data->ordered) {
 243         pcmk__order_starts(member->priv->parent, member,
 244                            pcmk__ar_first_implies_then
 245                            |pcmk__ar_unrunnable_first_blocks
 246                            |pcmk__ar_then_implies_first_graphed);
 247         if (member_data->promotable) {
 248             pcmk__order_resource_actions(member->priv->parent,
 249                                          PCMK_ACTION_PROMOTE,
 250                                          member, PCMK_ACTION_PROMOTE,
 251                                          pcmk__ar_first_implies_then
 252                                          |pcmk__ar_unrunnable_first_blocks
 253                                          |pcmk__ar_then_implies_first_graphed);
 254         }
 255 
 256     } else if (member_data->previous_member == NULL) {
 257         pcmk__order_starts(member->priv->parent, member, pcmk__ar_none);
 258         if (member_data->promotable) {
 259             pcmk__order_resource_actions(member->priv->parent,
 260                                          PCMK_ACTION_PROMOTE,
 261                                          member, PCMK_ACTION_PROMOTE,
 262                                          pcmk__ar_none);
 263         }
 264 
 265     } else {
 266         // Order this member relative to the previous one
 267 
 268         pcmk__order_starts(member_data->previous_member, member,
 269                            pcmk__ar_first_implies_then
 270                            |pcmk__ar_unrunnable_first_blocks);
 271         pcmk__order_stops(member, member_data->previous_member,
 272                           pcmk__ar_ordered|pcmk__ar_intermediate_stop);
 273 
 274         /* In unusual circumstances (such as adding a new member to the middle
 275          * of a group with unmanaged later members), this member may be active
 276          * while the previous (new) member is inactive. In this situation, the
 277          * usual restart orderings will be irrelevant, so we need to order this
 278          * member's stop before the previous member's start.
 279          */
 280         if ((member->priv->active_nodes != NULL)
 281             && (member_data->previous_member->priv->active_nodes == NULL)) {
 282             pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
 283                                          member_data->previous_member,
 284                                          PCMK_ACTION_START,
 285                                          pcmk__ar_then_implies_first
 286                                          |pcmk__ar_unrunnable_first_blocks);
 287         }
 288 
 289         if (member_data->promotable) {
 290             pcmk__order_resource_actions(member_data->previous_member,
 291                                          PCMK_ACTION_PROMOTE, member,
 292                                          PCMK_ACTION_PROMOTE,
 293                                          pcmk__ar_first_implies_then
 294                                          |pcmk__ar_unrunnable_first_blocks);
 295             pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
 296                                          member_data->previous_member,
 297                                          PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
 298         }
 299     }
 300 
 301     // Make sure partially active groups shut down in sequence
 302     if (member->priv->active_nodes != NULL) {
 303         if (member_data->ordered && (member_data->previous_member != NULL)
 304             && (member_data->previous_member->priv->active_nodes == NULL)
 305             && (member_data->last_active != NULL)
 306             && (member_data->last_active->priv->active_nodes != NULL)) {
 307             pcmk__order_stops(member, member_data->last_active,
 308                               pcmk__ar_ordered);
 309         }
 310         member_data->last_active = member;
 311     }
 312 
 313     member_data->previous_member = member;
 314 }
 315 
 316 /*!
 317  * \internal
 318  * \brief Create implicit constraints needed for a group resource
 319  *
 320  * \param[in,out] rsc  Group resource to create implicit constraints for
 321  */
 322 void
 323 pcmk__group_internal_constraints(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 324 {
 325     struct member_data member_data = { false, };
 326     const pcmk_resource_t *top = NULL;
 327 
 328     pcmk__assert(pcmk__is_group(rsc));
 329 
 330     /* Order group pseudo-actions relative to each other for restarting:
 331      * stop group -> group is stopped -> start group -> group is started
 332      */
 333     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
 334                                  rsc, PCMK_ACTION_STOPPED,
 335                                  pcmk__ar_unrunnable_first_blocks);
 336     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
 337                                  rsc, PCMK_ACTION_START,
 338                                  pcmk__ar_ordered);
 339     pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
 340                                  rsc, PCMK_ACTION_RUNNING,
 341                                  pcmk__ar_unrunnable_first_blocks);
 342 
 343     top = pe__const_top_resource(rsc, false);
 344 
 345     member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered);
 346     member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated);
 347     member_data.promotable = pcmk_is_set(top->flags, pcmk__rsc_promotable);
 348     g_list_foreach(rsc->priv->children, member_internal_constraints,
 349                    &member_data);
 350 }
 351 
 352 /*!
 353  * \internal
 354  * \brief Apply a colocation's score to node scores or resource priority
 355  *
 356  * Given a colocation constraint for a group with some other resource, apply the
 357  * score to the dependent's allowed node scores (if we are still placing
 358  * resources) or priority (if we are choosing promotable clone instance roles).
 359  *
 360  * \param[in,out] dependent      Dependent group resource in colocation
 361  * \param[in]     primary        Primary resource in colocation
 362  * \param[in]     colocation     Colocation constraint to apply
 363  *
 364  * \return The score added to the dependent's priority
 365  */
 366 static int
 367 colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
     /* [previous][next][first][last][top][bottom][index][help] */
 368                     const pcmk__colocation_t *colocation)
 369 {
 370     int priority_delta = 0;
 371 
 372     if (dependent->priv->children == NULL) {
 373         return 0;
 374     }
 375 
 376     pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
 377                     colocation->id, dependent->id, primary->id);
 378 
 379     if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) {
 380         // Colocate first member (internal colocations will handle the rest)
 381         pcmk_resource_t *member = dependent->priv->children->data;
 382         priority_delta = member->priv->cmds->apply_coloc_score(member, primary,
 383                                                                colocation,
 384                                                                true);
 385 
 386     } else {
 387         if (colocation->score >= PCMK_SCORE_INFINITY) {
 388             pcmk__config_err("%s: Cannot perform mandatory colocation between "
 389                              "non-colocated group and %s",
 390                              dependent->id, primary->id);
 391             return 0;
 392         }
 393 
 394         // Colocate each member individually
 395         for (GList *iter = dependent->priv->children; iter != NULL;
 396              iter = iter->next) {
 397 
 398             int instance_delta = 0;
 399             pcmk_resource_t *member = iter->data;
 400 
 401             instance_delta =
 402                 member->priv->cmds->apply_coloc_score(member, primary,
 403                                                       colocation, false);
 404 
 405             /* priority_delta is used for determining which instances of a
 406              * promotable clone to promote. It's possible that colocations
 407              * involving promotable cloned non-colocated groups may not behave
 408              * correctly in all circumstances. Non-colocated groups are
 409              * deprecated, and testing focused on colocated groups.
 410              */
 411             priority_delta = pcmk__add_scores(priority_delta, instance_delta);
 412         }
 413     }
 414 
 415     if (priority_delta != 0) {
 416         dependent->priv->priority =
 417             pcmk__add_scores(priority_delta, dependent->priv->priority);
 418 
 419         pcmk__rsc_trace(dependent,
 420                         "Applied %s to %s promotion priority "
 421                         "(now %s after %s %d)",
 422                         colocation->id, dependent->id,
 423                         pcmk_readable_score(dependent->priv->priority),
 424                         ((priority_delta > 0)? "adding" : "subtracting"),
 425                         QB_ABS(priority_delta));
 426     }
 427     return priority_delta;
 428 }
 429 
 430 /*!
 431  * \internal
 432  * \brief Apply a colocation's score to node scores or resource priority
 433  *
 434  * Given a colocation constraint for some other resource with a group, apply the
 435  * score to the dependent's allowed node scores (if we are still placing
 436  * resources) or priority (if we are choosing promotable clone instance roles).
 437  *
 438  * \param[in,out] dependent      Dependent resource in colocation
 439  * \param[in]     primary        Primary group resource in colocation
 440  * \param[in]     colocation     Colocation constraint to apply
 441  *
 442  * \return The score added to the dependent's priority
 443  */
 444 static int
 445 colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
     /* [previous][next][first][last][top][bottom][index][help] */
 446                     const pcmk__colocation_t *colocation)
 447 {
 448     int priority_delta = 0;
 449     const pcmk_resource_t *member = NULL;
 450 
 451     pcmk__rsc_trace(primary,
 452                     "Processing colocation %s (%s with group %s) for primary",
 453                     colocation->id, dependent->id, primary->id);
 454 
 455     if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
 456         return 0;
 457     }
 458 
 459     if (pe__group_flag_is_set(primary, pcmk__group_colocated)) {
 460 
 461         if (colocation->score >= PCMK_SCORE_INFINITY) {
 462             /* For mandatory colocations, the entire group must be assignable
 463              * (and in the specified role if any), so apply the colocation based
 464              * on the last member.
 465              */
 466             member = pe__last_group_member(primary);
 467         } else if (primary->priv->children != NULL) {
 468             /* For optional colocations, whether the group is partially or fully
 469              * up doesn't matter, so apply the colocation based on the first
 470              * member.
 471              */
 472             member = (pcmk_resource_t *) primary->priv->children->data;
 473         }
 474         if (member == NULL) {
 475             return 0;   // Nothing to colocate with
 476         }
 477 
 478         return member->priv->cmds->apply_coloc_score(dependent, member,
 479                                                      colocation, false);
 480     }
 481 
 482     if (colocation->score >= PCMK_SCORE_INFINITY) {
 483         pcmk__config_err("%s: Cannot perform mandatory colocation with"
 484                          " non-colocated group %s",
 485                          dependent->id, primary->id);
 486         return 0;
 487     }
 488 
 489     // Colocate dependent with each member individually
 490     for (const GList *iter = primary->priv->children;
 491          iter != NULL; iter = iter->next) {
 492 
 493         int instance_delta = 0;
 494 
 495         member = iter->data;
 496         instance_delta = member->priv->cmds->apply_coloc_score(dependent,
 497                                                                member,
 498                                                                colocation,
 499                                                                false);
 500         priority_delta = pcmk__add_scores(priority_delta, instance_delta);
 501     }
 502     return priority_delta;
 503 }
 504 
 505 /*!
 506  * \internal
 507  * \brief Apply a colocation's score to node scores or resource priority
 508  *
 509  * Given a colocation constraint, apply its score to the dependent's
 510  * allowed node scores (if we are still placing resources) or priority (if
 511  * we are choosing promotable clone instance roles).
 512  *
 513  * \param[in,out] dependent      Dependent resource in colocation
 514  * \param[in]     primary        Primary resource in colocation
 515  * \param[in]     colocation     Colocation constraint to apply
 516  * \param[in]     for_dependent  true if called on behalf of dependent
 517  *
 518  * \return The score added to the dependent's priority
 519  */
 520 int
 521 pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
 522                               const pcmk_resource_t *primary,
 523                               const pcmk__colocation_t *colocation,
 524                               bool for_dependent)
 525 {
 526     pcmk__assert((dependent != NULL) && (primary != NULL)
 527                  && (colocation != NULL));
 528 
 529     if (for_dependent) {
 530         return colocate_group_with(dependent, primary, colocation);
 531 
 532     } else {
 533         // Method should only be called for primitive dependents
 534         pcmk__assert(pcmk__is_primitive(dependent));
 535 
 536         return colocate_with_group(dependent, primary, colocation);
 537     }
 538 }
 539 
 540 /*!
 541  * \internal
 542  * \brief Return action flags for a given group resource action
 543  *
 544  * \param[in,out] action  Group action to get flags for
 545  * \param[in]     node    If not NULL, limit effects to this node
 546  *
 547  * \return Flags appropriate to \p action on \p node
 548  */
 549 uint32_t
 550 pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 551 {
 552     // Default flags for a group action
 553     uint32_t flags = pcmk__action_optional
 554                      |pcmk__action_runnable
 555                      |pcmk__action_pseudo;
 556 
 557     pcmk__assert(action != NULL);
 558 
 559     // Update flags considering each member's own flags for same action
 560     for (GList *iter = action->rsc->priv->children;
 561          iter != NULL; iter = iter->next) {
 562 
 563         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 564 
 565         // Check whether member has the same action
 566         enum pcmk__action_type task = get_complex_task(member, action->task);
 567         const char *task_s = pcmk__action_text(task);
 568         pcmk_action_t *member_action = NULL;
 569 
 570         member_action = find_first_action(member->priv->actions, NULL,
 571                                           task_s, node);
 572         if (member_action != NULL) {
 573             uint32_t member_flags = 0U;
 574 
 575             member_flags = member->priv->cmds->action_flags(member_action,
 576                                                             node);
 577 
 578             // Group action is mandatory if any member action is
 579             if (pcmk_is_set(flags, pcmk__action_optional)
 580                 && !pcmk_is_set(member_flags, pcmk__action_optional)) {
 581                 pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is",
 582                                 action->uuid, member_action->uuid);
 583                 pcmk__clear_raw_action_flags(flags, "group action",
 584                                              pcmk__action_optional);
 585                 pcmk__clear_action_flags(action, pcmk__action_optional);
 586             }
 587 
 588             // Group action is unrunnable if any member action is
 589             if (!pcmk__str_eq(task_s, action->task, pcmk__str_none)
 590                 && pcmk_is_set(flags, pcmk__action_runnable)
 591                 && !pcmk_is_set(member_flags, pcmk__action_runnable)) {
 592 
 593                 pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is",
 594                                 action->uuid, member_action->uuid);
 595                 pcmk__clear_raw_action_flags(flags, "group action",
 596                                              pcmk__action_runnable);
 597                 pcmk__clear_action_flags(action, pcmk__action_runnable);
 598             }
 599 
 600         /* Group (pseudo-)actions other than stop or demote are unrunnable
 601          * unless every member will do it.
 602          */
 603         } else if ((task != pcmk__action_stop)
 604                     && (task != pcmk__action_demote)) {
 605             pcmk__rsc_trace(action->rsc,
 606                             "%s is not runnable because %s will not %s",
 607                             action->uuid, member->id, task_s);
 608             pcmk__clear_raw_action_flags(flags, "group action",
 609                                          pcmk__action_runnable);
 610         }
 611     }
 612 
 613     return flags;
 614 }
 615 
 616 /*!
 617  * \internal
 618  * \brief Update two actions according to an ordering between them
 619  *
 620  * Given information about an ordering of two actions, update the actions' flags
 621  * (and runnable_before members if appropriate) as appropriate for the ordering.
 622  * Effects may cascade to other orderings involving the actions as well.
 623  *
 624  * \param[in,out] first      'First' action in an ordering
 625  * \param[in,out] then       'Then' action in an ordering
 626  * \param[in]     node       If not NULL, limit scope of ordering to this node
 627  *                           (only used when interleaving instances)
 628  * \param[in]     flags      Action flags for \p first for ordering purposes
 629  * \param[in]     filter     Action flags to limit scope of certain updates (may
 630  *                           include pcmk__action_optional to affect only
 631  *                           mandatory actions, and pcmk__action_runnable to
 632  *                           affect only runnable actions)
 633  * \param[in]     type       Group of enum pcmk__action_relation_flags to apply
 634  * \param[in,out] scheduler  Scheduler data
 635  *
 636  * \return Group of enum pcmk__updated flags indicating what was updated
 637  */
 638 uint32_t
 639 pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
     /* [previous][next][first][last][top][bottom][index][help] */
 640                                    const pcmk_node_t *node, uint32_t flags,
 641                                    uint32_t filter, uint32_t type,
 642                                    pcmk_scheduler_t *scheduler)
 643 {
 644     uint32_t changed = pcmk__updated_none;
 645 
 646     // Group method can be called only on behalf of "then" action
 647     pcmk__assert((first != NULL) && (then != NULL) && (then->rsc != NULL)
 648                  && (scheduler != NULL));
 649 
 650     // Update the actions for the group itself
 651     changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
 652                                             type, scheduler);
 653 
 654     // Update the actions for each group member
 655     for (GList *iter = then->rsc->priv->children;
 656          iter != NULL; iter = iter->next) {
 657 
 658         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 659         pcmk_action_t *member_action = NULL;
 660 
 661         member_action = find_first_action(member->priv->actions, NULL,
 662                                           then->task, node);
 663         if (member_action == NULL) {
 664             continue;
 665         }
 666         changed |= member->priv->cmds->update_ordered_actions(first,
 667                                                               member_action,
 668                                                               node, flags,
 669                                                               filter, type,
 670                                                               scheduler);
 671     }
 672     return changed;
 673 }
 674 
 675 /*!
 676  * \internal
 677  * \brief Apply a location constraint to a group's allowed node scores
 678  *
 679  * \param[in,out] rsc       Group resource to apply constraint to
 680  * \param[in,out] location  Location constraint to apply
 681  */
 682 void
 683 pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
     /* [previous][next][first][last][top][bottom][index][help] */
 684 {
 685     GList *node_list_orig = NULL;
 686     GList *node_list_copy = NULL;
 687 
 688     pcmk__assert(pcmk__is_group(rsc) && (location != NULL));
 689 
 690     // Save the constraint's original node list (with the constraint score)
 691     node_list_orig = location->nodes;
 692 
 693     // Make a copy of the nodes with all zero scores
 694     node_list_copy  = pcmk__copy_node_list(node_list_orig, true);
 695 
 696     /* Apply the constraint to the group itself. This ensures that any nodes
 697      * affected by the constraint are in the group's allowed nodes, with the
 698      * constraint score added.
 699      */
 700     pcmk__apply_location(rsc, location);
 701 
 702     // Apply the constraint for each member
 703     for (GList *iter = rsc->priv->children;
 704          iter != NULL; iter = iter->next) {
 705 
 706         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 707 
 708         if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
 709             && (iter != rsc->priv->children)) {
 710             /* When apply_location() is called below for the first member (iter
 711              * == rsc->priv->children), the constraint score will be added to
 712              * the member's affected allowed nodes.
 713              *
 714              * For subsequent members, we reset the constraint's node table to
 715              * the copy with all 0 scores. Otherwise, when assigning the member,
 716              * the constraint score would be counted multiple times (once for
 717              * each later member) due to internal group colocations. Though the
 718              * 0 score will not affect these members' allowed node scores, it
 719              * ensures that affected nodes are in each member's allowed nodes,
 720              * enabling the member on those nodes in asymmetric clusters.
 721              */
 722             location->nodes = node_list_copy;
 723         }
 724 
 725         member->priv->cmds->apply_location(member, location);
 726     }
 727 
 728     location->nodes = node_list_orig;
 729     g_list_free_full(node_list_copy, free);
 730 }
 731 
 732 // Group implementation of pcmk__assignment_methods_t:colocated_resources()
 733 GList *
 734 pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 735                                 const pcmk_resource_t *orig_rsc,
 736                                 GList *colocated_rscs)
 737 {
 738     pcmk__assert(pcmk__is_group(rsc));
 739 
 740     if (orig_rsc == NULL) {
 741         orig_rsc = rsc;
 742     }
 743 
 744     if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
 745         || pcmk__is_clone(rsc->priv->parent)) {
 746         /* This group has colocated members and/or is cloned -- either way,
 747          * add every child's colocated resources to the list. The first and last
 748          * members will include the group's own colocations.
 749          */
 750         colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
 751 
 752         for (const GList *iter = rsc->priv->children;
 753              iter != NULL; iter = iter->next) {
 754 
 755             const pcmk_resource_t *member = iter->data;
 756 
 757             colocated_rscs = member->priv->cmds->colocated_resources(member,
 758                                                                      orig_rsc,
 759                                                                      colocated_rscs);
 760         }
 761 
 762     } else if (rsc->priv->children != NULL) {
 763         /* This group's members are not colocated, and the group is not cloned,
 764          * so just add the group's own colocations to the list.
 765          */
 766         colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc,
 767                                                    colocated_rscs);
 768     }
 769 
 770     return colocated_rscs;
 771 }
 772 
 773 // Group implementation of pcmk__assignment_methods_t:with_this_colocations()
 774 void
 775 pcmk__with_group_colocations(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 776                              const pcmk_resource_t *orig_rsc, GList **list)
 777 
 778 {
 779     const pcmk_resource_t *parent = NULL;
 780 
 781     pcmk__assert((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
 782     parent = rsc->priv->parent;
 783 
 784     // Ignore empty groups
 785     if (rsc->priv->children == NULL) {
 786         return;
 787     }
 788 
 789     /* "With this" colocations are needed only for the group itself and for its
 790      * last member. (Previous members will chain via the group internal
 791      * colocations.)
 792      */
 793     if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) {
 794         return;
 795     }
 796 
 797     pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s",
 798                     rsc->id, orig_rsc->id);
 799 
 800     // Add the group's own colocations
 801     pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
 802                              orig_rsc);
 803 
 804     // If cloned, add any relevant colocations with the clone
 805     if (parent != NULL) {
 806         parent->priv->cmds->with_this_colocations(parent, orig_rsc, list);
 807     }
 808 
 809     if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
 810         // @COMPAT Non-colocated groups are deprecated
 811         return;
 812     }
 813 
 814     // Add explicit colocations with the group's (other) children
 815     for (const GList *iter = rsc->priv->children;
 816          iter != NULL; iter = iter->next) {
 817 
 818         const pcmk_resource_t *member = iter->data;
 819 
 820         if (member == orig_rsc) {
 821             continue;
 822         }
 823         member->priv->cmds->with_this_colocations(member, orig_rsc, list);
 824     }
 825 }
 826 
 827 // Group implementation of pcmk__assignment_methods_t:this_with_colocations()
 828 void
 829 pcmk__group_with_colocations(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 830                              const pcmk_resource_t *orig_rsc, GList **list)
 831 {
 832     const pcmk_resource_t *parent = NULL;
 833     const pcmk_resource_t *member = NULL;
 834 
 835     pcmk__assert((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
 836     parent = rsc->priv->parent;
 837 
 838     // Ignore empty groups
 839     if (rsc->priv->children == NULL) {
 840         return;
 841     }
 842 
 843     /* "This with" colocations are normally needed only for the group itself and
 844      * for its first member.
 845      */
 846     if ((rsc == orig_rsc) || (orig_rsc == rsc->priv->children->data)) {
 847         pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s",
 848                         rsc->id, orig_rsc->id);
 849 
 850         // Add the group's own colocations
 851         pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
 852                                  orig_rsc);
 853 
 854         // If cloned, add any relevant colocations involving the clone
 855         if (parent != NULL) {
 856             parent->priv->cmds->this_with_colocations(parent, orig_rsc, list);
 857         }
 858 
 859         if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
 860             // @COMPAT Non-colocated groups are deprecated
 861             return;
 862         }
 863 
 864         // Add explicit colocations involving the group's (other) children
 865         for (const GList *iter = rsc->priv->children;
 866              iter != NULL; iter = iter->next) {
 867 
 868             member = iter->data;
 869             if (member == orig_rsc) {
 870                 continue;
 871             }
 872             member->priv->cmds->this_with_colocations(member, orig_rsc, list);
 873         }
 874         return;
 875     }
 876 
 877     /* Later group members honor the group's colocations indirectly, due to the
 878      * internal group colocations that chain everything from the first member.
 879      * However, if an earlier group member is unmanaged, this chaining will not
 880      * happen, so the group's mandatory colocations must be explicitly added.
 881      */
 882     for (const GList *iter = rsc->priv->children;
 883          iter != NULL; iter = iter->next) {
 884 
 885         member = iter->data;
 886         if (orig_rsc == member) {
 887             break; // We've seen all earlier members, and none are unmanaged
 888         }
 889 
 890         if (!pcmk_is_set(member->flags, pcmk__rsc_managed)) {
 891             crm_trace("Adding mandatory '%s with' colocations to list for "
 892                       "member %s because earlier member %s is unmanaged",
 893                       rsc->id, orig_rsc->id, member->id);
 894             for (const GList *cons_iter = rsc->priv->this_with_colocations;
 895                  cons_iter != NULL; cons_iter = cons_iter->next) {
 896                 const pcmk__colocation_t *colocation = NULL;
 897 
 898                 colocation = (const pcmk__colocation_t *) cons_iter->data;
 899                 if (colocation->score == PCMK_SCORE_INFINITY) {
 900                     pcmk__add_this_with(list, colocation, orig_rsc);
 901                 }
 902             }
 903             // @TODO Add mandatory (or all?) clone constraints if cloned
 904             break;
 905         }
 906     }
 907 }
 908 
 909 /*!
 910  * \internal
 911  * \brief Update nodes with scores of colocated resources' nodes
 912  *
 913  * Given a table of nodes and a resource, update the nodes' scores with the
 914  * scores of the best nodes matching the attribute used for each of the
 915  * resource's relevant colocations.
 916  *
 917  * \param[in,out] source_rsc  Group resource whose node scores to add
 918  * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
 919  * \param[in]     log_id      Resource ID for logs (if \c NULL, use
 920  *                            \p source_rsc ID)
 921  * \param[in,out] nodes       Nodes to update (set initial contents to \c NULL
 922  *                            to copy allowed nodes from \p source_rsc)
 923  * \param[in]     colocation  Original colocation constraint (used to get
 924  *                            configured primary resource's stickiness, and
 925  *                            to get colocation node attribute; if \c NULL,
 926  *                            <tt>source_rsc</tt>'s own matching node scores will
 927  *                            not be added, and \p *nodes must be \c NULL as
 928  *                            well)
 929  * \param[in]     factor      Incorporate scores multiplied by this factor
 930  * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
 931  *
 932  * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
 933  *       the \c pcmk__coloc_select_this_with flag are used together (and only by
 934  *       \c cmp_resources()).
 935  * \note The caller remains responsible for freeing \p *nodes.
 936  * \note This is the group implementation of
 937  *       \c pcmk__assignment_methods_t:add_colocated_node_scores().
 938  */
 939 void
 940 pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 941                                       const pcmk_resource_t *target_rsc,
 942                                       const char *log_id, GHashTable **nodes,
 943                                       const pcmk__colocation_t *colocation,
 944                                       float factor, uint32_t flags)
 945 {
 946     pcmk_resource_t *member = NULL;
 947 
 948     pcmk__assert(pcmk__is_group(source_rsc) && (nodes != NULL)
 949                  && ((colocation != NULL)
 950                      || ((target_rsc == NULL) && (*nodes == NULL))));
 951 
 952     if (log_id == NULL) {
 953         log_id = source_rsc->id;
 954     }
 955 
 956     // Avoid infinite recursion
 957     if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) {
 958         pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
 959                        log_id, source_rsc->id);
 960         return;
 961     }
 962     pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
 963 
 964     // Ignore empty groups (only possible with schema validation disabled)
 965     if (source_rsc->priv->children == NULL) {
 966         return;
 967     }
 968 
 969     /* Refer the operation to the first or last member as appropriate.
 970      *
 971      * cmp_resources() is the only caller that passes a NULL nodes table,
 972      * and is also the only caller using pcmk__coloc_select_this_with.
 973      * For "this with" colocations, the last member will recursively incorporate
 974      * all the other members' "this with" colocations via the internal group
 975      * colocations (and via the first member, the group's own colocations).
 976      *
 977      * For "with this" colocations, the first member works similarly.
 978      */
 979     if (*nodes == NULL) {
 980         member = pe__last_group_member(source_rsc);
 981     } else {
 982         member = source_rsc->priv->children->data;
 983     }
 984 
 985     pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s "
 986                     "(at %.6f)", log_id, source_rsc->id, member->id, factor);
 987     member->priv->cmds->add_colocated_node_scores(member, target_rsc, log_id,
 988                                                   nodes, colocation, factor,
 989                                                   flags);
 990     pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
 991 }
 992 
 993 // Group implementation of pcmk__assignment_methods_t:add_utilization()
 994 void
 995 pcmk__group_add_utilization(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 996                             const pcmk_resource_t *orig_rsc, GList *all_rscs,
 997                             GHashTable *utilization)
 998 {
 999     pcmk_resource_t *member = NULL;
1000 
1001     pcmk__assert((orig_rsc != NULL) && (utilization != NULL)
1002                  && pcmk__is_group(rsc));
1003 
1004     if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
1005         return;
1006     }
1007 
1008     pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
1009                     orig_rsc->id, rsc->id);
1010     if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
1011         || pcmk__is_clone(rsc->priv->parent)) {
1012 
1013         // Every group member will be on same node, so sum all members
1014         for (GList *iter = rsc->priv->children;
1015              iter != NULL; iter = iter->next) {
1016 
1017             member = (pcmk_resource_t *) iter->data;
1018 
1019             if (pcmk_is_set(member->flags, pcmk__rsc_unassigned)
1020                 && (g_list_find(all_rscs, member) == NULL)) {
1021                 member->priv->cmds->add_utilization(member, orig_rsc, all_rscs,
1022                                                     utilization);
1023             }
1024         }
1025 
1026     } else if (rsc->priv->children != NULL) {
1027         // Just add first member's utilization
1028         member = (pcmk_resource_t *) rsc->priv->children->data;
1029         if ((member != NULL)
1030             && pcmk_is_set(member->flags, pcmk__rsc_unassigned)
1031             && (g_list_find(all_rscs, member) == NULL)) {
1032 
1033             member->priv->cmds->add_utilization(member, orig_rsc, all_rscs,
1034                                                 utilization);
1035         }
1036     }
1037 }
1038 
1039 void
1040 pcmk__group_shutdown_lock(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1041 {
1042     pcmk__assert(pcmk__is_group(rsc));
1043 
1044     for (GList *iter = rsc->priv->children;
1045          iter != NULL; iter = iter->next) {
1046 
1047         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
1048 
1049         member->priv->cmds->shutdown_lock(member);
1050     }
1051 }

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