root/lib/pengine/clone.c

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

DEFINITIONS

This source file includes following definitions.
  1. pe__clone_max
  2. pe__clone_node_max
  3. pe__clone_promoted_max
  4. pe__clone_promoted_node_max
  5. sorted_hash_table_values
  6. nodes_with_status
  7. node_list_to_str
  8. clone_header
  9. pe__force_anon
  10. pe__create_clone_child
  11. unpack_meta_int
  12. clone_unpack
  13. clone_active
  14. configured_role_str
  15. configured_role
  16. is_set_recursive
  17. PCMK__OUTPUT_ARGS
  18. PCMK__OUTPUT_ARGS
  19. clone_free
  20. clone_resource_state
  21. pe__is_universal_clone
  22. pe__clone_is_filtered
  23. pe__clone_child_id
  24. pe__clone_is_ordered
  25. pe__set_clone_flag
  26. pe__clone_flag_is_set
  27. pe__create_promotable_pseudo_ops
  28. pe__create_clone_notifications
  29. pe__free_clone_notification_data
  30. pe__create_clone_notif_pseudo_ops
  31. pe__clone_max_per_node

   1 /*
   2  * Copyright 2004-2025 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdbool.h>                        // bool, true, false
  13 #include <stdint.h>
  14 
  15 #include <crm/pengine/status.h>
  16 #include <crm/pengine/internal.h>
  17 #include <pe_status_private.h>
  18 #include <crm/common/xml.h>
  19 #include <crm/common/output.h>
  20 #include <crm/common/xml_internal.h>
  21 #include <crm/common/scheduler_internal.h>
  22 
  23 typedef struct clone_variant_data_s {
  24     int clone_max;
  25     int clone_node_max;
  26 
  27     int promoted_max;
  28     int promoted_node_max;
  29 
  30     int total_clones;
  31 
  32     uint32_t flags; // Group of enum pcmk__clone_flags
  33 
  34     notify_data_t *stop_notify;
  35     notify_data_t *start_notify;
  36     notify_data_t *demote_notify;
  37     notify_data_t *promote_notify;
  38 
  39     xmlNode *xml_obj_child;
  40 } clone_variant_data_t;
  41 
  42 #define get_clone_variant_data(data, rsc) do {  \
  43         pcmk__assert(pcmk__is_clone(rsc));      \
  44         data = rsc->priv->variant_opaque;       \
  45     } while (0)
  46 
  47 /*!
  48  * \internal
  49  * \brief Return the maximum number of clone instances allowed to be run
  50  *
  51  * \param[in] clone  Clone or clone instance to check
  52  *
  53  * \return Maximum instances for \p clone
  54  */
  55 int
  56 pe__clone_max(const pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
  57 {
  58     const clone_variant_data_t *clone_data = NULL;
  59 
  60     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
  61     return clone_data->clone_max;
  62 }
  63 
  64 /*!
  65  * \internal
  66  * \brief Return the maximum number of clone instances allowed per node
  67  *
  68  * \param[in] clone  Promotable clone or clone instance to check
  69  *
  70  * \return Maximum allowed instances per node for \p clone
  71  */
  72 int
  73 pe__clone_node_max(const pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
  74 {
  75     const clone_variant_data_t *clone_data = NULL;
  76 
  77     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
  78     return clone_data->clone_node_max;
  79 }
  80 
  81 /*!
  82  * \internal
  83  * \brief Return the maximum number of clone instances allowed to be promoted
  84  *
  85  * \param[in] clone  Promotable clone or clone instance to check
  86  *
  87  * \return Maximum promoted instances for \p clone
  88  */
  89 int
  90 pe__clone_promoted_max(const pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
  91 {
  92     clone_variant_data_t *clone_data = NULL;
  93 
  94     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
  95     return clone_data->promoted_max;
  96 }
  97 
  98 /*!
  99  * \internal
 100  * \brief Return the maximum number of clone instances allowed to be promoted
 101  *
 102  * \param[in] clone  Promotable clone or clone instance to check
 103  *
 104  * \return Maximum promoted instances for \p clone
 105  */
 106 int
 107 pe__clone_promoted_node_max(const pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
 108 {
 109     clone_variant_data_t *clone_data = NULL;
 110 
 111     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
 112     return clone_data->promoted_node_max;
 113 }
 114 
 115 static GList *
 116 sorted_hash_table_values(GHashTable *table)
     /* [previous][next][first][last][top][bottom][index][help] */
 117 {
 118     GList *retval = NULL;
 119     GHashTableIter iter;
 120     gpointer key, value;
 121 
 122     g_hash_table_iter_init(&iter, table);
 123     while (g_hash_table_iter_next(&iter, &key, &value)) {
 124         if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
 125             retval = g_list_prepend(retval, (char *) value);
 126         }
 127     }
 128 
 129     retval = g_list_sort(retval, (GCompareFunc) strcmp);
 130     return retval;
 131 }
 132 
 133 static GList *
 134 nodes_with_status(GHashTable *table, const char *status)
     /* [previous][next][first][last][top][bottom][index][help] */
 135 {
 136     GList *retval = NULL;
 137     GHashTableIter iter;
 138     gpointer key, value;
 139 
 140     g_hash_table_iter_init(&iter, table);
 141     while (g_hash_table_iter_next(&iter, &key, &value)) {
 142         if (!strcmp((char *) value, status)) {
 143             retval = g_list_prepend(retval, key);
 144         }
 145     }
 146 
 147     retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
 148     return retval;
 149 }
 150 
 151 static GString *
 152 node_list_to_str(const GList *list)
     /* [previous][next][first][last][top][bottom][index][help] */
 153 {
 154     GString *retval = NULL;
 155 
 156     for (const GList *iter = list; iter != NULL; iter = iter->next) {
 157         pcmk__add_word(&retval, 1024, (const char *) iter->data);
 158     }
 159 
 160     return retval;
 161 }
 162 
 163 static void
 164 clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 165              clone_variant_data_t *clone_data, const char *desc)
 166 {
 167     GString *attrs = NULL;
 168 
 169     if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
 170         pcmk__add_separated_word(&attrs, 64, "promotable", ", ");
 171     }
 172 
 173     if (pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
 174         pcmk__add_separated_word(&attrs, 64, "unique", ", ");
 175     }
 176 
 177     if (pe__resource_is_disabled(rsc)) {
 178         pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
 179     }
 180 
 181     if (pcmk_is_set(rsc->flags, pcmk__rsc_maintenance)) {
 182         pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
 183 
 184     } else if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
 185         pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
 186     }
 187 
 188     if (attrs != NULL) {
 189         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s",
 190                                  rsc->id,
 191                                  pcmk__xe_id(clone_data->xml_obj_child),
 192                                  (const char *) attrs->str, desc ? " (" : "",
 193                                  desc ? desc : "", desc ? ")" : "");
 194         g_string_free(attrs, TRUE);
 195     } else {
 196         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s",
 197                                  rsc->id,
 198                                  pcmk__xe_id(clone_data->xml_obj_child),
 199                                  desc ? " (" : "", desc ? desc : "",
 200                                  desc ? ")" : "");
 201     }
 202 }
 203 
 204 void
 205 pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid,
     /* [previous][next][first][last][top][bottom][index][help] */
 206                pcmk_scheduler_t *scheduler)
 207 {
 208     if (pcmk__is_clone(rsc)) {
 209         clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
 210 
 211         pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s "
 212                           "because %s resources such as %s can be used only as "
 213                           "anonymous clones", rsc->id, standard, rid);
 214 
 215         clone_data->clone_node_max = 1;
 216         clone_data->clone_max = QB_MIN(clone_data->clone_max,
 217                                        g_list_length(scheduler->nodes));
 218     }
 219 }
 220 
 221 pcmk_resource_t *
 222 pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 223 {
 224     gboolean as_orphan = FALSE;
 225     char *inc_num = NULL;
 226     char *inc_max = NULL;
 227     pcmk_resource_t *child_rsc = NULL;
 228     xmlNode *child_copy = NULL;
 229     clone_variant_data_t *clone_data = NULL;
 230 
 231     get_clone_variant_data(clone_data, rsc);
 232 
 233     CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
 234 
 235     if (clone_data->total_clones >= clone_data->clone_max) {
 236         // If we've already used all available instances, this is an orphan
 237         as_orphan = TRUE;
 238     }
 239 
 240     // Allocate instance numbers in numerical order (starting at 0)
 241     inc_num = pcmk__itoa(clone_data->total_clones);
 242     inc_max = pcmk__itoa(clone_data->clone_max);
 243 
 244     child_copy = pcmk__xml_copy(NULL, clone_data->xml_obj_child);
 245 
 246     crm_xml_add(child_copy, PCMK__META_CLONE, inc_num);
 247 
 248     if (pe__unpack_resource(child_copy, &child_rsc, rsc,
 249                             scheduler) != pcmk_rc_ok) {
 250         goto bail;
 251     }
 252 /*  child_rsc->globally_unique = rsc->globally_unique; */
 253 
 254     pcmk__assert(child_rsc != NULL);
 255     clone_data->total_clones += 1;
 256     pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s",
 257                     child_rsc->id);
 258     rsc->priv->children = g_list_append(rsc->priv->children, child_rsc);
 259     if (as_orphan) {
 260         pe__set_resource_flags_recursive(child_rsc, pcmk__rsc_removed);
 261     }
 262 
 263     pcmk__insert_meta(child_rsc->priv, PCMK_META_CLONE_MAX, inc_max);
 264     pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
 265 
 266   bail:
 267     free(inc_num);
 268     free(inc_max);
 269 
 270     return child_rsc;
 271 }
 272 
 273 /*!
 274  * \internal
 275  * \brief Unpack a nonnegative integer value from a resource meta-attribute
 276  *
 277  * \param[in]  rsc              Resource with meta-attribute
 278  * \param[in]  meta_name        Name of meta-attribute to unpack
 279  * \param[in]  deprecated_name  If not NULL, try unpacking this
 280  *                              if \p meta_name is unset
 281  * \param[in]  default_value    Value to use if unset
 282  *
 283  * \return Integer parsed from resource's specified meta-attribute if a valid
 284  *         nonnegative integer, \p default_value if unset, or 0 if invalid
 285  */
 286 static int
 287 unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 288                 const char *deprecated_name, int default_value)
 289 {
 290     int integer = default_value;
 291     const char *value = g_hash_table_lookup(rsc->priv->meta, meta_name);
 292 
 293     if ((value == NULL) && (deprecated_name != NULL)) {
 294         value = g_hash_table_lookup(rsc->priv->meta, deprecated_name);
 295 
 296         if (value != NULL) {
 297             if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_MAX_LEGACY,
 298                              pcmk__str_none)) {
 299                 pcmk__warn_once(pcmk__wo_clone_master_max,
 300                                 "Support for the " PCMK__META_PROMOTED_MAX_LEGACY
 301                                 " meta-attribute (such as in %s) is deprecated "
 302                                 "and will be removed in a future release. Use the "
 303                                 PCMK_META_PROMOTED_MAX " meta-attribute instead.",
 304                                 rsc->id);
 305             } else if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_NODE_MAX_LEGACY,
 306                                     pcmk__str_none)) {
 307                 pcmk__warn_once(pcmk__wo_clone_master_node_max,
 308                                 "Support for the " PCMK__META_PROMOTED_NODE_MAX_LEGACY
 309                                 " meta-attribute (such as in %s) is deprecated "
 310                                 "and will be removed in a future release. Use the "
 311                                 PCMK_META_PROMOTED_NODE_MAX " meta-attribute instead.",
 312                                 rsc->id);
 313             }
 314         }
 315     }
 316     if (value != NULL) {
 317         pcmk__scan_min_int(value, &integer, 0);
 318     }
 319     return integer;
 320 }
 321 
 322 bool
 323 clone_unpack(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 324 {
 325     int lpc = 0;
 326     int num_nodes = g_list_length(rsc->priv->scheduler->nodes);
 327     xmlNode *a_child = NULL;
 328     xmlNode *xml_obj = rsc->priv->xml;
 329     clone_variant_data_t *clone_data = NULL;
 330 
 331     pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
 332 
 333     clone_data = pcmk__assert_alloc(1, sizeof(clone_variant_data_t));
 334     rsc->priv->variant_opaque = clone_data;
 335 
 336     if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
 337         // Use 1 as default but 0 for minimum and invalid
 338         // @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0
 339         clone_data->promoted_max =
 340             unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX,
 341                             PCMK__META_PROMOTED_MAX_LEGACY, 1);
 342 
 343         // Use 1 as default but 0 for minimum and invalid
 344         // @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0
 345         clone_data->promoted_node_max =
 346             unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX,
 347                             PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1);
 348     }
 349 
 350     // Use 1 as default but 0 for minimum and invalid
 351     clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX,
 352                                                  NULL, 1);
 353 
 354     /* Use number of nodes (but always at least 1, which is handy for crm_verify
 355      * for a CIB without nodes) as default, but 0 for minimum and invalid
 356      *
 357      * @TODO Exclude bundle nodes when counting
 358      */
 359     clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL,
 360                                             QB_MAX(1, num_nodes));
 361 
 362     if (crm_is_true(g_hash_table_lookup(rsc->priv->meta,
 363                                         PCMK_META_ORDERED))) {
 364         clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
 365                                                "Clone", rsc->id,
 366                                                clone_data->flags,
 367                                                pcmk__clone_ordered,
 368                                                "pcmk__clone_ordered");
 369     }
 370 
 371     if (!pcmk_is_set(rsc->flags, pcmk__rsc_unique)
 372         && (clone_data->clone_node_max > 1)) {
 373 
 374         pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s "
 375                          "because anonymous clones support only one instance "
 376                          "per node", clone_data->clone_node_max, rsc->id);
 377         clone_data->clone_node_max = 1;
 378     }
 379 
 380     pcmk__rsc_trace(rsc, "Options for %s", rsc->id);
 381     pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
 382     pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
 383     pcmk__rsc_trace(rsc, "\tClone is unique: %s",
 384                     pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
 385     pcmk__rsc_trace(rsc, "\tClone is promotable: %s",
 386                     pcmk__flag_text(rsc->flags, pcmk__rsc_promotable));
 387 
 388     // Clones may contain a single group or primitive
 389     for (a_child = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
 390          a_child != NULL; a_child = pcmk__xe_next(a_child, NULL)) {
 391 
 392         if (pcmk__str_any_of((const char *) a_child->name,
 393                              PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) {
 394             clone_data->xml_obj_child = a_child;
 395             break;
 396         }
 397     }
 398 
 399     if (clone_data->xml_obj_child == NULL) {
 400         pcmk__config_err("%s has nothing to clone", rsc->id);
 401         return FALSE;
 402     }
 403 
 404     /*
 405      * Make clones ever so slightly sticky by default
 406      *
 407      * This helps ensure clone instances are not shuffled around the cluster
 408      * for no benefit in situations when pre-allocation is not appropriate
 409      */
 410     if (g_hash_table_lookup(rsc->priv->meta,
 411                             PCMK_META_RESOURCE_STICKINESS) == NULL) {
 412         pcmk__insert_meta(rsc->priv, PCMK_META_RESOURCE_STICKINESS, "1");
 413     }
 414 
 415     /* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for
 416      * children to inherit when being unpacked, as well as in resource agents'
 417      * environment.
 418      */
 419     pcmk__insert_meta(rsc->priv, PCMK_META_GLOBALLY_UNIQUE,
 420                       pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
 421 
 422     if (clone_data->clone_max <= 0) {
 423         /* Create one child instance so that unpack_find_resource() will hook up
 424          * any orphans up to the parent correctly.
 425          */
 426         if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
 427             return FALSE;
 428         }
 429 
 430     } else {
 431         // Create a child instance for each available instance number
 432         for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
 433             if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
 434                 return FALSE;
 435             }
 436         }
 437     }
 438 
 439     pcmk__rsc_trace(rsc, "Added %d children to resource %s...",
 440                     clone_data->clone_max, rsc->id);
 441     return TRUE;
 442 }
 443 
 444 bool
 445 clone_active(const pcmk_resource_t *rsc, bool all)
     /* [previous][next][first][last][top][bottom][index][help] */
 446 {
 447     for (GList *gIter = rsc->priv->children;
 448          gIter != NULL; gIter = gIter->next) {
 449 
 450         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 451         bool child_active = child_rsc->priv->fns->active(child_rsc, all);
 452 
 453         if (all == FALSE && child_active) {
 454             return TRUE;
 455         } else if (all && child_active == FALSE) {
 456             return FALSE;
 457         }
 458     }
 459 
 460     if (all) {
 461         return TRUE;
 462     } else {
 463         return FALSE;
 464     }
 465 }
 466 
 467 static const char *
 468 configured_role_str(pcmk_resource_t * rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 469 {
 470     const char *target_role = g_hash_table_lookup(rsc->priv->meta,
 471                                                   PCMK_META_TARGET_ROLE);
 472 
 473     if ((target_role == NULL) && (rsc->priv->children != NULL)) {
 474         // Any instance will do
 475         pcmk_resource_t *instance = rsc->priv->children->data;
 476 
 477         target_role = g_hash_table_lookup(instance->priv->meta,
 478                                           PCMK_META_TARGET_ROLE);
 479     }
 480     return target_role;
 481 }
 482 
 483 static enum rsc_role_e
 484 configured_role(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 485 {
 486     enum rsc_role_e role = pcmk_role_unknown;
 487     const char *target_role = configured_role_str(rsc);
 488 
 489     if (target_role != NULL) {
 490         role = pcmk_parse_role(target_role);
 491         if (role == pcmk_role_unknown) {
 492             pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
 493                              " for resource %s", rsc->id);
 494         }
 495     }
 496     return role;
 497 }
 498 
 499 bool
 500 is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any)
     /* [previous][next][first][last][top][bottom][index][help] */
 501 {
 502     bool all = !any;
 503 
 504     if (pcmk_is_set(rsc->flags, flag)) {
 505         if(any) {
 506             return TRUE;
 507         }
 508     } else if(all) {
 509         return FALSE;
 510     }
 511 
 512     for (GList *gIter = rsc->priv->children;
 513          gIter != NULL; gIter = gIter->next) {
 514 
 515         if(is_set_recursive(gIter->data, flag, any)) {
 516             if(any) {
 517                 return TRUE;
 518             }
 519 
 520         } else if(all) {
 521             return FALSE;
 522         }
 523     }
 524 
 525     if(all) {
 526         return TRUE;
 527     }
 528     return FALSE;
 529 }
 530 
 531 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
 532                   "GList *")
 533 int
 534 pe__clone_xml(pcmk__output_t *out, va_list args)
 535 {
 536     uint32_t show_opts = va_arg(args, uint32_t);
 537     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
 538     GList *only_node = va_arg(args, GList *);
 539     GList *only_rsc = va_arg(args, GList *);
 540 
 541     GList *all = NULL;
 542     int rc = pcmk_rc_no_output;
 543     gboolean printed_header = FALSE;
 544     bool print_everything = true;
 545 
 546     if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
 547         return rc;
 548     }
 549 
 550     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
 551                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 552 
 553     all = g_list_prepend(all, (gpointer) "*");
 554 
 555     for (GList *gIter = rsc->priv->children;
 556          gIter != NULL; gIter = gIter->next) {
 557 
 558         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 559 
 560         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
 561             continue;
 562         }
 563 
 564         if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
 565                                               print_everything)) {
 566             continue;
 567         }
 568 
 569         if (!printed_header) {
 570             const char *multi_state = pcmk__flag_text(rsc->flags,
 571                                                       pcmk__rsc_promotable);
 572             const char *unique = pcmk__flag_text(rsc->flags, pcmk__rsc_unique);
 573             const char *maintenance = pcmk__flag_text(rsc->flags,
 574                                                       pcmk__rsc_maintenance);
 575             const char *managed = pcmk__flag_text(rsc->flags,
 576                                                   pcmk__rsc_managed);
 577             const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
 578             const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed);
 579             const char *ignored = pcmk__flag_text(rsc->flags,
 580                                                   pcmk__rsc_ignore_failure);
 581             const char *target_role = configured_role_str(rsc);
 582             const char *desc = pe__resource_description(rsc, show_opts);
 583 
 584             printed_header = TRUE;
 585 
 586             rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE,
 587                                           PCMK_XA_ID, rsc->id,
 588                                           PCMK_XA_MULTI_STATE, multi_state,
 589                                           PCMK_XA_UNIQUE, unique,
 590                                           PCMK_XA_MAINTENANCE, maintenance,
 591                                           PCMK_XA_MANAGED, managed,
 592                                           PCMK_XA_DISABLED, disabled,
 593                                           PCMK_XA_FAILED, failed,
 594                                           PCMK_XA_FAILURE_IGNORED, ignored,
 595                                           PCMK_XA_TARGET_ROLE, target_role,
 596                                           PCMK_XA_DESCRIPTION, desc,
 597                                           NULL);
 598             pcmk__assert(rc == pcmk_rc_ok);
 599         }
 600 
 601         out->message(out, (const char *) child_rsc->priv->xml->name,
 602                      show_opts, child_rsc, only_node, all);
 603     }
 604 
 605     if (printed_header) {
 606         pcmk__output_xml_pop_parent(out);
 607     }
 608 
 609     g_list_free(all);
 610     return rc;
 611 }
 612 
 613 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
 614                   "GList *")
 615 int
 616 pe__clone_default(pcmk__output_t *out, va_list args)
 617 {
 618     uint32_t show_opts = va_arg(args, uint32_t);
 619     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
 620     GList *only_node = va_arg(args, GList *);
 621     GList *only_rsc = va_arg(args, GList *);
 622 
 623     GHashTable *stopped = NULL;
 624 
 625     GString *list_text = NULL;
 626 
 627     GList *promoted_list = NULL;
 628     GList *started_list = NULL;
 629     GList *gIter = NULL;
 630 
 631     const char *desc = NULL;
 632 
 633     clone_variant_data_t *clone_data = NULL;
 634     int active_instances = 0;
 635     int rc = pcmk_rc_no_output;
 636     gboolean print_everything = TRUE;
 637 
 638     desc = pe__resource_description(rsc, show_opts);
 639 
 640     get_clone_variant_data(clone_data, rsc);
 641 
 642     if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
 643         return rc;
 644     }
 645 
 646     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
 647                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 648 
 649     for (gIter = rsc->priv->children; gIter != NULL; gIter = gIter->next) {
 650         gboolean print_full = FALSE;
 651         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 652         bool partially_active = child_rsc->priv->fns->active(child_rsc, false);
 653 
 654         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
 655             continue;
 656         }
 657 
 658         if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
 659                                               print_everything)) {
 660             continue;
 661         }
 662 
 663         if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
 664             print_full = TRUE;
 665         }
 666 
 667         if (pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
 668             // Print individual instance when unique (except stopped orphans)
 669             if (partially_active
 670                 || !pcmk_is_set(rsc->flags, pcmk__rsc_removed)) {
 671                 print_full = TRUE;
 672             }
 673 
 674         // Everything else in this block is for anonymous clones
 675 
 676         } else if (pcmk_is_set(show_opts, pcmk_show_pending)
 677                    && (child_rsc->priv->pending_action != NULL)
 678                    && (strcmp(child_rsc->priv->pending_action,
 679                               "probe") != 0)) {
 680             // Print individual instance when non-probe action is pending
 681             print_full = TRUE;
 682 
 683         } else if (partially_active == FALSE) {
 684             // List stopped instances when requested (except orphans)
 685             if (!pcmk_is_set(child_rsc->flags, pcmk__rsc_removed)
 686                 && !pcmk_is_set(show_opts, pcmk_show_clone_detail)
 687                 && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
 688                 if (stopped == NULL) {
 689                     stopped = pcmk__strkey_table(free, free);
 690                 }
 691                 pcmk__insert_dup(stopped, child_rsc->id, "Stopped");
 692             }
 693 
 694         } else if (is_set_recursive(child_rsc, pcmk__rsc_removed, TRUE)
 695                    || !is_set_recursive(child_rsc, pcmk__rsc_managed, FALSE)
 696                    || is_set_recursive(child_rsc, pcmk__rsc_failed, TRUE)) {
 697 
 698             // Print individual instance when active orphaned/unmanaged/failed
 699             print_full = TRUE;
 700 
 701         } else if (child_rsc->priv->fns->active(child_rsc, true)) {
 702             // Instance of fully active anonymous clone
 703 
 704             pcmk_node_t *location = NULL;
 705 
 706             location = child_rsc->priv->fns->location(child_rsc, NULL,
 707                                                       pcmk__rsc_node_current);
 708             if (location) {
 709                 // Instance is active on a single node
 710 
 711                 enum rsc_role_e a_role;
 712 
 713                 a_role = child_rsc->priv->fns->state(child_rsc, TRUE);
 714 
 715                 if (location->details->online == FALSE && location->details->unclean) {
 716                     print_full = TRUE;
 717 
 718                 } else if (a_role > pcmk_role_unpromoted) {
 719                     promoted_list = g_list_append(promoted_list, location);
 720 
 721                 } else {
 722                     started_list = g_list_append(started_list, location);
 723                 }
 724 
 725             } else {
 726                 /* uncolocated group - bleh */
 727                 print_full = TRUE;
 728             }
 729 
 730         } else {
 731             // Instance of partially active anonymous clone
 732             print_full = TRUE;
 733         }
 734 
 735         if (print_full) {
 736             GList *all = NULL;
 737 
 738             clone_header(out, &rc, rsc, clone_data, desc);
 739 
 740             /* Print every resource that's a child of this clone. */
 741             all = g_list_prepend(all, (gpointer) "*");
 742             out->message(out, (const char *) child_rsc->priv->xml->name,
 743                          show_opts, child_rsc, only_node, all);
 744             g_list_free(all);
 745         }
 746     }
 747 
 748     if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
 749         PCMK__OUTPUT_LIST_FOOTER(out, rc);
 750         return pcmk_rc_ok;
 751     }
 752 
 753     /* Promoted */
 754     promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
 755     for (gIter = promoted_list; gIter; gIter = gIter->next) {
 756         pcmk_node_t *host = gIter->data;
 757 
 758         if (!pcmk__str_in_list(host->priv->name, only_node,
 759                                pcmk__str_star_matches|pcmk__str_casei)) {
 760             continue;
 761         }
 762 
 763         pcmk__add_word(&list_text, 1024, host->priv->name);
 764         active_instances++;
 765     }
 766     g_list_free(promoted_list);
 767 
 768     if ((list_text != NULL) && (list_text->len > 0)) {
 769         clone_header(out, &rc, rsc, clone_data, desc);
 770 
 771         out->list_item(out, NULL, PCMK_ROLE_PROMOTED ": [ %s ]",
 772                        (const char *) list_text->str);
 773         g_string_truncate(list_text, 0);
 774     }
 775 
 776     /* Started/Unpromoted */
 777     started_list = g_list_sort(started_list, pe__cmp_node_name);
 778     for (gIter = started_list; gIter; gIter = gIter->next) {
 779         pcmk_node_t *host = gIter->data;
 780 
 781         if (!pcmk__str_in_list(host->priv->name, only_node,
 782                                pcmk__str_star_matches|pcmk__str_casei)) {
 783             continue;
 784         }
 785 
 786         pcmk__add_word(&list_text, 1024, host->priv->name);
 787         active_instances++;
 788     }
 789     g_list_free(started_list);
 790 
 791     if ((list_text != NULL) && (list_text->len > 0)) {
 792         clone_header(out, &rc, rsc, clone_data, desc);
 793 
 794         if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
 795             enum rsc_role_e role = configured_role(rsc);
 796 
 797             if (role == pcmk_role_unpromoted) {
 798                 out->list_item(out, NULL,
 799                                PCMK_ROLE_UNPROMOTED
 800                                " (" PCMK_META_TARGET_ROLE "): [ %s ]",
 801                                (const char *) list_text->str);
 802             } else {
 803                 out->list_item(out, NULL, PCMK_ROLE_UNPROMOTED ": [ %s ]",
 804                                (const char *) list_text->str);
 805             }
 806 
 807         } else {
 808             out->list_item(out, NULL, "Started: [ %s ]",
 809                            (const char *) list_text->str);
 810         }
 811     }
 812 
 813     if (list_text != NULL) {
 814         g_string_free(list_text, TRUE);
 815     }
 816 
 817     if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
 818         if (!pcmk_is_set(rsc->flags, pcmk__rsc_unique)
 819             && (clone_data->clone_max > active_instances)) {
 820 
 821             GList *nIter;
 822             GList *list = g_hash_table_get_values(rsc->priv->allowed_nodes);
 823 
 824             /* Custom stopped table for non-unique clones */
 825             if (stopped != NULL) {
 826                 g_hash_table_destroy(stopped);
 827                 stopped = NULL;
 828             }
 829 
 830             if (list == NULL) {
 831                 /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
 832                  * calculated allowed nodes yet. If we've not probed for them
 833                  * yet, the Stopped list will be empty.
 834                  */
 835                 list = g_hash_table_get_values(rsc->priv->probed_nodes);
 836             }
 837 
 838             list = g_list_sort(list, pe__cmp_node_name);
 839             for (nIter = list; nIter != NULL; nIter = nIter->next) {
 840                 pcmk_node_t *node = (pcmk_node_t *) nIter->data;
 841 
 842                 if ((pcmk__find_node_in_list(rsc->priv->active_nodes,
 843                                              node->priv->name) == NULL)
 844                     && pcmk__str_in_list(node->priv->name, only_node,
 845                                          pcmk__str_star_matches|pcmk__str_casei)) {
 846 
 847                     xmlNode *probe_op = NULL;
 848                     const char *state = "Stopped";
 849 
 850                     if (configured_role(rsc) == pcmk_role_stopped) {
 851                         state = "Stopped (disabled)";
 852                     }
 853 
 854                     if (stopped == NULL) {
 855                         stopped = pcmk__strkey_table(free, free);
 856                     }
 857 
 858                     probe_op = pe__failed_probe_for_rsc(rsc,
 859                                                         node->priv->name);
 860                     if (probe_op != NULL) {
 861                         int rc;
 862 
 863                         pcmk__scan_min_int(crm_element_value(probe_op,
 864                                                              PCMK__XA_RC_CODE),
 865                                            &rc, 0);
 866                         g_hash_table_insert(stopped, strdup(node->priv->name),
 867                                             crm_strdup_printf("Stopped (%s)",
 868                                                               crm_exit_str(rc)));
 869                     } else {
 870                         pcmk__insert_dup(stopped, node->priv->name, state);
 871                     }
 872                 }
 873             }
 874             g_list_free(list);
 875         }
 876 
 877         if (stopped != NULL) {
 878             GList *list = sorted_hash_table_values(stopped);
 879 
 880             clone_header(out, &rc, rsc, clone_data, desc);
 881 
 882             for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
 883                 const char *status = status_iter->data;
 884                 GList *nodes = nodes_with_status(stopped, status);
 885                 GString *nodes_str = node_list_to_str(nodes);
 886 
 887                 if (nodes_str != NULL) {
 888                     if (nodes_str->len > 0) {
 889                         out->list_item(out, NULL, "%s: [ %s ]", status,
 890                                        (const char *) nodes_str->str);
 891                     }
 892                     g_string_free(nodes_str, TRUE);
 893                 }
 894 
 895                 g_list_free(nodes);
 896             }
 897 
 898             g_list_free(list);
 899             g_hash_table_destroy(stopped);
 900 
 901         /* If there are no instances of this clone (perhaps because there are no
 902          * nodes configured), simply output the clone header by itself.  This can
 903          * come up in PCS testing.
 904          */
 905         } else if (active_instances == 0) {
 906             clone_header(out, &rc, rsc, clone_data, desc);
 907             PCMK__OUTPUT_LIST_FOOTER(out, rc);
 908             return rc;
 909         }
 910     }
 911 
 912     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 913     return rc;
 914 }
 915 
 916 void
 917 clone_free(pcmk_resource_t * rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 918 {
 919     clone_variant_data_t *clone_data = NULL;
 920 
 921     get_clone_variant_data(clone_data, rsc);
 922 
 923     pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
 924 
 925     for (GList *gIter = rsc->priv->children;
 926          gIter != NULL; gIter = gIter->next) {
 927 
 928         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 929 
 930         pcmk__assert(child_rsc != NULL);
 931         pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
 932         pcmk__xml_free(child_rsc->priv->xml);
 933         child_rsc->priv->xml = NULL;
 934         /* There could be a saved unexpanded xml */
 935         pcmk__xml_free(child_rsc->priv->orig_xml);
 936         child_rsc->priv->orig_xml = NULL;
 937         pcmk__free_resource(child_rsc);
 938     }
 939 
 940     g_list_free(rsc->priv->children);
 941 
 942     if (clone_data) {
 943         pcmk__assert((clone_data->demote_notify == NULL)
 944                      && (clone_data->stop_notify == NULL)
 945                      && (clone_data->start_notify == NULL)
 946                      && (clone_data->promote_notify == NULL));
 947     }
 948 
 949     common_free(rsc);
 950 }
 951 
 952 enum rsc_role_e
 953 clone_resource_state(const pcmk_resource_t *rsc, bool current)
     /* [previous][next][first][last][top][bottom][index][help] */
 954 {
 955     enum rsc_role_e clone_role = pcmk_role_unknown;
 956 
 957     for (GList *gIter = rsc->priv->children;
 958          gIter != NULL; gIter = gIter->next) {
 959 
 960         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 961         enum rsc_role_e a_role = child_rsc->priv->fns->state(child_rsc,
 962                                                              current);
 963 
 964         if (a_role > clone_role) {
 965             clone_role = a_role;
 966         }
 967     }
 968 
 969     pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role));
 970     return clone_role;
 971 }
 972 
 973 /*!
 974  * \internal
 975  * \brief Check whether a clone has an instance for every node
 976  *
 977  * \param[in] rsc        Clone to check
 978  * \param[in] scheduler  Scheduler data
 979  */
 980 bool
 981 pe__is_universal_clone(const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 982                        const pcmk_scheduler_t *scheduler)
 983 {
 984     if (pcmk__is_clone(rsc)) {
 985         clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
 986 
 987         if (clone_data->clone_max == g_list_length(scheduler->nodes)) {
 988             return TRUE;
 989         }
 990     }
 991     return FALSE;
 992 }
 993 
 994 bool
 995 pe__clone_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 996                       bool check_parent)
 997 {
 998     bool passes = FALSE;
 999     clone_variant_data_t *clone_data = NULL;
1000 
1001     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
1002         passes = true;
1003     } else {
1004         get_clone_variant_data(clone_data, rsc);
1005         passes = pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child),
1006                                    only_rsc, pcmk__str_star_matches);
1007 
1008         if (!passes) {
1009             for (const GList *iter = rsc->priv->children;
1010                  iter != NULL; iter = iter->next) {
1011 
1012                 const pcmk_resource_t *child_rsc = NULL;
1013 
1014                 child_rsc = (const pcmk_resource_t *) iter->data;
1015                 if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
1016                                                        false)) {
1017                     passes = true;
1018                     break;
1019                 }
1020             }
1021         }
1022     }
1023     return !passes;
1024 }
1025 
1026 const char *
1027 pe__clone_child_id(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1028 {
1029     clone_variant_data_t *clone_data = NULL;
1030     get_clone_variant_data(clone_data, rsc);
1031     return pcmk__xe_id(clone_data->xml_obj_child);
1032 }
1033 
1034 /*!
1035  * \internal
1036  * \brief Check whether a clone is ordered
1037  *
1038  * \param[in] clone  Clone resource to check
1039  *
1040  * \return true if clone is ordered, otherwise false
1041  */
1042 bool
1043 pe__clone_is_ordered(const pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
1044 {
1045     clone_variant_data_t *clone_data = NULL;
1046 
1047     get_clone_variant_data(clone_data, clone);
1048     return pcmk_is_set(clone_data->flags, pcmk__clone_ordered);
1049 }
1050 
1051 /*!
1052  * \internal
1053  * \brief Set a clone flag
1054  *
1055  * \param[in,out] clone  Clone resource to set flag for
1056  * \param[in]     flag   Clone flag to set
1057  *
1058  * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
1059  *         already set or pcmk_rc_already if it was)
1060  */
1061 int
1062 pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
1063 {
1064     clone_variant_data_t *clone_data = NULL;
1065 
1066     get_clone_variant_data(clone_data, clone);
1067     if (pcmk_is_set(clone_data->flags, flag)) {
1068         return pcmk_rc_already;
1069     }
1070     clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
1071                                            "Clone", clone->id,
1072                                            clone_data->flags, flag, "flag");
1073     return pcmk_rc_ok;
1074 }
1075 
1076 /*!
1077  * \internal
1078  * \brief Check whether a clone flag is set
1079  *
1080  * \param[in] group  Clone resource to check
1081  * \param[in] flags  Flag or flags to check
1082  *
1083  * \return \c true if all \p flags are set for \p clone, otherwise \c false
1084  */
1085 bool
1086 pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags)
     /* [previous][next][first][last][top][bottom][index][help] */
