| /* |
| * Copyright 2004-2019 Andrew Beekhof <andrew@beekhof.net> |
| * |
| * This source code is licensed under the GNU General Public License version 2 |
| * or later (GPLv2+) WITHOUT ANY WARRANTY. |
| */ |
| |
| #include <crm_internal.h> |
| |
| #include <crm/msg_xml.h> |
| #include <allocate.h> |
| #include <notif.h> |
| #include <utils.h> |
| #include <allocate.h> |
| |
| #define VARIANT_CLONE 1 |
| #include <lib/pengine/variant.h> |
| |
| gint sort_clone_instance(gconstpointer a, gconstpointer b, gpointer data_set); |
| static void append_parent_colocation(resource_t * rsc, resource_t * child, gboolean all); |
| |
| static gint |
| sort_rsc_id(gconstpointer a, gconstpointer b) |
| { |
| const resource_t *resource1 = (const resource_t *)a; |
| const resource_t *resource2 = (const resource_t *)b; |
| |
| CRM_ASSERT(resource1 != NULL); |
| CRM_ASSERT(resource2 != NULL); |
| |
| return strcmp(resource1->id, resource2->id); |
| } |
| |
| static node_t * |
| parent_node_instance(const resource_t * rsc, node_t * node) |
| { |
| node_t *ret = NULL; |
| |
| if (node != NULL && rsc->parent) { |
| ret = pe_hash_table_lookup(rsc->parent->allowed_nodes, node->details->id); |
| } else if(node != NULL) { |
| ret = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id); |
| } |
| return ret; |
| } |
| |
| static gboolean |
| did_fail(const resource_t * rsc) |
| { |
| GListPtr gIter = rsc->children; |
| |
| if (is_set(rsc->flags, pe_rsc_failed)) { |
| return TRUE; |
| } |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| if (did_fail(child_rsc)) { |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| gint |
| sort_clone_instance(gconstpointer a, gconstpointer b, gpointer data_set) |
| { |
| int rc = 0; |
| node_t *node1 = NULL; |
| node_t *node2 = NULL; |
| node_t *current_node1 = NULL; |
| node_t *current_node2 = NULL; |
| unsigned int nnodes1 = 0; |
| unsigned int nnodes2 = 0; |
| |
| gboolean can1 = TRUE; |
| gboolean can2 = TRUE; |
| |
| const resource_t *resource1 = (const resource_t *)a; |
| const resource_t *resource2 = (const resource_t *)b; |
| |
| CRM_ASSERT(resource1 != NULL); |
| CRM_ASSERT(resource2 != NULL); |
| |
| /* allocation order: |
| * - active instances |
| * - instances running on nodes with the least copies |
| * - active instances on nodes that can't support them or are to be fenced |
| * - failed instances |
| * - inactive instances |
| */ |
| |
| current_node1 = pe__find_active_on(resource1, &nnodes1, NULL); |
| current_node2 = pe__find_active_on(resource2, &nnodes2, NULL); |
| |
| if (nnodes1 && nnodes2) { |
| if (nnodes1 < nnodes2) { |
| crm_trace("%s < %s: running_on", resource1->id, resource2->id); |
| return -1; |
| |
| } else if (nnodes1 > nnodes2) { |
| crm_trace("%s > %s: running_on", resource1->id, resource2->id); |
| return 1; |
| } |
| } |
| |
| node1 = current_node1; |
| node2 = current_node2; |
| if (node1) { |
| node_t *match = pe_hash_table_lookup(resource1->allowed_nodes, node1->details->id); |
| |
| if (match == NULL || match->weight < 0) { |
| crm_trace("%s: current location is unavailable", resource1->id); |
| node1 = NULL; |
| can1 = FALSE; |
| } |
| } |
| |
| if (node2) { |
| node_t *match = pe_hash_table_lookup(resource2->allowed_nodes, node2->details->id); |
| |
| if (match == NULL || match->weight < 0) { |
| crm_trace("%s: current location is unavailable", resource2->id); |
| node2 = NULL; |
| can2 = FALSE; |
| } |
| } |
| |
| if (can1 != can2) { |
| if (can1) { |
| crm_trace("%s < %s: availability of current location", resource1->id, resource2->id); |
| return -1; |
| } |
| crm_trace("%s > %s: availability of current location", resource1->id, resource2->id); |
| return 1; |
| } |
| |
| if (resource1->priority < resource2->priority) { |
| crm_trace("%s < %s: priority", resource1->id, resource2->id); |
| return 1; |
| |
| } else if (resource1->priority > resource2->priority) { |
| crm_trace("%s > %s: priority", resource1->id, resource2->id); |
| return -1; |
| } |
| |
| if (node1 == NULL && node2 == NULL) { |
| crm_trace("%s == %s: not active", resource1->id, resource2->id); |
| return 0; |
| } |
| |
| if (node1 != node2) { |
| if (node1 == NULL) { |
| crm_trace("%s > %s: active", resource1->id, resource2->id); |
| return 1; |
| } else if (node2 == NULL) { |
| crm_trace("%s < %s: active", resource1->id, resource2->id); |
| return -1; |
| } |
| } |
| |
| can1 = can_run_resources(node1); |
| can2 = can_run_resources(node2); |
| if (can1 != can2) { |
| if (can1) { |
| crm_trace("%s < %s: can", resource1->id, resource2->id); |
| return -1; |
| } |
| crm_trace("%s > %s: can", resource1->id, resource2->id); |
| return 1; |
| } |
| |
| node1 = parent_node_instance(resource1, node1); |
| node2 = parent_node_instance(resource2, node2); |
| if (node1 != NULL && node2 == NULL) { |
| crm_trace("%s < %s: not allowed", resource1->id, resource2->id); |
| return -1; |
| } else if (node1 == NULL && node2 != NULL) { |
| crm_trace("%s > %s: not allowed", resource1->id, resource2->id); |
| return 1; |
| } |
| |
| if (node1 == NULL || node2 == NULL) { |
| crm_trace("%s == %s: not allowed", resource1->id, resource2->id); |
| return 0; |
| } |
| |
| if (node1->count < node2->count) { |
| crm_trace("%s < %s: count", resource1->id, resource2->id); |
| return -1; |
| |
| } else if (node1->count > node2->count) { |
| crm_trace("%s > %s: count", resource1->id, resource2->id); |
| return 1; |
| } |
| |
| can1 = did_fail(resource1); |
| can2 = did_fail(resource2); |
| if (can1 != can2) { |
| if (can1) { |
| crm_trace("%s > %s: failed", resource1->id, resource2->id); |
| return 1; |
| } |
| crm_trace("%s < %s: failed", resource1->id, resource2->id); |
| return -1; |
| } |
| |
| if (node1 && node2) { |
| int lpc = 0; |
| int max = 0; |
| node_t *n = NULL; |
| GListPtr gIter = NULL; |
| GListPtr list1 = NULL; |
| GListPtr list2 = NULL; |
| GHashTable *hash1 = |
| g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_str); |
| GHashTable *hash2 = |
| g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_str); |
| |
| n = node_copy(current_node1); |
| g_hash_table_insert(hash1, (gpointer) n->details->id, n); |
| |
| n = node_copy(current_node2); |
| g_hash_table_insert(hash2, (gpointer) n->details->id, n); |
| |
| if(resource1->parent) { |
| for (gIter = resource1->parent->rsc_cons; gIter; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| if (constraint->score == 0) { |
| continue; |
| } |
| crm_trace("Applying %s to %s", constraint->id, resource1->id); |
| |
| hash1 = pcmk__native_merge_weights(constraint->rsc_rh, |
| resource1->id, hash1, |
| constraint->node_attribute, |
| constraint->score / (float) INFINITY, |
| 0); |
| } |
| |
| for (gIter = resource1->parent->rsc_cons_lhs; gIter; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| if (constraint->score == 0) { |
| continue; |
| } |
| crm_trace("Applying %s to %s", constraint->id, resource1->id); |
| |
| hash1 = pcmk__native_merge_weights(constraint->rsc_lh, |
| resource1->id, hash1, |
| constraint->node_attribute, |
| constraint->score / (float) INFINITY, |
| pe_weights_positive); |
| } |
| } |
| |
| if(resource2->parent) { |
| for (gIter = resource2->parent->rsc_cons; gIter; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| crm_trace("Applying %s to %s", constraint->id, resource2->id); |
| |
| hash2 = pcmk__native_merge_weights(constraint->rsc_rh, |
| resource2->id, hash2, |
| constraint->node_attribute, |
| constraint->score / (float) INFINITY, |
| 0); |
| } |
| |
| for (gIter = resource2->parent->rsc_cons_lhs; gIter; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| crm_trace("Applying %s to %s", constraint->id, resource2->id); |
| |
| hash2 = pcmk__native_merge_weights(constraint->rsc_lh, |
| resource2->id, hash2, |
| constraint->node_attribute, |
| constraint->score / (float) INFINITY, |
| pe_weights_positive); |
| } |
| } |
| |
| /* Current location score */ |
| node1 = g_hash_table_lookup(hash1, current_node1->details->id); |
| node2 = g_hash_table_lookup(hash2, current_node2->details->id); |
| |
| if (node1->weight < node2->weight) { |
| if (node1->weight < 0) { |
| crm_trace("%s > %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight); |
| rc = -1; |
| goto out; |
| |
| } else { |
| crm_trace("%s < %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight); |
| rc = 1; |
| goto out; |
| } |
| |
| } else if (node1->weight > node2->weight) { |
| crm_trace("%s > %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight); |
| rc = -1; |
| goto out; |
| } |
| |
| /* All location scores */ |
| list1 = g_hash_table_get_values(hash1); |
| list2 = g_hash_table_get_values(hash2); |
| |
| list1 = g_list_sort_with_data(list1, sort_node_weight, current_node1); |
| list2 = g_list_sort_with_data(list2, sort_node_weight, current_node2); |
| max = g_list_length(list1); |
| if (max < g_list_length(list2)) { |
| max = g_list_length(list2); |
| } |
| |
| for (; lpc < max; lpc++) { |
| node1 = g_list_nth_data(list1, lpc); |
| node2 = g_list_nth_data(list2, lpc); |
| if (node1 == NULL) { |
| crm_trace("%s < %s: colocated score NULL", resource1->id, resource2->id); |
| rc = 1; |
| break; |
| |
| } else if (node2 == NULL) { |
| crm_trace("%s > %s: colocated score NULL", resource1->id, resource2->id); |
| rc = -1; |
| break; |
| } |
| |
| if (node1->weight < node2->weight) { |
| crm_trace("%s < %s: colocated score", resource1->id, resource2->id); |
| rc = 1; |
| break; |
| |
| } else if (node1->weight > node2->weight) { |
| crm_trace("%s > %s: colocated score", resource1->id, resource2->id); |
| rc = -1; |
| break; |
| } |
| } |
| |
| /* Order by reverse uname - same as sort_node_weight() does? */ |
| out: |
| g_hash_table_destroy(hash1); /* Free mem */ |
| g_hash_table_destroy(hash2); /* Free mem */ |
| g_list_free(list1); |
| g_list_free(list2); |
| |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| rc = strcmp(resource1->id, resource2->id); |
| crm_trace("%s %c %s: default", resource1->id, rc < 0 ? '<' : '>', resource2->id); |
| return rc; |
| } |
| |
| static node_t * |
| can_run_instance(resource_t * rsc, node_t * node, int limit) |
| { |
| node_t *local_node = NULL; |
| |
| if (node == NULL && rsc->allowed_nodes) { |
| GHashTableIter iter; |
| g_hash_table_iter_init(&iter, rsc->allowed_nodes); |
| while (g_hash_table_iter_next(&iter, NULL, (void **)&local_node)) { |
| can_run_instance(rsc, local_node, limit); |
| } |
| return NULL; |
| } |
| |
| if (can_run_resources(node) == FALSE) { |
| goto bail; |
| |
| } else if (is_set(rsc->flags, pe_rsc_orphan)) { |
| goto bail; |
| } |
| |
| local_node = parent_node_instance(rsc, node); |
| |
| if (local_node == NULL) { |
| crm_warn("%s cannot run on %s: node not allowed", rsc->id, node->details->uname); |
| goto bail; |
| |
| } else if (local_node->weight < 0) { |
| common_update_score(rsc, node->details->id, local_node->weight); |
| pe_rsc_trace(rsc, "%s cannot run on %s: Parent node weight doesn't allow it.", |
| rsc->id, node->details->uname); |
| |
| } else if (local_node->count < limit) { |
| pe_rsc_trace(rsc, "%s can run on %s (already running %d)", |
| rsc->id, node->details->uname, local_node->count); |
| return local_node; |
| |
| } else { |
| pe_rsc_trace(rsc, "%s cannot run on %s: node full (%d >= %d)", |
| rsc->id, node->details->uname, local_node->count, limit); |
| } |
| |
| bail: |
| if (node) { |
| common_update_score(rsc, node->details->id, -INFINITY); |
| } |
| return NULL; |
| } |
| |
| static pe_node_t * |
| allocate_instance(pe_resource_t *rsc, pe_node_t *prefer, gboolean all_coloc, |
| int limit, pe_working_set_t *data_set) |
| { |
| node_t *chosen = NULL; |
| GHashTable *backup = NULL; |
| |
| CRM_ASSERT(rsc); |
| pe_rsc_trace(rsc, "Checking allocation of %s (preferring %s, using %s parent colocations)", |
| rsc->id, (prefer? prefer->details->uname: "none"), |
| (all_coloc? "all" : "some")); |
| |
| if (is_not_set(rsc->flags, pe_rsc_provisional)) { |
| return rsc->fns->location(rsc, NULL, FALSE); |
| |
| } else if (is_set(rsc->flags, pe_rsc_allocating)) { |
| pe_rsc_debug(rsc, "Dependency loop detected involving %s", rsc->id); |
| return NULL; |
| } |
| |
| /* Only include positive colocation preferences of dependent resources |
| * if not every node will get a copy of the clone |
| */ |
| append_parent_colocation(rsc->parent, rsc, all_coloc); |
| |
| if (prefer) { |
| node_t *local_prefer = g_hash_table_lookup(rsc->allowed_nodes, prefer->details->id); |
| |
| if (local_prefer == NULL || local_prefer->weight < 0) { |
| pe_rsc_trace(rsc, "Not pre-allocating %s to %s - unavailable", rsc->id, |
| prefer->details->uname); |
| return NULL; |
| } |
| } |
| |
| can_run_instance(rsc, NULL, limit); |
| |
| backup = node_hash_dup(rsc->allowed_nodes); |
| pe_rsc_trace(rsc, "Allocating instance %s", rsc->id); |
| chosen = rsc->cmds->allocate(rsc, prefer, data_set); |
| if (chosen) { |
| node_t *local_node = parent_node_instance(rsc, chosen); |
| if (prefer && (chosen->details != prefer->details)) { |
| crm_notice("Pre-allocation failed: got %s instead of %s", |
| chosen->details->uname, prefer->details->uname); |
| g_hash_table_destroy(rsc->allowed_nodes); |
| rsc->allowed_nodes = backup; |
| native_deallocate(rsc); |
| chosen = NULL; |
| backup = NULL; |
| |
| } else if (local_node) { |
| local_node->count++; |
| |
| } else if (is_set(rsc->flags, pe_rsc_managed)) { |
| /* what to do? we can't enforce per-node limits in this case */ |
| crm_config_err("%s not found in %s (list=%d)", |
| chosen->details->id, rsc->parent->id, |
| g_hash_table_size(rsc->parent->allowed_nodes)); |
| } |
| } |
| |
| if(backup) { |
| g_hash_table_destroy(backup); |
| } |
| return chosen; |
| } |
| |
| static void |
| append_parent_colocation(resource_t * rsc, resource_t * child, gboolean all) |
| { |
| |
| GListPtr gIter = NULL; |
| |
| gIter = rsc->rsc_cons; |
| for (; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *cons = (rsc_colocation_t *) gIter->data; |
| |
| if (cons->score == 0) { |
| continue; |
| } |
| if (all || cons->score < 0 || cons->score == INFINITY) { |
| child->rsc_cons = g_list_prepend(child->rsc_cons, cons); |
| } |
| } |
| |
| gIter = rsc->rsc_cons_lhs; |
| for (; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *cons = (rsc_colocation_t *) gIter->data; |
| |
| if (cons->score == 0) { |
| continue; |
| } |
| if (all || cons->score < 0) { |
| child->rsc_cons_lhs = g_list_prepend(child->rsc_cons_lhs, cons); |
| } |
| } |
| } |
| |
| |
| void |
| distribute_children(resource_t *rsc, GListPtr children, GListPtr nodes, |
| int max, int per_host_max, pe_working_set_t * data_set); |
| |
| void |
| distribute_children(resource_t *rsc, GListPtr children, GListPtr nodes, |
| int max, int per_host_max, pe_working_set_t * data_set) |
| { |
| int loop_max = 0; |
| int allocated = 0; |
| int available_nodes = 0; |
| |
| /* count now tracks the number of clones currently allocated */ |
| for(GListPtr nIter = nodes; nIter != NULL; nIter = nIter->next) { |
| pe_node_t *node = nIter->data; |
| |
| node->count = 0; |
| if (can_run_resources(node)) { |
| available_nodes++; |
| } |
| } |
| |
| if(available_nodes) { |
| loop_max = max / available_nodes; |
| } |
| if (loop_max < 1) { |
| loop_max = 1; |
| } |
| |
| pe_rsc_debug(rsc, "Allocating up to %d %s instances to a possible %d nodes (at most %d per host, %d optimal)", |
| max, rsc->id, available_nodes, per_host_max, loop_max); |
| |
| /* Pre-allocate as many instances as we can to their current location */ |
| for (GListPtr gIter = children; gIter != NULL && allocated < max; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| |
| if (child->running_on && is_set(child->flags, pe_rsc_provisional) |
| && is_not_set(child->flags, pe_rsc_failed)) { |
| node_t *child_node = pe__current_node(child); |
| node_t *local_node = parent_node_instance(child, child_node); |
| |
| pe_rsc_trace(rsc, "Checking pre-allocation of %s to %s (%d remaining of %d)", |
| child->id, child_node->details->uname, max - allocated, max); |
| |
| if (can_run_resources(child_node) == FALSE || child_node->weight < 0) { |
| pe_rsc_trace(rsc, "Not pre-allocating because %s can not run %s", |
| child_node->details->uname, child->id); |
| |
| } else if(local_node && local_node->count >= loop_max) { |
| pe_rsc_trace(rsc, |
| "Not pre-allocating because %s already allocated optimal instances", |
| child_node->details->uname); |
| |
| } else if (allocate_instance(child, child_node, |
| max < available_nodes, per_host_max, |
| data_set)) { |
| pe_rsc_trace(rsc, "Pre-allocated %s to %s", child->id, |
| child_node->details->uname); |
| allocated++; |
| } |
| } |
| } |
| |
| pe_rsc_trace(rsc, "Done pre-allocating (%d of %d)", allocated, max); |
| |
| for (GListPtr gIter = children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| |
| if (child->running_on != NULL) { |
| node_t *child_node = pe__current_node(child); |
| node_t *local_node = parent_node_instance(child, child_node); |
| |
| if (local_node == NULL) { |
| crm_err("%s is running on %s which isn't allowed", |
| child->id, child_node->details->uname); |
| } |
| } |
| |
| if (is_not_set(child->flags, pe_rsc_provisional)) { |
| } else if (allocated >= max) { |
| pe_rsc_debug(rsc, "Child %s not allocated - limit reached %d %d", child->id, allocated, max); |
| resource_location(child, NULL, -INFINITY, "clone:limit_reached", data_set); |
| } else { |
| if (allocate_instance(child, NULL, max < available_nodes, |
| per_host_max, data_set)) { |
| allocated++; |
| } |
| } |
| } |
| |
| pe_rsc_debug(rsc, "Allocated %d %s instances of a possible %d", |
| allocated, rsc->id, max); |
| } |
| |
| |
| pe_node_t * |
| pcmk__clone_allocate(pe_resource_t *rsc, pe_node_t *prefer, |
| pe_working_set_t *data_set) |
| { |
| GListPtr nodes = NULL; |
| |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| |
| if (is_not_set(rsc->flags, pe_rsc_provisional)) { |
| return NULL; |
| |
| } else if (is_set(rsc->flags, pe_rsc_allocating)) { |
| pe_rsc_debug(rsc, "Dependency loop detected involving %s", rsc->id); |
| return NULL; |
| } |
| |
| set_bit(rsc->flags, pe_rsc_allocating); |
| |
| /* this information is used by sort_clone_instance() when deciding in which |
| * order to allocate clone instances |
| */ |
| for (GListPtr gIter = rsc->rsc_cons; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| if (constraint->score == 0) { |
| continue; |
| } |
| pe_rsc_trace(rsc, "%s: Allocating %s first", |
| rsc->id, constraint->rsc_rh->id); |
| constraint->rsc_rh->cmds->allocate(constraint->rsc_rh, prefer, data_set); |
| } |
| |
| for (GListPtr gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| if (constraint->score == 0) { |
| continue; |
| } |
| rsc->allowed_nodes = |
| constraint->rsc_lh->cmds->merge_weights(constraint->rsc_lh, rsc->id, rsc->allowed_nodes, |
| constraint->node_attribute, |
| (float)constraint->score / INFINITY, |
| (pe_weights_rollback | pe_weights_positive)); |
| } |
| |
| pe__show_node_weights(!show_scores, rsc, __FUNCTION__, rsc->allowed_nodes); |
| |
| nodes = g_hash_table_get_values(rsc->allowed_nodes); |
| nodes = g_list_sort_with_data(nodes, sort_node_weight, NULL); |
| rsc->children = g_list_sort_with_data(rsc->children, sort_clone_instance, data_set); |
| distribute_children(rsc, rsc->children, nodes, clone_data->clone_max, clone_data->clone_node_max, data_set); |
| g_list_free(nodes); |
| |
| clear_bit(rsc->flags, pe_rsc_provisional); |
| clear_bit(rsc->flags, pe_rsc_allocating); |
| |
| pe_rsc_trace(rsc, "Done allocating %s", rsc->id); |
| return NULL; |
| } |
| |
| static void |
| clone_update_pseudo_status(resource_t * rsc, gboolean * stopping, gboolean * starting, |
| gboolean * active) |
| { |
| GListPtr gIter = NULL; |
| |
| if (rsc->children) { |
| |
| gIter = rsc->children; |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| |
| clone_update_pseudo_status(child, stopping, starting, active); |
| } |
| |
| return; |
| } |
| |
| CRM_ASSERT(active != NULL); |
| CRM_ASSERT(starting != NULL); |
| CRM_ASSERT(stopping != NULL); |
| |
| if (rsc->running_on) { |
| *active = TRUE; |
| } |
| |
| gIter = rsc->actions; |
| for (; gIter != NULL; gIter = gIter->next) { |
| action_t *action = (action_t *) gIter->data; |
| |
| if (*starting && *stopping) { |
| return; |
| |
| } else if (is_set(action->flags, pe_action_optional)) { |
| pe_rsc_trace(rsc, "Skipping optional: %s", action->uuid); |
| continue; |
| |
| } else if (is_set(action->flags, pe_action_pseudo) == FALSE |
| && is_set(action->flags, pe_action_runnable) == FALSE) { |
| pe_rsc_trace(rsc, "Skipping unrunnable: %s", action->uuid); |
| continue; |
| |
| } else if (safe_str_eq(RSC_STOP, action->task)) { |
| pe_rsc_trace(rsc, "Stopping due to: %s", action->uuid); |
| *stopping = TRUE; |
| |
| } else if (safe_str_eq(RSC_START, action->task)) { |
| if (is_set(action->flags, pe_action_runnable) == FALSE) { |
| pe_rsc_trace(rsc, "Skipping pseudo-op: %s run=%d, pseudo=%d", |
| action->uuid, is_set(action->flags, pe_action_runnable), |
| is_set(action->flags, pe_action_pseudo)); |
| } else { |
| pe_rsc_trace(rsc, "Starting due to: %s", action->uuid); |
| pe_rsc_trace(rsc, "%s run=%d, pseudo=%d", |
| action->uuid, is_set(action->flags, pe_action_runnable), |
| is_set(action->flags, pe_action_pseudo)); |
| *starting = TRUE; |
| } |
| } |
| } |
| } |
| |
| static action_t * |
| find_rsc_action(resource_t * rsc, const char *key, gboolean active_only, GListPtr * list) |
| { |
| action_t *match = NULL; |
| GListPtr possible = NULL; |
| GListPtr active = NULL; |
| |
| possible = find_actions(rsc->actions, key, NULL); |
| |
| if (active_only) { |
| GListPtr gIter = possible; |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| action_t *op = (action_t *) gIter->data; |
| |
| if (is_set(op->flags, pe_action_optional) == FALSE) { |
| active = g_list_prepend(active, op); |
| } |
| } |
| |
| if (active && g_list_length(active) == 1) { |
| match = g_list_nth_data(active, 0); |
| } |
| |
| if (list) { |
| *list = active; |
| active = NULL; |
| } |
| |
| } else if (possible && g_list_length(possible) == 1) { |
| match = g_list_nth_data(possible, 0); |
| |
| } |
| if (list) { |
| *list = possible; |
| possible = NULL; |
| } |
| |
| if (possible) { |
| g_list_free(possible); |
| } |
| if (active) { |
| g_list_free(active); |
| } |
| |
| return match; |
| } |
| |
| static void |
| child_ordering_constraints(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| char *key = NULL; |
| action_t *stop = NULL; |
| action_t *start = NULL; |
| action_t *last_stop = NULL; |
| action_t *last_start = NULL; |
| GListPtr gIter = NULL; |
| gboolean active_only = TRUE; /* change to false to get the old behavior */ |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| |
| if (clone_data->ordered == FALSE) { |
| return; |
| } |
| /* we have to maintain a consistent sorted child list when building order constraints */ |
| rsc->children = g_list_sort(rsc->children, sort_rsc_id); |
| |
| for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| |
| key = stop_key(child); |
| stop = find_rsc_action(child, key, active_only, NULL); |
| free(key); |
| |
| key = start_key(child); |
| start = find_rsc_action(child, key, active_only, NULL); |
| free(key); |
| |
| if (stop) { |
| if (last_stop) { |
| /* child/child relative stop */ |
| order_actions(stop, last_stop, pe_order_optional); |
| } |
| last_stop = stop; |
| } |
| |
| if (start) { |
| if (last_start) { |
| /* child/child relative start */ |
| order_actions(last_start, start, pe_order_optional); |
| } |
| last_start = start; |
| } |
| } |
| } |
| |
| void |
| clone_create_actions(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| clone_create_pseudo_actions(rsc, rsc->children, &clone_data->start_notify, &clone_data->stop_notify,data_set); |
| child_ordering_constraints(rsc, data_set); |
| } |
| |
| void |
| clone_create_pseudo_actions( |
| resource_t * rsc, GListPtr children, notify_data_t **start_notify, notify_data_t **stop_notify, pe_working_set_t * data_set) |
| { |
| gboolean child_active = FALSE; |
| gboolean child_starting = FALSE; |
| gboolean child_stopping = FALSE; |
| gboolean allow_dependent_migrations = TRUE; |
| |
| action_t *stop = NULL; |
| action_t *stopped = NULL; |
| |
| action_t *start = NULL; |
| action_t *started = NULL; |
| |
| pe_rsc_trace(rsc, "Creating actions for %s", rsc->id); |
| |
| for (GListPtr gIter = children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| gboolean starting = FALSE; |
| gboolean stopping = FALSE; |
| |
| child_rsc->cmds->create_actions(child_rsc, data_set); |
| clone_update_pseudo_status(child_rsc, &stopping, &starting, &child_active); |
| if (stopping && starting) { |
| allow_dependent_migrations = FALSE; |
| } |
| |
| child_stopping |= stopping; |
| child_starting |= starting; |
| } |
| |
| /* start */ |
| start = create_pseudo_resource_op(rsc, RSC_START, !child_starting, TRUE, data_set); |
| started = create_pseudo_resource_op(rsc, RSC_STARTED, !child_starting, FALSE, data_set); |
| started->priority = INFINITY; |
| |
| if (child_active || child_starting) { |
| update_action_flags(started, pe_action_runnable, __FUNCTION__, __LINE__); |
| } |
| |
| if (start_notify != NULL && *start_notify == NULL) { |
| *start_notify = create_notification_boundaries(rsc, RSC_START, start, started, data_set); |
| } |
| |
| /* stop */ |
| stop = create_pseudo_resource_op(rsc, RSC_STOP, !child_stopping, TRUE, data_set); |
| stopped = create_pseudo_resource_op(rsc, RSC_STOPPED, !child_stopping, TRUE, data_set); |
| stopped->priority = INFINITY; |
| if (allow_dependent_migrations) { |
| update_action_flags(stop, pe_action_migrate_runnable, __FUNCTION__, __LINE__); |
| } |
| |
| if (stop_notify != NULL && *stop_notify == NULL) { |
| *stop_notify = create_notification_boundaries(rsc, RSC_STOP, stop, stopped, data_set); |
| |
| if (start_notify && *start_notify && *stop_notify) { |
| order_actions((*stop_notify)->post_done, (*start_notify)->pre, pe_order_optional); |
| } |
| } |
| } |
| |
| void |
| clone_internal_constraints(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| resource_t *last_rsc = NULL; |
| GListPtr gIter; |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| |
| pe_rsc_trace(rsc, "Internal constraints for %s", rsc->id); |
| new_rsc_order(rsc, RSC_STOPPED, rsc, RSC_START, pe_order_optional, data_set); |
| new_rsc_order(rsc, RSC_START, rsc, RSC_STARTED, pe_order_runnable_left, data_set); |
| new_rsc_order(rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_runnable_left, data_set); |
| |
| if (rsc->variant == pe_master) { |
| new_rsc_order(rsc, RSC_DEMOTED, rsc, RSC_STOP, pe_order_optional, data_set); |
| new_rsc_order(rsc, RSC_STARTED, rsc, RSC_PROMOTE, pe_order_runnable_left, data_set); |
| } |
| |
| if (clone_data->ordered) { |
| /* we have to maintain a consistent sorted child list when building order constraints */ |
| rsc->children = g_list_sort(rsc->children, sort_rsc_id); |
| } |
| for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->internal_constraints(child_rsc, data_set); |
| |
| order_start_start(rsc, child_rsc, pe_order_runnable_left | pe_order_implies_first_printed); |
| new_rsc_order(child_rsc, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed, |
| data_set); |
| if (clone_data->ordered && last_rsc) { |
| order_start_start(last_rsc, child_rsc, pe_order_optional); |
| } |
| |
| order_stop_stop(rsc, child_rsc, pe_order_implies_first_printed); |
| new_rsc_order(child_rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed, |
| data_set); |
| if (clone_data->ordered && last_rsc) { |
| order_stop_stop(child_rsc, last_rsc, pe_order_optional); |
| } |
| |
| last_rsc = child_rsc; |
| } |
| } |
| |
| bool |
| assign_node(resource_t * rsc, node_t * node, gboolean force) |
| { |
| bool changed = FALSE; |
| |
| if (rsc->children) { |
| |
| for (GListPtr gIter = rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| changed |= assign_node(child_rsc, node, force); |
| } |
| |
| return changed; |
| } |
| |
| if (rsc->allocated_to != NULL) { |
| changed = true; |
| } |
| |
| native_assign_node(rsc, NULL, node, force); |
| return changed; |
| } |
| |
| gboolean |
| is_child_compatible(resource_t *child_rsc, node_t * local_node, enum rsc_role_e filter, gboolean current) |
| { |
| node_t *node = NULL; |
| enum rsc_role_e next_role = child_rsc->fns->state(child_rsc, current); |
| |
| CRM_CHECK(child_rsc && local_node, return FALSE); |
| if (is_set_recursive(child_rsc, pe_rsc_block, TRUE) == FALSE) { |
| /* We only want instances that haven't failed */ |
| node = child_rsc->fns->location(child_rsc, NULL, current); |
| } |
| |
| if (filter != RSC_ROLE_UNKNOWN && next_role != filter) { |
| crm_trace("Filtered %s", child_rsc->id); |
| return FALSE; |
| } |
| |
| if (node && (node->details == local_node->details)) { |
| return TRUE; |
| |
| } else if (node) { |
| crm_trace("%s - %s vs %s", child_rsc->id, node->details->uname, |
| local_node->details->uname); |
| |
| } else { |
| crm_trace("%s - not allocated %d", child_rsc->id, current); |
| } |
| return FALSE; |
| } |
| |
| resource_t * |
| find_compatible_child(resource_t * local_child, resource_t * rsc, enum rsc_role_e filter, gboolean current) |
| { |
| resource_t *pair = NULL; |
| GListPtr gIter = NULL; |
| GListPtr scratch = NULL; |
| node_t *local_node = NULL; |
| |
| local_node = local_child->fns->location(local_child, NULL, current); |
| if (local_node) { |
| return find_compatible_child_by_node(local_child, local_node, rsc, filter, current); |
| } |
| |
| scratch = g_hash_table_get_values(local_child->allowed_nodes); |
| scratch = g_list_sort_with_data(scratch, sort_node_weight, NULL); |
| |
| gIter = scratch; |
| for (; gIter != NULL; gIter = gIter->next) { |
| node_t *node = (node_t *) gIter->data; |
| |
| pair = find_compatible_child_by_node(local_child, node, rsc, filter, current); |
| if (pair) { |
| goto done; |
| } |
| } |
| |
| pe_rsc_debug(rsc, "Can't pair %s with %s", local_child->id, rsc->id); |
| done: |
| g_list_free(scratch); |
| return pair; |
| } |
| |
| void |
| clone_rsc_colocation_lh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint) |
| { |
| /* -- Never called -- |
| * |
| * Instead we add the colocation constraints to the child and call from there |
| */ |
| CRM_ASSERT(FALSE); |
| } |
| |
| void |
| clone_rsc_colocation_rh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint) |
| { |
| GListPtr gIter = NULL; |
| gboolean do_interleave = FALSE; |
| const char *interleave_s = NULL; |
| |
| CRM_CHECK(constraint != NULL, return); |
| CRM_CHECK(rsc_lh != NULL, pe_err("rsc_lh was NULL for %s", constraint->id); return); |
| CRM_CHECK(rsc_rh != NULL, pe_err("rsc_rh was NULL for %s", constraint->id); return); |
| CRM_CHECK(rsc_lh->variant == pe_native, return); |
| |
| if (constraint->score == 0) { |
| return; |
| } |
| pe_rsc_trace(rsc_rh, "Processing constraint %s: %s -> %s %d", |
| constraint->id, rsc_lh->id, rsc_rh->id, constraint->score); |
| |
| /* only the LHS side needs to be labeled as interleave */ |
| interleave_s = g_hash_table_lookup(constraint->rsc_lh->meta, XML_RSC_ATTR_INTERLEAVE); |
| if(crm_is_true(interleave_s) && constraint->rsc_lh->variant > pe_group) { |
| // TODO: Do we actually care about multiple RH copies sharing a LH copy anymore? |
| if (copies_per_node(constraint->rsc_lh) != copies_per_node(constraint->rsc_rh)) { |
| crm_config_err("Cannot interleave %s and %s because" |
| " they do not support the same number of copies per node", |
| constraint->rsc_lh->id, constraint->rsc_rh->id); |
| |
| } else { |
| do_interleave = TRUE; |
| } |
| } |
| |
| if (is_set(rsc_rh->flags, pe_rsc_provisional)) { |
| pe_rsc_trace(rsc_rh, "%s is still provisional", rsc_rh->id); |
| return; |
| |
| } else if (do_interleave) { |
| resource_t *rh_child = NULL; |
| |
| rh_child = find_compatible_child(rsc_lh, rsc_rh, RSC_ROLE_UNKNOWN, FALSE); |
| |
| if (rh_child) { |
| pe_rsc_debug(rsc_rh, "Pairing %s with %s", rsc_lh->id, rh_child->id); |
| rsc_lh->cmds->rsc_colocation_lh(rsc_lh, rh_child, constraint); |
| |
| } else if (constraint->score >= INFINITY) { |
| crm_notice("Cannot pair %s with instance of %s", rsc_lh->id, rsc_rh->id); |
| assign_node(rsc_lh, NULL, TRUE); |
| |
| } else { |
| pe_rsc_debug(rsc_rh, "Cannot pair %s with instance of %s", rsc_lh->id, rsc_rh->id); |
| } |
| |
| return; |
| |
| } else if (constraint->score >= INFINITY) { |
| GListPtr rhs = NULL; |
| |
| gIter = rsc_rh->children; |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| node_t *chosen = child_rsc->fns->location(child_rsc, NULL, FALSE); |
| |
| if (chosen != NULL && is_set_recursive(child_rsc, pe_rsc_block, TRUE) == FALSE) { |
| pe_rsc_trace(rsc_rh, "Allowing %s: %s %d", constraint->id, chosen->details->uname, chosen->weight); |
| rhs = g_list_prepend(rhs, chosen); |
| } |
| } |
| |
| node_list_exclude(rsc_lh->allowed_nodes, rhs, FALSE); |
| g_list_free(rhs); |
| return; |
| } |
| |
| gIter = rsc_rh->children; |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->rsc_colocation_rh(rsc_lh, child_rsc, constraint); |
| } |
| } |
| |
| enum action_tasks |
| clone_child_action(action_t * action) |
| { |
| enum action_tasks result = no_action; |
| resource_t *child = (resource_t *) action->rsc->children->data; |
| |
| if (safe_str_eq(action->task, "notify") |
| || safe_str_eq(action->task, "notified")) { |
| |
| /* Find the action we're notifying about instead */ |
| |
| int stop = 0; |
| char *key = action->uuid; |
| int lpc = strlen(key); |
| |
| for (; lpc > 0; lpc--) { |
| if (key[lpc] == '_' && stop == 0) { |
| stop = lpc; |
| |
| } else if (key[lpc] == '_') { |
| char *task_mutable = NULL; |
| |
| lpc++; |
| task_mutable = strdup(key + lpc); |
| task_mutable[stop - lpc] = 0; |
| |
| crm_trace("Extracted action '%s' from '%s'", task_mutable, key); |
| result = get_complex_task(child, task_mutable, TRUE); |
| free(task_mutable); |
| break; |
| } |
| } |
| |
| } else { |
| result = get_complex_task(child, action->task, TRUE); |
| } |
| return result; |
| } |
| |
| enum pe_action_flags |
| summary_action_flags(action_t * action, GListPtr children, node_t * node) |
| { |
| GListPtr gIter = NULL; |
| gboolean any_runnable = FALSE; |
| gboolean check_runnable = TRUE; |
| enum action_tasks task = clone_child_action(action); |
| enum pe_action_flags flags = (pe_action_optional | pe_action_runnable | pe_action_pseudo); |
| const char *task_s = task2text(task); |
| |
| for (gIter = children; gIter != NULL; gIter = gIter->next) { |
| action_t *child_action = NULL; |
| resource_t *child = (resource_t *) gIter->data; |
| |
| child_action = find_first_action(child->actions, NULL, task_s, child->children ? NULL : node); |
| pe_rsc_trace(action->rsc, "Checking for %s in %s on %s (%s)", task_s, child->id, |
| node ? node->details->uname : "none", child_action?child_action->uuid:"NA"); |
| if (child_action) { |
| enum pe_action_flags child_flags = child->cmds->action_flags(child_action, node); |
| |
| if (is_set(flags, pe_action_optional) |
| && is_set(child_flags, pe_action_optional) == FALSE) { |
| pe_rsc_trace(child, "%s is mandatory because of %s", action->uuid, |
| child_action->uuid); |
| flags = crm_clear_bit(__FUNCTION__, __LINE__, action->rsc->id, flags, pe_action_optional); |
| pe_clear_action_bit(action, pe_action_optional); |
| } |
| if (is_set(child_flags, pe_action_runnable)) { |
| any_runnable = TRUE; |
| } |
| } |
| } |
| |
| if (check_runnable && any_runnable == FALSE) { |
| pe_rsc_trace(action->rsc, "%s is not runnable because no children are", action->uuid); |
| flags = crm_clear_bit(__FUNCTION__, __LINE__, action->rsc->id, flags, pe_action_runnable); |
| if (node == NULL) { |
| pe_clear_action_bit(action, pe_action_runnable); |
| } |
| } |
| |
| return flags; |
| } |
| |
| enum pe_action_flags |
| clone_action_flags(action_t * action, node_t * node) |
| { |
| return summary_action_flags(action, action->rsc->children, node); |
| } |
| |
| void |
| clone_rsc_location(pe_resource_t *rsc, pe__location_t *constraint) |
| { |
| GListPtr gIter = rsc->children; |
| |
| pe_rsc_trace(rsc, "Processing location constraint %s for %s", constraint->id, rsc->id); |
| |
| native_rsc_location(rsc, constraint); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->rsc_location(child_rsc, constraint); |
| } |
| } |
| |
| void |
| clone_expand(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| GListPtr gIter = NULL; |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| |
| gIter = rsc->actions; |
| for (; gIter != NULL; gIter = gIter->next) { |
| action_t *op = (action_t *) gIter->data; |
| |
| rsc->cmds->action_flags(op, NULL); |
| } |
| |
| if (clone_data->start_notify) { |
| collect_notification_data(rsc, TRUE, TRUE, clone_data->start_notify); |
| expand_notification_data(rsc, clone_data->start_notify, data_set); |
| create_notifications(rsc, clone_data->start_notify, data_set); |
| } |
| |
| if (clone_data->stop_notify) { |
| collect_notification_data(rsc, TRUE, TRUE, clone_data->stop_notify); |
| expand_notification_data(rsc, clone_data->stop_notify, data_set); |
| create_notifications(rsc, clone_data->stop_notify, data_set); |
| } |
| |
| if (clone_data->promote_notify) { |
| collect_notification_data(rsc, TRUE, TRUE, clone_data->promote_notify); |
| expand_notification_data(rsc, clone_data->promote_notify, data_set); |
| create_notifications(rsc, clone_data->promote_notify, data_set); |
| } |
| |
| if (clone_data->demote_notify) { |
| collect_notification_data(rsc, TRUE, TRUE, clone_data->demote_notify); |
| expand_notification_data(rsc, clone_data->demote_notify, data_set); |
| create_notifications(rsc, clone_data->demote_notify, data_set); |
| } |
| |
| /* Now that the notifcations have been created we can expand the children */ |
| |
| gIter = rsc->children; |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->expand(child_rsc, data_set); |
| } |
| |
| native_expand(rsc, data_set); |
| |
| /* The notifications are in the graph now, we can destroy the notify_data */ |
| free_notification_data(clone_data->demote_notify); |
| clone_data->demote_notify = NULL; |
| free_notification_data(clone_data->stop_notify); |
| clone_data->stop_notify = NULL; |
| free_notification_data(clone_data->start_notify); |
| clone_data->start_notify = NULL; |
| free_notification_data(clone_data->promote_notify); |
| clone_data->promote_notify = NULL; |
| } |
| |
| // Check whether a resource or any of its children is known on node |
| static bool |
| rsc_known_on(const pe_resource_t *rsc, const pe_node_t *node) |
| { |
| if (rsc->children) { |
| for (GList *child_iter = rsc->children; child_iter != NULL; |
| child_iter = child_iter->next) { |
| |
| resource_t *child = (resource_t *) child_iter->data; |
| |
| if (rsc_known_on(child, node)) { |
| return TRUE; |
| } |
| } |
| |
| } else if (rsc->known_on) { |
| GHashTableIter iter; |
| node_t *known_node = NULL; |
| |
| g_hash_table_iter_init(&iter, rsc->known_on); |
| while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &known_node)) { |
| if (node->details == known_node->details) { |
| return TRUE; |
| } |
| } |
| } |
| return FALSE; |
| } |
| |
| // Look for an instance of clone that is known on node |
| static pe_resource_t * |
| find_instance_on(const pe_resource_t *clone, const pe_node_t *node) |
| { |
| for (GList *gIter = clone->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| |
| if (rsc_known_on(child, node)) { |
| return child; |
| } |
| } |
| return NULL; |
| } |
| |
| // For unique clones, probe each instance separately |
| static gboolean |
| probe_unique_clone(pe_resource_t *rsc, pe_node_t *node, pe_action_t *complete, |
| gboolean force, pe_working_set_t *data_set) |
| { |
| gboolean any_created = FALSE; |
| |
| for (GList *child_iter = rsc->children; child_iter != NULL; |
| child_iter = child_iter->next) { |
| |
| resource_t *child = (resource_t *) child_iter->data; |
| |
| any_created |= child->cmds->create_probe(child, node, complete, force, |
| data_set); |
| } |
| return any_created; |
| } |
| |
| // For anonymous clones, only a single instance needs to be probed |
| static gboolean |
| probe_anonymous_clone(pe_resource_t *rsc, pe_node_t *node, |
| pe_action_t *complete, gboolean force, |
| pe_working_set_t *data_set) |
| { |
| // First, check if we probed an instance on this node last time |
| pe_resource_t *child = find_instance_on(rsc, node); |
| |
| // Otherwise, check if we plan to start an instance on this node |
| if (child == NULL) { |
| for (GList *child_iter = rsc->children; child_iter && !child; |
| child_iter = child_iter->next) { |
| |
| node_t *local_node = NULL; |
| resource_t *child_rsc = (resource_t *) child_iter->data; |
| |
| local_node = child_rsc->fns->location(child_rsc, NULL, FALSE); |
| if (local_node && (local_node->details == node->details)) { |
| child = child_rsc; |
| } |
| } |
| } |
| |
| // Otherwise, use the first clone instance |
| if (child == NULL) { |
| child = rsc->children->data; |
| } |
| return child->cmds->create_probe(child, node, complete, force, data_set); |
| } |
| |
| gboolean |
| clone_create_probe(resource_t * rsc, node_t * node, action_t * complete, |
| gboolean force, pe_working_set_t * data_set) |
| { |
| gboolean any_created = FALSE; |
| |
| CRM_ASSERT(rsc); |
| |
| rsc->children = g_list_sort(rsc->children, sort_rsc_id); |
| if (rsc->children == NULL) { |
| pe_warn("Clone %s has no children", rsc->id); |
| return FALSE; |
| } |
| |
| if (rsc->exclusive_discover) { |
| node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id); |
| if (allowed && allowed->rsc_discover_mode != pe_discover_exclusive) { |
| /* exclusive discover is enabled and this node is not marked |
| * as a node this resource should be discovered on |
| * |
| * remove the node from allowed_nodes so that the |
| * notification contains only nodes that we might ever run |
| * on |
| */ |
| g_hash_table_remove(rsc->allowed_nodes, node->details->id); |
| |
| /* Bit of a shortcut - might as well take it */ |
| return FALSE; |
| } |
| } |
| |
| if (is_set(rsc->flags, pe_rsc_unique)) { |
| any_created = probe_unique_clone(rsc, node, complete, force, data_set); |
| } else { |
| any_created = probe_anonymous_clone(rsc, node, complete, force, |
| data_set); |
| } |
| return any_created; |
| } |
| |
| void |
| clone_append_meta(resource_t * rsc, xmlNode * xml) |
| { |
| char *name = NULL; |
| clone_variant_data_t *clone_data = NULL; |
| |
| get_clone_variant_data(clone_data, rsc); |
| |
| name = crm_meta_name(XML_RSC_ATTR_UNIQUE); |
| crm_xml_add(xml, name, is_set(rsc->flags, pe_rsc_unique) ? "true" : "false"); |
| free(name); |
| |
| name = crm_meta_name(XML_RSC_ATTR_NOTIFY); |
| crm_xml_add(xml, name, is_set(rsc->flags, pe_rsc_notify) ? "true" : "false"); |
| free(name); |
| |
| name = crm_meta_name(XML_RSC_ATTR_INCARNATION_MAX); |
| crm_xml_add_int(xml, name, clone_data->clone_max); |
| free(name); |
| |
| name = crm_meta_name(XML_RSC_ATTR_INCARNATION_NODEMAX); |
| crm_xml_add_int(xml, name, clone_data->clone_node_max); |
| free(name); |
| } |