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. find_clone_instance
  11. pe__create_clone_child
  12. unpack_meta_int
  13. clone_unpack
  14. clone_active
  15. short_print
  16. configured_role_str
  17. configured_role
  18. clone_print_xml
  19. is_set_recursive
  20. clone_print
  21. PCMK__OUTPUT_ARGS
  22. PCMK__OUTPUT_ARGS
  23. clone_free
  24. clone_resource_state
  25. pe__is_universal_clone
  26. pe__clone_is_filtered
  27. pe__clone_child_id
  28. pe__clone_is_ordered
  29. pe__set_clone_flag
  30. pe__clone_flag_is_set
  31. pe__create_promotable_pseudo_ops
  32. pe__create_clone_notifications
  33. pe__free_clone_notification_data
  34. pe__create_clone_notif_pseudo_ops
  35. pe__clone_max_per_node

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

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