root/lib/pacemaker/pcmk_sched_clone.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__clone_assign
  2. find_rsc_action
  3. order_instance_starts_stops
  4. clone_create_actions
  5. clone_internal_constraints
  6. pcmk__clone_apply_coloc_score
  7. pcmk__with_clone_colocations
  8. pcmk__clone_with_colocations
  9. clone_action_flags
  10. clone_rsc_location
  11. clone_expand
  12. rsc_known_on
  13. find_instance_on
  14. probe_anonymous_clone
  15. clone_create_probe
  16. clone_append_meta
  17. pcmk__clone_add_utilization
  18. pcmk__clone_shutdown_lock

   1 /*
   2  * Copyright 2004-2023 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 <crm/msg_xml.h>
  13 #include <pacemaker-internal.h>
  14 
  15 #include "libpacemaker_private.h"
  16 
  17 /*!
  18  * \internal
  19  * \brief Assign a clone resource's instances to nodes
  20  *
  21  * \param[in,out] rsc     Clone resource to assign
  22  * \param[in]     prefer  Node to prefer, if all else is equal
  23  *
  24  * \return NULL (clones are not assigned to a single node)
  25  */
  26 pe_node_t *
  27 pcmk__clone_assign(pe_resource_t *rsc, const pe_node_t *prefer)
     /* [previous][next][first][last][top][bottom][index][help] */
  28 {
  29     CRM_ASSERT(pe_rsc_is_clone(rsc));
  30 
  31     if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
  32         return NULL; // Assignment has already been done
  33     }
  34 
  35     // Detect assignment loops
  36     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
  37         pe_rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
  38         return NULL;
  39     }
  40     pe__set_resource_flags(rsc, pe_rsc_allocating);
  41 
  42     // If this clone is promotable, consider nodes' promotion scores
  43     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
  44         pcmk__add_promotion_scores(rsc);
  45     }
  46 
  47     /* If this clone is colocated with any other resources, assign those first.
  48      * Since the this_with_colocations() method boils down to a copy of rsc_cons
  49      * for clones, we can use that here directly for efficiency.
  50      */
  51     for (GList *iter = rsc->rsc_cons; iter != NULL; iter = iter->next) {
  52         pcmk__colocation_t *constraint = (pcmk__colocation_t *) iter->data;
  53 
  54         pe_rsc_trace(rsc, "%s: Assigning colocation %s primary %s first",
  55                      rsc->id, constraint->id, constraint->primary->id);
  56         constraint->primary->cmds->assign(constraint->primary, prefer);
  57     }
  58 
  59     /* If any resources are colocated with this one, consider their preferences.
  60      * Because the with_this_colocations() method boils down to a copy of
  61      * rsc_cons_lhs for clones, we can use that here directly for efficiency.
  62      */
  63     g_list_foreach(rsc->rsc_cons_lhs, pcmk__add_dependent_scores, rsc);
  64 
  65     pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_scores),
  66                           rsc, __func__, rsc->allowed_nodes, rsc->cluster);
  67 
  68     rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance);
  69     pcmk__assign_instances(rsc, rsc->children, pe__clone_max(rsc),
  70                            pe__clone_node_max(rsc));
  71 
  72     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
  73         pcmk__set_instance_roles(rsc);
  74     }
  75 
  76     pe__clear_resource_flags(rsc, pe_rsc_provisional|pe_rsc_allocating);
  77     pe_rsc_trace(rsc, "Assigned clone %s", rsc->id);
  78     return NULL;
  79 }
  80 
  81 static pe_action_t *
  82 find_rsc_action(pe_resource_t *rsc, const char *task)
     /* [previous][next][first][last][top][bottom][index][help] */
  83 {
  84     pe_action_t *match = NULL;
  85     GList *actions = pe__resource_actions(rsc, NULL, task, FALSE);
  86 
  87     for (GList *item = actions; item != NULL; item = item->next) {
  88         pe_action_t *op = (pe_action_t *) item->data;
  89 
  90         if (!pcmk_is_set(op->flags, pe_action_optional)) {
  91             if (match != NULL) {
  92                 // More than one match, don't return any
  93                 match = NULL;
  94                 break;
  95             }
  96             match = op;
  97         }
  98     }
  99     g_list_free(actions);
 100     return match;
 101 }
 102 
 103 /*!
 104  * \internal
 105  * \brief Order starts and stops of an ordered clone's instances
 106  *
 107  * \param[in,out] rsc  Clone resource
 108  */
 109 static void
 110 order_instance_starts_stops(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 111 {
 112     pe_action_t *last_stop = NULL;
 113     pe_action_t *last_start = NULL;
 114 
 115     // Instances must be ordered by ascending instance number, so sort them
 116     rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number);
 117 
 118     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
 119         pe_resource_t *child = (pe_resource_t *) iter->data;
 120         pe_action_t *action = NULL;
 121 
 122         // Order this instance's stop after previous instance's stop
 123         // @TODO: Should instances be stopped in reverse order instead?
 124         action = find_rsc_action(child, RSC_STOP);
 125         if (action != NULL) {
 126             if (last_stop != NULL) {
 127                 order_actions(action, last_stop, pe_order_optional);
 128             }
 129             last_stop = action;
 130         }
 131 
 132         // Order this instance's start after previous instance's start
 133         action = find_rsc_action(child, RSC_START);
 134         if (action != NULL) {
 135             if (last_start != NULL) {
 136                 order_actions(last_start, action, pe_order_optional);
 137             }
 138             last_start = action;
 139         }
 140     }
 141 }
 142 
 143 void
 144 clone_create_actions(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 145 {
 146     pe_rsc_debug(rsc, "Creating actions for clone %s", rsc->id);
 147     pcmk__create_instance_actions(rsc, rsc->children);
 148     if (pe__clone_is_ordered(rsc)) {
 149         order_instance_starts_stops(rsc);
 150     }
 151     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 152         pcmk__create_promotable_actions(rsc);
 153     }
 154 }
 155 
 156 void
 157 clone_internal_constraints(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 158 {
 159     pe_resource_t *last_rsc = NULL;
 160     GList *gIter;
 161     bool ordered = pe__clone_is_ordered(rsc);
 162 
 163     pe_rsc_trace(rsc, "Internal constraints for %s", rsc->id);
 164     pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_START,
 165                                  pe_order_optional);
 166     pcmk__order_resource_actions(rsc, RSC_START, rsc, RSC_STARTED,
 167                                  pe_order_runnable_left);
 168     pcmk__order_resource_actions(rsc, RSC_STOP, rsc, RSC_STOPPED,
 169                                  pe_order_runnable_left);
 170 
 171     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 172         pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_STOP,
 173                                      pe_order_optional);
 174         pcmk__order_resource_actions(rsc, RSC_STARTED, rsc, RSC_PROMOTE,
 175                                      pe_order_runnable_left);
 176     }
 177 
 178     if (ordered) {
 179         /* we have to maintain a consistent sorted child list when building order constraints */
 180         rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number);
 181     }
 182     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
 183         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 184 
 185         child_rsc->cmds->internal_constraints(child_rsc);
 186 
 187         pcmk__order_starts(rsc, child_rsc,
 188                            pe_order_runnable_left|pe_order_implies_first_printed);
 189         pcmk__order_resource_actions(child_rsc, RSC_START, rsc, RSC_STARTED,
 190                                      pe_order_implies_then_printed);
 191         if (ordered && (last_rsc != NULL)) {
 192             pcmk__order_starts(last_rsc, child_rsc, pe_order_optional);
 193         }
 194 
 195         pcmk__order_stops(rsc, child_rsc, pe_order_implies_first_printed);
 196         pcmk__order_resource_actions(child_rsc, RSC_STOP, rsc, RSC_STOPPED,
 197                                      pe_order_implies_then_printed);
 198         if (ordered && (last_rsc != NULL)) {
 199             pcmk__order_stops(child_rsc, last_rsc, pe_order_optional);
 200         }
 201 
 202         last_rsc = child_rsc;
 203     }
 204     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 205         pcmk__order_promotable_instances(rsc);
 206     }
 207 }
 208 
 209 /*!
 210  * \internal
 211  * \brief Apply a colocation's score to node weights or resource priority
 212  *
 213  * Given a colocation constraint, apply its score to the dependent's
 214  * allowed node weights (if we are still placing resources) or priority (if
 215  * we are choosing promotable clone instance roles).
 216  *
 217  * \param[in,out] dependent      Dependent resource in colocation
 218  * \param[in]     primary        Primary resource in colocation
 219  * \param[in]     colocation     Colocation constraint to apply
 220  * \param[in]     for_dependent  true if called on behalf of dependent
 221  */
 222 void
 223 pcmk__clone_apply_coloc_score(pe_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
 224                               const pe_resource_t *primary,
 225                               const pcmk__colocation_t *colocation,
 226                               bool for_dependent)
 227 {
 228     GList *gIter = NULL;
 229     gboolean do_interleave = FALSE;
 230     const char *interleave_s = NULL;
 231 
 232     /* This should never be called for the clone itself as a dependent. Instead,
 233      * we add its colocation constraints to its instances and call the
 234      * apply_coloc_score() for the instances as dependents.
 235      */
 236     CRM_ASSERT(!for_dependent);
 237 
 238     CRM_CHECK((colocation != NULL) && (dependent != NULL) && (primary != NULL),
 239               return);
 240     CRM_CHECK(dependent->variant == pe_native, return);
 241 
 242     pe_rsc_trace(primary, "Processing constraint %s: %s -> %s %d",
 243                  colocation->id, dependent->id, primary->id, colocation->score);
 244 
 245     if (pcmk_is_set(primary->flags, pe_rsc_promotable)) {
 246         if (pcmk_is_set(primary->flags, pe_rsc_provisional)) {
 247             // We haven't placed the primary yet, so we can't apply colocation
 248             pe_rsc_trace(primary, "%s is still provisional", primary->id);
 249             return;
 250 
 251         } else if (colocation->primary_role == RSC_ROLE_UNKNOWN) {
 252             // This isn't a role-specfic colocation, so handle normally
 253             pe_rsc_trace(primary, "Handling %s as a clone colocation",
 254                          colocation->id);
 255 
 256         } else if (pcmk_is_set(dependent->flags, pe_rsc_provisional)) {
 257             // We're placing the dependent
 258             pcmk__update_dependent_with_promotable(primary, dependent,
 259                                                    colocation);
 260             return;
 261 
 262         } else if (colocation->dependent_role == RSC_ROLE_PROMOTED) {
 263             // We're choosing roles for the dependent
 264             pcmk__update_promotable_dependent_priority(primary, dependent,
 265                                                        colocation);
 266             return;
 267         }
 268     }
 269 
 270     // Only the dependent needs to be marked for interleave
 271     interleave_s = g_hash_table_lookup(colocation->dependent->meta,
 272                                        XML_RSC_ATTR_INTERLEAVE);
 273     if (crm_is_true(interleave_s)
 274         && (colocation->dependent->variant > pe_group)) {
 275         /* @TODO Do we actually care about multiple primary copies sharing a
 276          * dependent copy anymore?
 277          */
 278         if (copies_per_node(colocation->dependent) != copies_per_node(colocation->primary)) {
 279             pcmk__config_err("Cannot interleave %s and %s because they do not "
 280                              "support the same number of instances per node",
 281                              colocation->dependent->id,
 282                              colocation->primary->id);
 283 
 284         } else {
 285             do_interleave = TRUE;
 286         }
 287     }
 288 
 289     if (pcmk_is_set(primary->flags, pe_rsc_provisional)) {
 290         pe_rsc_trace(primary, "%s is still provisional", primary->id);
 291         return;
 292 
 293     } else if (do_interleave) {
 294         pe_resource_t *primary_instance = NULL;
 295 
 296         primary_instance = pcmk__find_compatible_instance(dependent, primary,
 297                                                           RSC_ROLE_UNKNOWN,
 298                                                           false);
 299         if (primary_instance != NULL) {
 300             pe_rsc_debug(primary, "Pairing %s with %s",
 301                          dependent->id, primary_instance->id);
 302             dependent->cmds->apply_coloc_score(dependent, primary_instance,
 303                                                colocation, true);
 304 
 305         } else if (colocation->score >= INFINITY) {
 306             crm_notice("Cannot pair %s with instance of %s",
 307                        dependent->id, primary->id);
 308             pcmk__assign_resource(dependent, NULL, true);
 309 
 310         } else {
 311             pe_rsc_debug(primary, "Cannot pair %s with instance of %s",
 312                          dependent->id, primary->id);
 313         }
 314 
 315         return;
 316 
 317     } else if (colocation->score >= INFINITY) {
 318         GList *affected_nodes = NULL;
 319 
 320         gIter = primary->children;
 321         for (; gIter != NULL; gIter = gIter->next) {
 322             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 323             pe_node_t *chosen = child_rsc->fns->location(child_rsc, NULL, FALSE);
 324 
 325             if (chosen != NULL && is_set_recursive(child_rsc, pe_rsc_block, TRUE) == FALSE) {
 326                 pe_rsc_trace(primary, "Allowing %s: %s %d",
 327                              colocation->id, pe__node_name(chosen),
 328                              chosen->weight);
 329                 affected_nodes = g_list_prepend(affected_nodes, chosen);
 330             }
 331         }
 332 
 333         node_list_exclude(dependent->allowed_nodes, affected_nodes, FALSE);
 334         g_list_free(affected_nodes);
 335         return;
 336     }
 337 
 338     gIter = primary->children;
 339     for (; gIter != NULL; gIter = gIter->next) {
 340         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 341 
 342         child_rsc->cmds->apply_coloc_score(dependent, child_rsc, colocation,
 343                                            false);
 344     }
 345 }
 346 
 347 // Clone implementation of resource_alloc_functions_t:with_this_colocations()
 348 void
 349 pcmk__with_clone_colocations(const pe_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 350                              const pe_resource_t *orig_rsc, GList **list)
 351 {
 352     CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
 353 
 354     if (rsc == orig_rsc) { // Colocations are wanted for clone itself
 355         pcmk__add_with_this_list(list, rsc->rsc_cons_lhs);
 356     } else {
 357         pcmk__add_collective_constraints(list, orig_rsc, rsc, true);
 358     }
 359 }
 360 
 361 // Clone implementation of resource_alloc_functions_t:this_with_colocations()
 362 void
 363 pcmk__clone_with_colocations(const pe_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 364                              const pe_resource_t *orig_rsc, GList **list)
 365 {
 366     CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
 367 
 368     if (rsc == orig_rsc) { // Colocations are wanted for clone itself
 369         pcmk__add_this_with_list(list, rsc->rsc_cons);
 370     } else {
 371         pcmk__add_collective_constraints(list, orig_rsc, rsc, false);
 372     }
 373 }
 374 
 375 enum pe_action_flags
 376 clone_action_flags(pe_action_t *action, const pe_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 377 {
 378     return pcmk__collective_action_flags(action, action->rsc->children, node);
 379 }
 380 
 381 void
 382 clone_rsc_location(pe_resource_t *rsc, pe__location_t *constraint)
     /* [previous][next][first][last][top][bottom][index][help] */
 383 {
 384     GList *gIter = rsc->children;
 385 
 386     pe_rsc_trace(rsc, "Processing location constraint %s for %s", constraint->id, rsc->id);
 387 
 388     pcmk__apply_location(rsc, constraint);
 389 
 390     for (; gIter != NULL; gIter = gIter->next) {
 391         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 392 
 393         child_rsc->cmds->apply_location(child_rsc, constraint);
 394     }
 395 }
 396 
 397 /*!
 398  * \internal
 399  * \brief Add a resource's actions to the transition graph
 400  *
 401  * \param[in,out] rsc  Resource whose actions should be added
 402  */
 403 void
 404 clone_expand(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 405 {
 406     GList *gIter = NULL;
 407 
 408     g_list_foreach(rsc->actions, (GFunc) rsc->cmds->action_flags, NULL);
 409 
 410     pe__create_clone_notifications(rsc);
 411 
 412     /* Now that the notifcations have been created we can expand the children */
 413 
 414     gIter = rsc->children;
 415     for (; gIter != NULL; gIter = gIter->next) {
 416         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 417 
 418         child_rsc->cmds->add_actions_to_graph(child_rsc);
 419     }
 420 
 421     pcmk__add_rsc_actions_to_graph(rsc);
 422 
 423     /* The notifications are in the graph now, we can destroy the notify_data */
 424     pe__free_clone_notification_data(rsc);
 425 }
 426 
 427 // Check whether a resource or any of its children is known on node
 428 static bool
 429 rsc_known_on(const pe_resource_t *rsc, const pe_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 430 {
 431     if (rsc->children) {
 432         for (GList *child_iter = rsc->children; child_iter != NULL;
 433              child_iter = child_iter->next) {
 434 
 435             pe_resource_t *child = (pe_resource_t *) child_iter->data;
 436 
 437             if (rsc_known_on(child, node)) {
 438                 return TRUE;
 439             }
 440         }
 441 
 442     } else if (rsc->known_on) {
 443         GHashTableIter iter;
 444         pe_node_t *known_node = NULL;
 445 
 446         g_hash_table_iter_init(&iter, rsc->known_on);
 447         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &known_node)) {
 448             if (node->details == known_node->details) {
 449                 return TRUE;
 450             }
 451         }
 452     }
 453     return FALSE;
 454 }
 455 
 456 // Look for an instance of clone that is known on node
 457 static pe_resource_t *
 458 find_instance_on(const pe_resource_t *clone, const pe_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 459 {
 460     for (GList *gIter = clone->children; gIter != NULL; gIter = gIter->next) {
 461         pe_resource_t *child = (pe_resource_t *) gIter->data;
 462 
 463         if (rsc_known_on(child, node)) {
 464             return child;
 465         }
 466     }
 467     return NULL;
 468 }
 469 
 470 // For anonymous clones, only a single instance needs to be probed
 471 static bool
 472 probe_anonymous_clone(pe_resource_t *rsc, pe_node_t *node,
     /* [previous][next][first][last][top][bottom][index][help] */
 473                       pe_working_set_t *data_set)
 474 {
 475     // First, check if we probed an instance on this node last time
 476     pe_resource_t *child = find_instance_on(rsc, node);
 477 
 478     // Otherwise, check if we plan to start an instance on this node
 479     if (child == NULL) {
 480         for (GList *child_iter = rsc->children; child_iter && !child;
 481              child_iter = child_iter->next) {
 482 
 483             pe_node_t *local_node = NULL;
 484             pe_resource_t *child_rsc = (pe_resource_t *) child_iter->data;
 485 
 486             if (child_rsc) { /* make clang analyzer happy */
 487                 local_node = child_rsc->fns->location(child_rsc, NULL, FALSE);
 488                 if (local_node && (local_node->details == node->details)) {
 489                     child = child_rsc;
 490                 }
 491             }
 492         }
 493     }
 494 
 495     // Otherwise, use the first clone instance
 496     if (child == NULL) {
 497         child = rsc->children->data;
 498     }
 499     CRM_ASSERT(child);
 500     return child->cmds->create_probe(child, node);
 501 }
 502 
 503 /*!
 504  * \internal
 505  *
 506  * \brief Schedule any probes needed for a resource on a node
 507  *
 508  * \param[in,out] rsc   Resource to create probe for
 509  * \param[in,out] node  Node to create probe on
 510  *
 511  * \return true if any probe was created, otherwise false
 512  */
 513 bool
 514 clone_create_probe(pe_resource_t *rsc, pe_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 515 {
 516     CRM_ASSERT(rsc);
 517 
 518     rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number);
 519     if (rsc->children == NULL) {
 520         pe_warn("Clone %s has no children", rsc->id);
 521         return false;
 522     }
 523 
 524     if (rsc->exclusive_discover) {
 525         pe_node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
 526         if (allowed && allowed->rsc_discover_mode != pe_discover_exclusive) {
 527             /* exclusive discover is enabled and this node is not marked
 528              * as a node this resource should be discovered on
 529              *
 530              * remove the node from allowed_nodes so that the
 531              * notification contains only nodes that we might ever run
 532              * on
 533              */
 534             g_hash_table_remove(rsc->allowed_nodes, node->details->id);
 535 
 536             /* Bit of a shortcut - might as well take it */
 537             return false;
 538         }
 539     }
 540 
 541     if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
 542         return pcmk__probe_resource_list(rsc->children, node);
 543     } else {
 544         return probe_anonymous_clone(rsc, node, rsc->cluster);
 545     }
 546 }
 547 
 548 void
 549 clone_append_meta(const pe_resource_t *rsc, xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 550 {
 551     char *name = NULL;
 552 
 553     name = crm_meta_name(XML_RSC_ATTR_UNIQUE);
 554     crm_xml_add(xml, name, pe__rsc_bool_str(rsc, pe_rsc_unique));
 555     free(name);
 556 
 557     name = crm_meta_name(XML_RSC_ATTR_NOTIFY);
 558     crm_xml_add(xml, name, pe__rsc_bool_str(rsc, pe_rsc_notify));
 559     free(name);
 560 
 561     name = crm_meta_name(XML_RSC_ATTR_INCARNATION_MAX);
 562     crm_xml_add_int(xml, name, pe__clone_max(rsc));
 563     free(name);
 564 
 565     name = crm_meta_name(XML_RSC_ATTR_INCARNATION_NODEMAX);
 566     crm_xml_add_int(xml, name, pe__clone_node_max(rsc));
 567     free(name);
 568 
 569     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 570         int promoted_max = pe__clone_promoted_max(rsc);
 571         int promoted_node_max = pe__clone_promoted_node_max(rsc);
 572 
 573         name = crm_meta_name(XML_RSC_ATTR_PROMOTED_MAX);
 574         crm_xml_add_int(xml, name, promoted_max);
 575         free(name);
 576 
 577         name = crm_meta_name(XML_RSC_ATTR_PROMOTED_NODEMAX);
 578         crm_xml_add_int(xml, name, promoted_node_max);
 579         free(name);
 580 
 581         /* @COMPAT Maintain backward compatibility with resource agents that
 582          * expect the old names (deprecated since 2.0.0).
 583          */
 584         name = crm_meta_name(PCMK_XA_PROMOTED_MAX_LEGACY);
 585         crm_xml_add_int(xml, name, promoted_max);
 586         free(name);
 587 
 588         name = crm_meta_name(PCMK_XA_PROMOTED_NODE_MAX_LEGACY);
 589         crm_xml_add_int(xml, name, promoted_node_max);
 590         free(name);
 591     }
 592 }
 593 
 594 // Clone implementation of resource_alloc_functions_t:add_utilization()
 595 void
 596 pcmk__clone_add_utilization(const pe_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 597                             const pe_resource_t *orig_rsc, GList *all_rscs,
 598                             GHashTable *utilization)
 599 {
 600     bool existing = false;
 601     pe_resource_t *child = NULL;
 602 
 603     if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
 604         return;
 605     }
 606 
 607     // Look for any child already existing in the list
 608     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
 609         child = (pe_resource_t *) iter->data;
 610         if (g_list_find(all_rscs, child)) {
 611             existing = true; // Keep checking remaining children
 612         } else {
 613             // If this is a clone of a group, look for group's members
 614             for (GList *member_iter = child->children; member_iter != NULL;
 615                  member_iter = member_iter->next) {
 616 
 617                 pe_resource_t *member = (pe_resource_t *) member_iter->data;
 618 
 619                 if (g_list_find(all_rscs, member) != NULL) {
 620                     // Add *child's* utilization, not group member's
 621                     child->cmds->add_utilization(child, orig_rsc, all_rscs,
 622                                                  utilization);
 623                     existing = true;
 624                     break;
 625                 }
 626             }
 627         }
 628     }
 629 
 630     if (!existing && (rsc->children != NULL)) {
 631         // If nothing was found, still add first child's utilization
 632         child = (pe_resource_t *) rsc->children->data;
 633 
 634         child->cmds->add_utilization(child, orig_rsc, all_rscs, utilization);
 635     }
 636 }
 637 
 638 // Clone implementation of resource_alloc_functions_t:shutdown_lock()
 639 void
 640 pcmk__clone_shutdown_lock(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 641 {
 642     return; // Clones currently don't support shutdown locks
 643 }

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