1087 {
1088     clone_variant_data_t *clone_data = NULL;
1089 
1090     get_clone_variant_data(clone_data, clone);
1091     pcmk__assert(clone_data != NULL);
1092 
1093     return pcmk_all_flags_set(clone_data->flags, flags);
1094 }
1095 
1096 /*!
1097  * \internal
1098  * \brief Create pseudo-actions needed for promotable clones
1099  *
1100  * \param[in,out] clone          Promotable clone to create actions for
1101  * \param[in]     any_promoting  Whether any instances will be promoted
1102  * \param[in]     any_demoting   Whether any instance will be demoted
1103  */
1104 void
1105 pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting,
     /* [previous][next][first][last][top][bottom][index][help] */
1106                                  bool any_demoting)
1107 {
1108     pcmk_action_t *action = NULL;
1109     pcmk_action_t *action_complete = NULL;
1110     clone_variant_data_t *clone_data = NULL;
1111 
1112     get_clone_variant_data(clone_data, clone);
1113 
1114     // Create a "promote" action for the clone itself
1115     action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE,
1116                                        !any_promoting, true);
1117 
1118     // Create a "promoted" action for when all promotions are done
1119     action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED,
1120                                                 !any_promoting, true);
1121     action_complete->priority = PCMK_SCORE_INFINITY;
1122 
1123     // Create notification pseudo-actions for promotion
1124     if (clone_data->promote_notify == NULL) {
1125         clone_data->promote_notify = pe__action_notif_pseudo_ops(clone,
1126                                                                  PCMK_ACTION_PROMOTE,
1127                                                                  action,
1128                                                                  action_complete);
1129     }
1130 
1131     // Create a "demote" action for the clone itself
1132     action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE,
1133                                        !any_demoting, true);
1134 
1135     // Create a "demoted" action for when all demotions are done
1136     action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED,
1137                                                 !any_demoting, true);
1138     action_complete->priority = PCMK_SCORE_INFINITY;
1139 
1140     // Create notification pseudo-actions for demotion
1141     if (clone_data->demote_notify == NULL) {
1142         clone_data->demote_notify = pe__action_notif_pseudo_ops(clone,
1143                                                                 PCMK_ACTION_DEMOTE,
1144                                                                 action,
1145                                                                 action_complete);
1146 
1147         if (clone_data->promote_notify != NULL) {
1148             order_actions(clone_data->stop_notify->post_done,
1149                           clone_data->promote_notify->pre, pcmk__ar_ordered);
1150             order_actions(clone_data->start_notify->post_done,
1151                           clone_data->promote_notify->pre, pcmk__ar_ordered);
1152             order_actions(clone_data->demote_notify->post_done,
1153                           clone_data->promote_notify->pre, pcmk__ar_ordered);
1154             order_actions(clone_data->demote_notify->post_done,
1155                           clone_data->start_notify->pre, pcmk__ar_ordered);
1156             order_actions(clone_data->demote_notify->post_done,
1157                           clone_data->stop_notify->pre, pcmk__ar_ordered);
1158         }
1159     }
1160 }
1161 
1162 /*!
1163  * \internal
1164  * \brief Create all notification data and actions for a clone
1165  *
1166  * \param[in,out] clone  Clone to create notifications for
1167  */
1168 void
1169 pe__create_clone_notifications(pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
1170 {
1171     clone_variant_data_t *clone_data = NULL;
1172 
1173     get_clone_variant_data(clone_data, clone);
1174 
1175     pe__create_action_notifications(clone, clone_data->start_notify);
1176     pe__create_action_notifications(clone, clone_data->stop_notify);
1177     pe__create_action_notifications(clone, clone_data->promote_notify);
1178     pe__create_action_notifications(clone, clone_data->demote_notify);
1179 }
1180 
1181 /*!
1182  * \internal
1183  * \brief Free all notification data for a clone
1184  *
1185  * \param[in,out] clone  Clone to free notification data for
1186  */
1187 void
1188 pe__free_clone_notification_data(pcmk_resource_t *clone)
     /* [previous][next][first][last][top][bottom][index][help] */
1189 {
1190     clone_variant_data_t *clone_data = NULL;
1191 
1192     get_clone_variant_data(clone_data, clone);
1193 
1194     pe__free_action_notification_data(clone_data->demote_notify);
1195     clone_data->demote_notify = NULL;
1196 
1197     pe__free_action_notification_data(clone_data->stop_notify);
1198     clone_data->stop_notify = NULL;
1199 
1200     pe__free_action_notification_data(clone_data->start_notify);
1201     clone_data->start_notify = NULL;
1202 
1203     pe__free_action_notification_data(clone_data->promote_notify);
1204     clone_data->promote_notify = NULL;
1205 }
1206 
1207 /*!
1208  * \internal
1209  * \brief Create pseudo-actions for clone start/stop notifications
1210  *
1211  * \param[in,out] clone    Clone to create pseudo-actions for
1212  * \param[in,out] start    Start action for \p clone
1213  * \param[in,out] stop     Stop action for \p clone
1214  * \param[in,out] started  Started action for \p clone
1215  * \param[in,out] stopped  Stopped action for \p clone
1216  */
1217 void
1218 pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
     /* [previous][next][first][last][top][bottom][index][help] */
1219                                   pcmk_action_t *start, pcmk_action_t *started,
1220                                   pcmk_action_t *stop, pcmk_action_t *stopped)
1221 {
1222     clone_variant_data_t *clone_data = NULL;
1223 
1224     get_clone_variant_data(clone_data, clone);
1225 
1226     if (clone_data->start_notify == NULL) {
1227         clone_data->start_notify = pe__action_notif_pseudo_ops(clone,
1228                                                                PCMK_ACTION_START,
1229                                                                start, started);
1230     }
1231 
1232     if (clone_data->stop_notify == NULL) {
1233         clone_data->stop_notify = pe__action_notif_pseudo_ops(clone,
1234                                                               PCMK_ACTION_STOP,
1235                                                               stop, stopped);
1236         if ((clone_data->start_notify != NULL)
1237             && (clone_data->stop_notify != NULL)) {
1238             order_actions(clone_data->stop_notify->post_done,
1239                           clone_data->start_notify->pre, pcmk__ar_ordered);
1240         }
1241     }
1242 }
1243 
1244 /*!
1245  * \internal
1246  * \brief Get maximum clone resource instances per node
1247  *
1248  * \param[in] rsc  Clone resource to check
1249  *
1250  * \return Maximum number of \p rsc instances that can be active on one node
1251  */
1252 unsigned int
1253 pe__clone_max_per_node(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1254 {
1255     const clone_variant_data_t *clone_data = NULL;
1256 
1257     get_clone_variant_data(clone_data, rsc);
1258     return clone_data->clone_node_max;
1259 }

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