root/lib/pengine/clone.c

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

DEFINITIONS

This source file includes following definitions.
  1. sorted_hash_table_values
  2. nodes_with_status
  3. node_list_to_str
  4. clone_header
  5. pe__force_anon
  6. find_clone_instance
  7. pe__create_clone_child
  8. clone_unpack
  9. clone_active
  10. short_print
  11. configured_role_str
  12. configured_role
  13. clone_print_xml
  14. is_set_recursive
  15. clone_print
  16. PCMK__OUTPUT_ARGS
  17. PCMK__OUTPUT_ARGS
  18. clone_free
  19. clone_resource_state
  20. pe__is_universal_clone
  21. pe__clone_is_filtered
  22. pe__clone_child_id

   1 /*
   2  * Copyright 2004-2022 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/msg_xml.h>
  19 #include <crm/common/output.h>
  20 #include <crm/common/xml_internal.h>
  21 
  22 #define VARIANT_CLONE 1
  23 #include "./variant.h"
  24 
  25 #ifdef PCMK__COMPAT_2_0
  26 #define PROMOTED_INSTANCES   RSC_ROLE_PROMOTED_LEGACY_S "s"
  27 #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_LEGACY_S "s"
  28 #else
  29 #define PROMOTED_INSTANCES   RSC_ROLE_PROMOTED_S
  30 #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_S
  31 #endif
  32 
  33 static GList *
  34 sorted_hash_table_values(GHashTable *table)
     /* [previous][next][first][last][top][bottom][index][help] */
  35 {
  36     GList *retval = NULL;
  37     GHashTableIter iter;
  38     gpointer key, value;
  39 
  40     g_hash_table_iter_init(&iter, table);
  41     while (g_hash_table_iter_next(&iter, &key, &value)) {
  42         if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
  43             retval = g_list_prepend(retval, (char *) value);
  44         }
  45     }
  46 
  47     retval = g_list_sort(retval, (GCompareFunc) strcmp);
  48     return retval;
  49 }
  50 
  51 static GList *
  52 nodes_with_status(GHashTable *table, const char *status)
     /* [previous][next][first][last][top][bottom][index][help] */
  53 {
  54     GList *retval = NULL;
  55     GHashTableIter iter;
  56     gpointer key, value;
  57 
  58     g_hash_table_iter_init(&iter, table);
  59     while (g_hash_table_iter_next(&iter, &key, &value)) {
  60         if (!strcmp((char *) value, status)) {
  61             retval = g_list_prepend(retval, key);
  62         }
  63     }
  64 
  65     retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
  66     return retval;
  67 }
  68 
  69 static char *
  70 node_list_to_str(GList *list)
     /* [previous][next][first][last][top][bottom][index][help] */
  71 {
  72     char *retval = NULL;
  73     size_t len = 0;
  74 
  75     for (GList *iter = list; iter != NULL; iter = iter->next) {
  76         pcmk__add_word(&retval, &len, (char *) iter->data);
  77     }
  78 
  79     return retval;
  80 }
  81 
  82 static void
  83 clone_header(pcmk__output_t *out, int *rc, pe_resource_t *rsc, clone_variant_data_t *clone_data)
     /* [previous][next][first][last][top][bottom][index][help] */
  84 {
  85     char *attrs = NULL;
  86     size_t len = 0;
  87 
  88     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
  89         pcmk__add_separated_word(&attrs, &len, "promotable", ", ");
  90     }
  91 
  92     if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
  93         pcmk__add_separated_word(&attrs, &len, "unique", ", ");
  94     }
  95 
  96     if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
  97         pcmk__add_separated_word(&attrs, &len, "unmanaged", ", ");
  98     }
  99 
 100     if (pe__resource_is_disabled(rsc)) {
 101         pcmk__add_separated_word(&attrs, &len, "disabled", ", ");
 102     }
 103 
 104     if (attrs) {
 105         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)",
 106                              rsc->id, ID(clone_data->xml_obj_child),
 107                                  attrs);
 108         free(attrs);
 109     } else {
 110         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]",
 111                                  rsc->id, ID(clone_data->xml_obj_child))
 112     }
 113 }
 114 
 115 void
 116 pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid,
     /* [previous][next][first][last][top][bottom][index][help] */
 117                pe_working_set_t *data_set)
 118 {
 119     if (pe_rsc_is_clone(rsc)) {
 120         clone_variant_data_t *clone_data = NULL;
 121 
 122         get_clone_variant_data(clone_data, rsc);
 123 
 124         pe_warn("Ignoring " XML_RSC_ATTR_UNIQUE " for %s because %s resources "
 125                 "such as %s can be used only as anonymous clones",
 126                 rsc->id, standard, rid);
 127 
 128         clone_data->clone_node_max = 1;
 129         clone_data->clone_max = QB_MIN(clone_data->clone_max,
 130                                        g_list_length(data_set->nodes));
 131     }
 132 }
 133 
 134 pe_resource_t *
 135 find_clone_instance(pe_resource_t * rsc, const char *sub_id, pe_working_set_t * data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 136 {
 137     char *child_id = NULL;
 138     pe_resource_t *child = NULL;
 139     const char *child_base = NULL;
 140     clone_variant_data_t *clone_data = NULL;
 141 
 142     get_clone_variant_data(clone_data, rsc);
 143 
 144     child_base = ID(clone_data->xml_obj_child);
 145     child_id = crm_strdup_printf("%s:%s", child_base, sub_id);
 146     child = pe_find_resource(rsc->children, child_id);
 147 
 148     free(child_id);
 149     return child;
 150 }
 151 
 152 pe_resource_t *
 153 pe__create_clone_child(pe_resource_t *rsc, pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 154 {
 155     gboolean as_orphan = FALSE;
 156     char *inc_num = NULL;
 157     char *inc_max = NULL;
 158     pe_resource_t *child_rsc = NULL;
 159     xmlNode *child_copy = NULL;
 160     clone_variant_data_t *clone_data = NULL;
 161 
 162     get_clone_variant_data(clone_data, rsc);
 163 
 164     CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
 165 
 166     if (clone_data->total_clones >= clone_data->clone_max) {
 167         // If we've already used all available instances, this is an orphan
 168         as_orphan = TRUE;
 169     }
 170 
 171     // Allocate instance numbers in numerical order (starting at 0)
 172     inc_num = pcmk__itoa(clone_data->total_clones);
 173     inc_max = pcmk__itoa(clone_data->clone_max);
 174 
 175     child_copy = copy_xml(clone_data->xml_obj_child);
 176 
 177     crm_xml_add(child_copy, XML_RSC_ATTR_INCARNATION, inc_num);
 178 
 179     if (common_unpack(child_copy, &child_rsc, rsc, data_set) == FALSE) {
 180         pe_err("Failed unpacking resource %s", crm_element_value(child_copy, XML_ATTR_ID));
 181         child_rsc = NULL;
 182         goto bail;
 183     }
 184 /*  child_rsc->globally_unique = rsc->globally_unique; */
 185 
 186     CRM_ASSERT(child_rsc);
 187     clone_data->total_clones += 1;
 188     pe_rsc_trace(child_rsc, "Setting clone attributes for: %s", child_rsc->id);
 189     rsc->children = g_list_append(rsc->children, child_rsc);
 190     if (as_orphan) {
 191         pe__set_resource_flags_recursive(child_rsc, pe_rsc_orphan);
 192     }
 193 
 194     add_hash_param(child_rsc->meta, XML_RSC_ATTR_INCARNATION_MAX, inc_max);
 195     pe_rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
 196 
 197   bail:
 198     free(inc_num);
 199     free(inc_max);
 200 
 201     return child_rsc;
 202 }
 203 
 204 gboolean
 205 clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 206 {
 207     int lpc = 0;
 208     xmlNode *a_child = NULL;
 209     xmlNode *xml_obj = rsc->xml;
 210     clone_variant_data_t *clone_data = NULL;
 211 
 212     const char *ordered = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_ORDERED);
 213     const char *max_clones = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_MAX);
 214     const char *max_clones_node = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_NODEMAX);
 215 
 216     pe_rsc_trace(rsc, "Processing resource %s...", rsc->id);
 217 
 218     clone_data = calloc(1, sizeof(clone_variant_data_t));
 219     rsc->variant_opaque = clone_data;
 220 
 221     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 222         const char *promoted_max = NULL;
 223         const char *promoted_node_max = NULL;
 224 
 225         promoted_max = g_hash_table_lookup(rsc->meta,
 226                                            XML_RSC_ATTR_PROMOTED_MAX);
 227         if (promoted_max == NULL) {
 228             // @COMPAT deprecated since 2.0.0
 229             promoted_max = g_hash_table_lookup(rsc->meta,
 230                                                PCMK_XE_PROMOTED_MAX_LEGACY);
 231         }
 232 
 233         promoted_node_max = g_hash_table_lookup(rsc->meta,
 234                                                 XML_RSC_ATTR_PROMOTED_NODEMAX);
 235         if (promoted_node_max == NULL) {
 236             // @COMPAT deprecated since 2.0.0
 237             promoted_node_max = g_hash_table_lookup(rsc->meta,
 238                                                     PCMK_XE_PROMOTED_NODE_MAX_LEGACY);
 239         }
 240 
 241         // Use 1 as default but 0 for minimum and invalid
 242         if (promoted_max == NULL) {
 243             clone_data->promoted_max = 1;
 244         } else {
 245             pcmk__scan_min_int(promoted_max, &(clone_data->promoted_max), 0);
 246         }
 247 
 248         // Use 1 as default but 0 for minimum and invalid
 249         if (promoted_node_max == NULL) {
 250             clone_data->promoted_node_max = 1;
 251         } else {
 252             pcmk__scan_min_int(promoted_node_max,
 253                                &(clone_data->promoted_node_max), 0);
 254         }
 255     }
 256 
 257     // Implied by calloc()
 258     /* clone_data->xml_obj_child = NULL; */
 259 
 260     // Use 1 as default but 0 for minimum and invalid
 261     if (max_clones_node == NULL) {
 262         clone_data->clone_node_max = 1;
 263     } else {
 264         pcmk__scan_min_int(max_clones_node, &(clone_data->clone_node_max), 0);
 265     }
 266 
 267     /* Use number of nodes (but always at least 1, which is handy for crm_verify
 268      * for a CIB without nodes) as default, but 0 for minimum and invalid
 269      */
 270     if (max_clones == NULL) {
 271         clone_data->clone_max = QB_MAX(1, g_list_length(data_set->nodes));
 272     } else {
 273         pcmk__scan_min_int(max_clones, &(clone_data->clone_max), 0);
 274     }
 275 
 276     clone_data->ordered = crm_is_true(ordered);
 277 
 278     if ((rsc->flags & pe_rsc_unique) == 0 && clone_data->clone_node_max > 1) {
 279         pcmk__config_err("Ignoring " XML_RSC_ATTR_PROMOTED_MAX " for %s "
 280                          "because anonymous clones support only one instance "
 281                          "per node", rsc->id);
 282         clone_data->clone_node_max = 1;
 283     }
 284 
 285     pe_rsc_trace(rsc, "Options for %s", rsc->id);
 286     pe_rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
 287     pe_rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
 288     pe_rsc_trace(rsc, "\tClone is unique: %s",
 289                  pe__rsc_bool_str(rsc, pe_rsc_unique));
 290     pe_rsc_trace(rsc, "\tClone is promotable: %s",
 291                  pe__rsc_bool_str(rsc, pe_rsc_promotable));
 292 
 293     // Clones may contain a single group or primitive
 294     for (a_child = pcmk__xe_first_child(xml_obj); a_child != NULL;
 295          a_child = pcmk__xe_next(a_child)) {
 296 
 297         if (pcmk__str_any_of((const char *)a_child->name, XML_CIB_TAG_RESOURCE, XML_CIB_TAG_GROUP, NULL)) {
 298             clone_data->xml_obj_child = a_child;
 299             break;
 300         }
 301     }
 302 
 303     if (clone_data->xml_obj_child == NULL) {
 304         pcmk__config_err("%s has nothing to clone", rsc->id);
 305         return FALSE;
 306     }
 307 
 308     /*
 309      * Make clones ever so slightly sticky by default
 310      *
 311      * This helps ensure clone instances are not shuffled around the cluster
 312      * for no benefit in situations when pre-allocation is not appropriate
 313      */
 314     if (g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_STICKINESS) == NULL) {
 315         add_hash_param(rsc->meta, XML_RSC_ATTR_STICKINESS, "1");
 316     }
 317 
 318     /* This ensures that the globally-unique value always exists for children to
 319      * inherit when being unpacked, as well as in resource agents' environment.
 320      */
 321     add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE,
 322                    pe__rsc_bool_str(rsc, pe_rsc_unique));
 323 
 324     if (clone_data->clone_max <= 0) {
 325         /* Create one child instance so that unpack_find_resource() will hook up
 326          * any orphans up to the parent correctly.
 327          */
 328         if (pe__create_clone_child(rsc, data_set) == NULL) {
 329             return FALSE;
 330         }
 331 
 332     } else {
 333         // Create a child instance for each available instance number
 334         for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
 335             if (pe__create_clone_child(rsc, data_set) == NULL) {
 336                 return FALSE;
 337             }
 338         }
 339     }
 340 
 341     pe_rsc_trace(rsc, "Added %d children to resource %s...", clone_data->clone_max, rsc->id);
 342     return TRUE;
 343 }
 344 
 345 gboolean
 346 clone_active(pe_resource_t * rsc, gboolean all)
     /* [previous][next][first][last][top][bottom][index][help] */
 347 {
 348     GList *gIter = rsc->children;
 349 
 350     for (; gIter != NULL; gIter = gIter->next) {
 351         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 352         gboolean child_active = child_rsc->fns->active(child_rsc, all);
 353 
 354         if (all == FALSE && child_active) {
 355             return TRUE;
 356         } else if (all && child_active == FALSE) {
 357             return FALSE;
 358         }
 359     }
 360 
 361     if (all) {
 362         return TRUE;
 363     } else {
 364         return FALSE;
 365     }
 366 }
 367 
 368 static void
 369 short_print(char *list, const char *prefix, const char *type, const char *suffix, long options, void *print_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 370 {
 371     if(suffix == NULL) {
 372         suffix = "";
 373     }
 374 
 375     if (list) {
 376         if (options & pe_print_html) {
 377             status_print("<li>");
 378         }
 379         status_print("%s%s: [ %s ]%s", prefix, type, list, suffix);
 380 
 381         if (options & pe_print_html) {
 382             status_print("</li>\n");
 383 
 384         } else if (options & pe_print_suppres_nl) {
 385             /* nothing */
 386         } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) {
 387             status_print("\n");
 388         }
 389 
 390     }
 391 }
 392 
 393 static const char *
 394 configured_role_str(pe_resource_t * rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 395 {
 396     const char *target_role = g_hash_table_lookup(rsc->meta,
 397                                                   XML_RSC_ATTR_TARGET_ROLE);
 398 
 399     if ((target_role == NULL) && rsc->children && rsc->children->data) {
 400         target_role = g_hash_table_lookup(((pe_resource_t*)rsc->children->data)->meta,
 401                                           XML_RSC_ATTR_TARGET_ROLE);
 402     }
 403     return target_role;
 404 }
 405 
 406 static enum rsc_role_e
 407 configured_role(pe_resource_t * rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 408 {
 409     const char *target_role = configured_role_str(rsc);
 410 
 411     if (target_role) {
 412         return text2role(target_role);
 413     }
 414     return RSC_ROLE_UNKNOWN;
 415 }
 416 
 417 static void
 418 clone_print_xml(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 419 {
 420     char *child_text = crm_strdup_printf("%s    ", pre_text);
 421     const char *target_role = configured_role_str(rsc);
 422     GList *gIter = rsc->children;
 423 
 424     status_print("%s<clone ", pre_text);
 425     status_print("id=\"%s\" ", rsc->id);
 426     status_print("multi_state=\"%s\" ",
 427                  pe__rsc_bool_str(rsc, pe_rsc_promotable));
 428     status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique));
 429     status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed));
 430     status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed));
 431     status_print("failure_ignored=\"%s\" ",
 432                  pe__rsc_bool_str(rsc, pe_rsc_failure_ignored));
 433     if (target_role) {
 434         status_print("target_role=\"%s\" ", target_role);
 435     }
 436     status_print(">\n");
 437 
 438     for (; gIter != NULL; gIter = gIter->next) {
 439         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 440 
 441         child_rsc->fns->print(child_rsc, child_text, options, print_data);
 442     }
 443 
 444     status_print("%s</clone>\n", pre_text);
 445     free(child_text);
 446 }
 447 
 448 bool is_set_recursive(pe_resource_t * rsc, long long flag, bool any)
     /* [previous][next][first][last][top][bottom][index][help] */
 449 {
 450     GList *gIter;
 451     bool all = !any;
 452 
 453     if (pcmk_is_set(rsc->flags, flag)) {
 454         if(any) {
 455             return TRUE;
 456         }
 457     } else if(all) {
 458         return FALSE;
 459     }
 460 
 461     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
 462         if(is_set_recursive(gIter->data, flag, any)) {
 463             if(any) {
 464                 return TRUE;
 465             }
 466 
 467         } else if(all) {
 468             return FALSE;
 469         }
 470     }
 471 
 472     if(all) {
 473         return TRUE;
 474     }
 475     return FALSE;
 476 }
 477 
 478 void
 479 clone_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 480 {
 481     char *list_text = NULL;
 482     char *child_text = NULL;
 483     char *stopped_list = NULL;
 484     size_t list_text_len = 0;
 485     size_t stopped_list_len = 0;
 486 
 487     GList *promoted_list = NULL;
 488     GList *started_list = NULL;
 489     GList *gIter = rsc->children;
 490 
 491     clone_variant_data_t *clone_data = NULL;
 492     int active_instances = 0;
 493 
 494     if (pre_text == NULL) {
 495         pre_text = " ";
 496     }
 497 
 498     if (options & pe_print_xml) {
 499         clone_print_xml(rsc, pre_text, options, print_data);
 500         return;
 501     }
 502 
 503     get_clone_variant_data(clone_data, rsc);
 504 
 505     child_text = crm_strdup_printf("%s    ", pre_text);
 506 
 507     status_print("%sClone Set: %s [%s]%s%s%s",
 508                  pre_text ? pre_text : "", rsc->id, ID(clone_data->xml_obj_child),
 509                  pcmk_is_set(rsc->flags, pe_rsc_promotable)? " (promotable)" : "",
 510                  pcmk_is_set(rsc->flags, pe_rsc_unique)? " (unique)" : "",
 511                  pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : " (unmanaged)");
 512 
 513     if (options & pe_print_html) {
 514         status_print("\n<ul>\n");
 515 
 516     } else if ((options & pe_print_log) == 0) {
 517         status_print("\n");
 518     }
 519 
 520     for (; gIter != NULL; gIter = gIter->next) {
 521         gboolean print_full = FALSE;
 522         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 523         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 524 
 525         if (options & pe_print_clone_details) {
 526             print_full = TRUE;
 527         }
 528 
 529         if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
 530             // Print individual instance when unique (except stopped orphans)
 531             if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
 532                 print_full = TRUE;
 533             }
 534 
 535         // Everything else in this block is for anonymous clones
 536 
 537         } else if (pcmk_is_set(options, pe_print_pending)
 538                    && (child_rsc->pending_task != NULL)
 539                    && strcmp(child_rsc->pending_task, "probe")) {
 540             // Print individual instance when non-probe action is pending
 541             print_full = TRUE;
 542 
 543         } else if (partially_active == FALSE) {
 544             // List stopped instances when requested (except orphans)
 545             if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan)
 546                 && !pcmk_is_set(options, pe_print_clone_active)) {
 547                 pcmk__add_word(&stopped_list, &stopped_list_len, child_rsc->id);
 548             }
 549 
 550         } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE)
 551                    || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE
 552                    || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) {
 553 
 554             // Print individual instance when active orphaned/unmanaged/failed
 555             print_full = TRUE;
 556 
 557         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
 558             // Instance of fully active anonymous clone
 559 
 560             pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE);
 561 
 562             if (location) {
 563                 // Instance is active on a single node
 564 
 565                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 566 
 567                 if (location->details->online == FALSE && location->details->unclean) {
 568                     print_full = TRUE;
 569 
 570                 } else if (a_role > RSC_ROLE_UNPROMOTED) {
 571                     promoted_list = g_list_append(promoted_list, location);
 572 
 573                 } else {
 574                     started_list = g_list_append(started_list, location);
 575                 }
 576 
 577             } else {
 578                 /* uncolocated group - bleh */
 579                 print_full = TRUE;
 580             }
 581 
 582         } else {
 583             // Instance of partially active anonymous clone
 584             print_full = TRUE;
 585         }
 586 
 587         if (print_full) {
 588             if (options & pe_print_html) {
 589                 status_print("<li>\n");
 590             }
 591             child_rsc->fns->print(child_rsc, child_text, options, print_data);
 592             if (options & pe_print_html) {
 593                 status_print("</li>\n");
 594             }
 595         }
 596     }
 597 
 598     /* Promoted */
 599     promoted_list = g_list_sort(promoted_list, sort_node_uname);
 600     for (gIter = promoted_list; gIter; gIter = gIter->next) {
 601         pe_node_t *host = gIter->data;
 602 
 603         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
 604         active_instances++;
 605     }
 606 
 607     short_print(list_text, child_text, PROMOTED_INSTANCES, NULL, options,
 608                 print_data);
 609     g_list_free(promoted_list);
 610     free(list_text);
 611     list_text = NULL;
 612     list_text_len = 0;
 613 
 614     /* Started/Unpromoted */
 615     started_list = g_list_sort(started_list, sort_node_uname);
 616     for (gIter = started_list; gIter; gIter = gIter->next) {
 617         pe_node_t *host = gIter->data;
 618 
 619         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
 620         active_instances++;
 621     }
 622 
 623     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 624         enum rsc_role_e role = configured_role(rsc);
 625 
 626         if (role == RSC_ROLE_UNPROMOTED) {
 627             short_print(list_text, child_text,
 628                         UNPROMOTED_INSTANCES " (target-role)", NULL, options,
 629                         print_data);
 630         } else {
 631             short_print(list_text, child_text, UNPROMOTED_INSTANCES, NULL,
 632                         options, print_data);
 633         }
 634 
 635     } else {
 636         short_print(list_text, child_text, "Started", NULL, options, print_data);
 637     }
 638 
 639     g_list_free(started_list);
 640     free(list_text);
 641     list_text = NULL;
 642     list_text_len = 0;
 643 
 644     if (!pcmk_is_set(options, pe_print_clone_active)) {
 645         const char *state = "Stopped";
 646         enum rsc_role_e role = configured_role(rsc);
 647 
 648         if (role == RSC_ROLE_STOPPED) {
 649             state = "Stopped (disabled)";
 650         }
 651 
 652         if (!pcmk_is_set(rsc->flags, pe_rsc_unique)
 653             && (clone_data->clone_max > active_instances)) {
 654 
 655             GList *nIter;
 656             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 657 
 658             /* Custom stopped list for non-unique clones */
 659             free(stopped_list);
 660             stopped_list = NULL;
 661             stopped_list_len = 0;
 662 
 663             if (list == NULL) {
 664                 /* Clusters with symmetrical=false haven't calculated allowed_nodes yet
 665                  * If we've not probed for them yet, the Stopped list will be empty
 666                  */
 667                 list = g_hash_table_get_values(rsc->known_on);
 668             }
 669 
 670             list = g_list_sort(list, sort_node_uname);
 671             for (nIter = list; nIter != NULL; nIter = nIter->next) {
 672                 pe_node_t *node = (pe_node_t *)nIter->data;
 673 
 674                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL) {
 675                     pcmk__add_word(&stopped_list, &stopped_list_len,
 676                                    node->details->uname);
 677                 }
 678             }
 679             g_list_free(list);
 680         }
 681 
 682         short_print(stopped_list, child_text, state, NULL, options, print_data);
 683         free(stopped_list);
 684     }
 685 
 686     if (options & pe_print_html) {
 687         status_print("</ul>\n");
 688     }
 689 
 690     free(child_text);
 691 }
 692 
 693 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *")
     /* [previous][next][first][last][top][bottom][index][help] */
 694 int
 695 pe__clone_xml(pcmk__output_t *out, va_list args)
 696 {
 697     uint32_t show_opts = va_arg(args, uint32_t);
 698     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
 699     GList *only_node = va_arg(args, GList *);
 700     GList *only_rsc = va_arg(args, GList *);
 701 
 702     GList *gIter = rsc->children;
 703     GList *all = NULL;
 704     int rc = pcmk_rc_no_output;
 705     gboolean printed_header = FALSE;
 706     gboolean print_everything = TRUE;
 707 
 708     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
 709         return rc;
 710     }
 711 
 712     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
 713                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 714 
 715     all = g_list_prepend(all, (gpointer) "*");
 716 
 717     for (; gIter != NULL; gIter = gIter->next) {
 718         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 719 
 720         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
 721             continue;
 722         }
 723 
 724         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
 725             continue;
 726         }
 727 
 728         if (!printed_header) {
 729             printed_header = TRUE;
 730 
 731             rc = pe__name_and_nvpairs_xml(out, true, "clone", 8,
 732                     "id", rsc->id,
 733                     "multi_state", pe__rsc_bool_str(rsc, pe_rsc_promotable),
 734                     "unique", pe__rsc_bool_str(rsc, pe_rsc_unique),
 735                     "managed", pe__rsc_bool_str(rsc, pe_rsc_managed),
 736                     "disabled", pcmk__btoa(pe__resource_is_disabled(rsc)),
 737                     "failed", pe__rsc_bool_str(rsc, pe_rsc_failed),
 738                     "failure_ignored", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored),
 739                     "target_role", configured_role_str(rsc));
 740             CRM_ASSERT(rc == pcmk_rc_ok);
 741         }
 742 
 743         out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
 744                      child_rsc, only_node, all);
 745     }
 746 
 747     if (printed_header) {
 748         pcmk__output_xml_pop_parent(out);
 749     }
 750 
 751     g_list_free(all);
 752     return rc;
 753 }
 754 
 755 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *")
     /* [previous][next][first][last][top][bottom][index][help] */
 756 int
 757 pe__clone_default(pcmk__output_t *out, va_list args)
 758 {
 759     uint32_t show_opts = va_arg(args, uint32_t);
 760     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
 761     GList *only_node = va_arg(args, GList *);
 762     GList *only_rsc = va_arg(args, GList *);
 763 
 764     GHashTable *stopped = NULL;
 765 
 766     char *list_text = NULL;
 767     size_t list_text_len = 0;
 768 
 769     GList *promoted_list = NULL;
 770     GList *started_list = NULL;
 771     GList *gIter = rsc->children;
 772 
 773     clone_variant_data_t *clone_data = NULL;
 774     int active_instances = 0;
 775     int rc = pcmk_rc_no_output;
 776     gboolean print_everything = TRUE;
 777 
 778     get_clone_variant_data(clone_data, rsc);
 779 
 780     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
 781         return rc;
 782     }
 783 
 784     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
 785                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 786 
 787     for (; gIter != NULL; gIter = gIter->next) {
 788         gboolean print_full = FALSE;
 789         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 790         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 791 
 792         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
 793             continue;
 794         }
 795 
 796         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
 797             continue;
 798         }
 799 
 800         if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
 801             print_full = TRUE;
 802         }
 803 
 804         if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
 805             // Print individual instance when unique (except stopped orphans)
 806             if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
 807                 print_full = TRUE;
 808             }
 809 
 810         // Everything else in this block is for anonymous clones
 811 
 812         } else if (pcmk_is_set(show_opts, pcmk_show_pending)
 813                    && (child_rsc->pending_task != NULL)
 814                    && strcmp(child_rsc->pending_task, "probe")) {
 815             // Print individual instance when non-probe action is pending
 816             print_full = TRUE;
 817 
 818         } else if (partially_active == FALSE) {
 819             // List stopped instances when requested (except orphans)
 820             if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan)
 821                 && !pcmk_is_set(show_opts, pcmk_show_clone_detail)
 822                 && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
 823                 if (stopped == NULL) {
 824                     stopped = pcmk__strkey_table(free, free);
 825                 }
 826                 g_hash_table_insert(stopped, strdup(child_rsc->id), strdup("Stopped"));
 827             }
 828 
 829         } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE)
 830                    || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE
 831                    || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) {
 832 
 833             // Print individual instance when active orphaned/unmanaged/failed
 834             print_full = TRUE;
 835 
 836         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
 837             // Instance of fully active anonymous clone
 838 
 839             pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE);
 840 
 841             if (location) {
 842                 // Instance is active on a single node
 843 
 844                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 845 
 846                 if (location->details->online == FALSE && location->details->unclean) {
 847                     print_full = TRUE;
 848 
 849                 } else if (a_role > RSC_ROLE_UNPROMOTED) {
 850                     promoted_list = g_list_append(promoted_list, location);
 851 
 852                 } else {
 853                     started_list = g_list_append(started_list, location);
 854                 }
 855 
 856             } else {
 857                 /* uncolocated group - bleh */
 858                 print_full = TRUE;
 859             }
 860 
 861         } else {
 862             // Instance of partially active anonymous clone
 863             print_full = TRUE;
 864         }
 865 
 866         if (print_full) {
 867             GList *all = NULL;
 868 
 869             clone_header(out, &rc, rsc, clone_data);
 870 
 871             /* Print every resource that's a child of this clone. */
 872             all = g_list_prepend(all, (gpointer) "*");
 873             out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
 874                          child_rsc, only_node, all);
 875             g_list_free(all);
 876         }
 877     }
 878 
 879     if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
 880         PCMK__OUTPUT_LIST_FOOTER(out, rc);
 881         return pcmk_rc_ok;
 882     }
 883 
 884     /* Promoted */
 885     promoted_list = g_list_sort(promoted_list, sort_node_uname);
 886     for (gIter = promoted_list; gIter; gIter = gIter->next) {
 887         pe_node_t *host = gIter->data;
 888 
 889         if (!pcmk__str_in_list(host->details->uname, only_node,
 890                                pcmk__str_star_matches|pcmk__str_casei)) {
 891             continue;
 892         }
 893 
 894         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
 895         active_instances++;
 896     }
 897     g_list_free(promoted_list);
 898 
 899     if (list_text != NULL) {
 900         clone_header(out, &rc, rsc, clone_data);
 901 
 902         out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]", list_text);
 903         free(list_text);
 904         list_text = NULL;
 905         list_text_len = 0;
 906     }
 907 
 908     /* Started/Unpromoted */
 909     started_list = g_list_sort(started_list, sort_node_uname);
 910     for (gIter = started_list; gIter; gIter = gIter->next) {
 911         pe_node_t *host = gIter->data;
 912 
 913         if (!pcmk__str_in_list(host->details->uname, only_node,
 914                                pcmk__str_star_matches|pcmk__str_casei)) {
 915             continue;
 916         }
 917 
 918         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
 919         active_instances++;
 920     }
 921     g_list_free(started_list);
 922 
 923     if (list_text != NULL) {
 924         clone_header(out, &rc, rsc, clone_data);
 925 
 926         if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
 927             enum rsc_role_e role = configured_role(rsc);
 928 
 929             if (role == RSC_ROLE_UNPROMOTED) {
 930                 out->list_item(out, NULL,
 931                                UNPROMOTED_INSTANCES " (target-role): [ %s ]",
 932                                list_text);
 933             } else {
 934                 out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]",
 935                                list_text);
 936             }
 937 
 938         } else {
 939             out->list_item(out, NULL, "Started: [ %s ]", list_text);
 940         }
 941         free(list_text);
 942         list_text = NULL;
 943         list_text_len = 0;
 944     }
 945 
 946     if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
 947         if (!pcmk_is_set(rsc->flags, pe_rsc_unique)
 948             && (clone_data->clone_max > active_instances)) {
 949 
 950             GList *nIter;
 951             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 952 
 953             /* Custom stopped table for non-unique clones */
 954             if (stopped != NULL) {
 955                 g_hash_table_destroy(stopped);
 956                 stopped = NULL;
 957             }
 958 
 959             if (list == NULL) {
 960                 /* Clusters with symmetrical=false haven't calculated allowed_nodes yet
 961                  * If we've not probed for them yet, the Stopped list will be empty
 962                  */
 963                 list = g_hash_table_get_values(rsc->known_on);
 964             }
 965 
 966             list = g_list_sort(list, sort_node_uname);
 967             for (nIter = list; nIter != NULL; nIter = nIter->next) {
 968                 pe_node_t *node = (pe_node_t *)nIter->data;
 969 
 970                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL &&
 971                     pcmk__str_in_list(node->details->uname, only_node,
 972                                       pcmk__str_star_matches|pcmk__str_casei)) {
 973                     xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname);
 974                     const char *state = "Stopped";
 975 
 976                     if (configured_role(rsc) == RSC_ROLE_STOPPED) {
 977                         state = "Stopped (disabled)";
 978                     }
 979 
 980                     if (stopped == NULL) {
 981                         stopped = pcmk__strkey_table(free, free);
 982                     }
 983                     if (probe_op != NULL) {
 984                         int rc;
 985 
 986                         pcmk__scan_min_int(crm_element_value(probe_op, XML_LRM_ATTR_RC), &rc, 0);
 987                         g_hash_table_insert(stopped, strdup(node->details->uname),
 988                                             crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc)));
 989                     } else {
 990                         g_hash_table_insert(stopped, strdup(node->details->uname),
 991                                             strdup(state));
 992                     }
 993                 }
 994             }
 995             g_list_free(list);
 996         }
 997 
 998         if (stopped != NULL) {
 999             GList *list = sorted_hash_table_values(stopped);
1000 
1001             clone_header(out, &rc, rsc, clone_data);
1002 
1003             for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
1004                 const char *status = status_iter->data;
1005                 GList *nodes = nodes_with_status(stopped, status);
1006                 char *str = node_list_to_str(nodes);
1007 
1008                 if (str != NULL) {
1009                     out->list_item(out, NULL, "%s: [ %s ]", status, str);
1010                     free(str);
1011                 }
1012 
1013                 g_list_free(nodes);
1014             }
1015 
1016             g_list_free(list);
1017             g_hash_table_destroy(stopped);
1018 
1019         /* If there are no instances of this clone (perhaps because there are no
1020          * nodes configured), simply output the clone header by itself.  This can
1021          * come up in PCS testing.
1022          */
1023         } else if (active_instances == 0) {
1024             clone_header(out, &rc, rsc, clone_data);
1025             PCMK__OUTPUT_LIST_FOOTER(out, rc);
1026             return rc;
1027         }
1028     }
1029 
1030     PCMK__OUTPUT_LIST_FOOTER(out, rc);
1031     return rc;
1032 }
1033 
1034 void
1035 clone_free(pe_resource_t * rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1036 {
1037     clone_variant_data_t *clone_data = NULL;
1038 
1039     get_clone_variant_data(clone_data, rsc);
1040 
1041     pe_rsc_trace(rsc, "Freeing %s", rsc->id);
1042 
1043     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
1044         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
1045 
1046         CRM_ASSERT(child_rsc);
1047         pe_rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
1048         free_xml(child_rsc->xml);
1049         child_rsc->xml = NULL;
1050         /* There could be a saved unexpanded xml */
1051         free_xml(child_rsc->orig_xml);
1052         child_rsc->orig_xml = NULL;
1053         child_rsc->fns->free(child_rsc);
1054     }
1055 
1056     g_list_free(rsc->children);
1057 
1058     if (clone_data) {
1059         CRM_ASSERT(clone_data->demote_notify == NULL);
1060         CRM_ASSERT(clone_data->stop_notify == NULL);
1061         CRM_ASSERT(clone_data->start_notify == NULL);
1062         CRM_ASSERT(clone_data->promote_notify == NULL);
1063     }
1064 
1065     common_free(rsc);
1066 }
1067 
1068 enum rsc_role_e
1069 clone_resource_state(const pe_resource_t * rsc, gboolean current)
     /* [previous][next][first][last][top][bottom][index][help] */
1070 {
1071     enum rsc_role_e clone_role = RSC_ROLE_UNKNOWN;
1072     GList *gIter = rsc->children;
1073 
1074     for (; gIter != NULL; gIter = gIter->next) {
1075         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
1076         enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, current);
1077 
1078         if (a_role > clone_role) {
1079             clone_role = a_role;
1080         }
1081     }
1082 
1083     pe_rsc_trace(rsc, "%s role: %s", rsc->id, role2text(clone_role));
1084     return clone_role;
1085 }
1086 
1087 /*!
1088  * \internal
1089  * \brief Check whether a clone has an instance for every node
1090  *
1091  * \param[in] rsc       Clone to check
1092  * \param[in] data_set  Cluster state
1093  */
1094 bool
1095 pe__is_universal_clone(pe_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
1096                        pe_working_set_t *data_set)
1097 {
1098     if (pe_rsc_is_clone(rsc)) {
1099         clone_variant_data_t *clone_data = NULL;
1100 
1101         get_clone_variant_data(clone_data, rsc);
1102         if (clone_data->clone_max == g_list_length(data_set->nodes)) {
1103             return TRUE;
1104         }
1105     }
1106     return FALSE;
1107 }
1108 
1109 gboolean
1110 pe__clone_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent)
     /* [previous][next][first][last][top][bottom][index][help] */
1111 {
1112     gboolean passes = FALSE;
1113     clone_variant_data_t *clone_data = NULL;
1114 
1115     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
1116         passes = TRUE;
1117     } else {
1118         get_clone_variant_data(clone_data, rsc);
1119         passes = pcmk__str_in_list(ID(clone_data->xml_obj_child), only_rsc, pcmk__str_star_matches);
1120 
1121         if (!passes) {
1122             for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
1123                 pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
1124 
1125                 if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) {
1126                     passes = TRUE;
1127                     break;
1128                 }
1129             }
1130         }
1131     }
1132 
1133     return !passes;
1134 }
1135 
1136 const char *
1137 pe__clone_child_id(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1138 {
1139     clone_variant_data_t *clone_data = NULL;
1140     get_clone_variant_data(clone_data, rsc);
1141     return ID(clone_data->xml_obj_child);
1142 }

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