diff --git a/lib/pacemaker/pcmk_sched_utilization.c b/lib/pacemaker/pcmk_sched_utilization.c index 2ee217de27..b3c6e6505d 100644 --- a/lib/pacemaker/pcmk_sched_utilization.c +++ b/lib/pacemaker/pcmk_sched_utilization.c @@ -1,372 +1,371 @@ /* * Copyright 2014-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include "libpacemaker_private.h" /*! * \internal * \brief Get integer utilization from a string * * \param[in] s String representation of a node utilization value * * \return Integer equivalent of \p s * \todo It would make sense to restrict utilization values to nonnegative * integers, but the documentation just says "integers" and we didn't * restrict them initially, so for backward compatibility, allow any * integer. */ static int utilization_value(const char *s) { int value = 0; if ((s != NULL) && (pcmk__scan_min_int(s, &value, INT_MIN) == EINVAL)) { pe_warn("Using 0 for utilization instead of invalid value '%s'", value); value = 0; } return value; } /* * Functions for comparing node capacities */ struct compare_data { const pe_node_t *node1; const pe_node_t *node2; bool node2_only; int result; }; /*! * \internal * \brief Compare a single utilization attribute for two nodes * * Compare one utilization attribute for two nodes, incrementing the result if * the first node has greater capacity, and decrementing it if the second node * has greater capacity. * * \param[in] key Utilization attribute name to compare * \param[in] value Utilization attribute value to compare * \param[in] user_data Comparison data (as struct compare_data*) */ static void compare_utilization_value(gpointer key, gpointer value, gpointer user_data) { int node1_capacity = 0; int node2_capacity = 0; struct compare_data *data = user_data; const char *node2_value = NULL; if (data->node2_only) { if (g_hash_table_lookup(data->node1->details->utilization, key)) { return; // We've already compared this attribute } } else { node1_capacity = utilization_value((const char *) value); } node2_value = g_hash_table_lookup(data->node2->details->utilization, key); node2_capacity = utilization_value(node2_value); if (node1_capacity > node2_capacity) { data->result--; } else if (node1_capacity < node2_capacity) { data->result++; } } /*! * \internal * \brief Compare utilization capacities of two nodes * * \param[in] node1 First node to compare * \param[in] node2 Second node to compare * * \return Negative integer if node1 has more free capacity, * 0 if the capacities are equal, or a positive integer * if node2 has more free capacity */ int pcmk__compare_node_capacities(const pe_node_t *node1, const pe_node_t *node2) { struct compare_data data = { .node1 = node1, .node2 = node2, .node2_only = false, .result = 0, }; // Compare utilization values that node1 and maybe node2 have g_hash_table_foreach(node1->details->utilization, compare_utilization_value, &data); // Compare utilization values that only node2 has data.node2_only = true; g_hash_table_foreach(node2->details->utilization, compare_utilization_value, &data); return data.result; } /* * Functions for updating node capacities */ struct calculate_data { GHashTable *current_utilization; bool plus; }; /*! * \internal * \brief Update a single utilization attribute with a new value * * \param[in] key Name of utilization attribute to update * \param[in] value Value to add or substract * \param[in] user_data Calculation data (as struct calculate_data *) */ static void update_utilization_value(gpointer key, gpointer value, gpointer user_data) { int result = 0; const char *current = NULL; struct calculate_data *data = user_data; current = g_hash_table_lookup(data->current_utilization, key); if (data->plus) { result = utilization_value(current) + utilization_value(value); } else if (current) { result = utilization_value(current) - utilization_value(value); } g_hash_table_replace(data->current_utilization, strdup(key), pcmk__itoa(result)); } /*! * \internal * \brief Subtract a resource's utilization from node capacity * * \param[in] current_utilization Current node utilization attributes * \param[in] rsc Resource with utilization to subtract */ void pcmk__consume_node_capacity(GHashTable *current_utilization, pe_resource_t *rsc) { struct calculate_data data = { .current_utilization = current_utilization, .plus = false, }; g_hash_table_foreach(rsc->utilization, update_utilization_value, &data); } /*! * \internal * \brief Add a resource's utilization to node capacity * * \param[in] current_utilization Current node utilization attributes * \param[in] rsc Resource with utilization to add */ void pcmk__release_node_capacity(GHashTable *current_utilization, pe_resource_t *rsc) { struct calculate_data data = { .current_utilization = current_utilization, .plus = true, }; g_hash_table_foreach(rsc->utilization, update_utilization_value, &data); } /* * Functions for checking for sufficient node capacity */ struct capacity_data { pe_node_t *node; const char *rsc_id; bool is_enough; }; /*! * \internal * \brief Check whether a single utilization attribute has sufficient capacity * * \param[in] key Name of utilization attribute to check * \param[in] value Amount of utilization required * \param[in] user_data Capacity data (as struct capacity_data *) */ static void check_capacity(gpointer key, gpointer value, gpointer user_data) { int required = 0; int remaining = 0; const char *node_value_s = NULL; struct capacity_data *data = user_data; node_value_s = g_hash_table_lookup(data->node->details->utilization, key); required = utilization_value(value); remaining = utilization_value(node_value_s); if (required > remaining) { crm_debug("Remaining capacity for %s on %s (%d) is insufficient " "for resource %s usage (%d)", (const char *) key, data->node->details->uname, remaining, data->rsc_id, required); data->is_enough = false; } } /*! * \internal * \brief Check whether a node has sufficient capacity for a resource * * \param[in] node Node to check * \param[in] rsc_id ID of resource to check (for debug logs only) * \param[in] utilization Required utilization amounts * * \return true if node has sufficient capacity for resource, otherwise false */ static bool have_enough_capacity(pe_node_t *node, const char *rsc_id, GHashTable *utilization) { struct capacity_data data = { .node = node, .rsc_id = rsc_id, .is_enough = true, }; g_hash_table_foreach(utilization, check_capacity, &data); return data.is_enough; } - +/*! + * \internal + * \brief Sum the utilization requirements of a list of resources + * + * \param[in] orig_rsc Resource being allocated (for logging purposes) + * \param[in] rscs Resources whose utilization should be summed + * + * \return Newly allocated hash table with sum of all utilization values + * \note It is the caller's responsibility to free the return value using + * g_hash_table_destroy(). + */ static GHashTable * -sum_unallocated_utilization(pe_resource_t * rsc, GList *colocated_rscs) +sum_resource_utilization(pe_resource_t *orig_rsc, GList *rscs) { - GList *gIter = NULL; - GList *all_rscs = NULL; - GHashTable *all_utilization = pcmk__strkey_table(free, free); - - all_rscs = g_list_copy(colocated_rscs); - if (g_list_find(all_rscs, rsc) == FALSE) { - all_rscs = g_list_append(all_rscs, rsc); - } - - for (gIter = all_rscs; gIter != NULL; gIter = gIter->next) { - pe_resource_t *listed_rsc = (pe_resource_t *) gIter->data; + GHashTable *utilization = pcmk__strkey_table(free, free); - if (!pcmk_is_set(listed_rsc->flags, pe_rsc_provisional)) { - continue; - } + for (GList *iter = rscs; iter != NULL; iter = iter->next) { + pe_resource_t *rsc = (pe_resource_t *) iter->data; - pe_rsc_trace(rsc, "%s: Processing unallocated colocated %s", rsc->id, listed_rsc->id); - listed_rsc->cmds->add_utilization(listed_rsc, rsc, all_rscs, - all_utilization); + rsc->cmds->add_utilization(rsc, orig_rsc, rscs, utilization); } - - g_list_free(all_rscs); - - return all_utilization; + return utilization; } void process_utilization(pe_resource_t * rsc, pe_node_t ** prefer, pe_working_set_t * data_set) { CRM_CHECK(rsc && prefer && data_set, return); if (!pcmk__str_eq(data_set->placement_strategy, "default", pcmk__str_casei)) { GHashTableIter iter; GList *colocated_rscs = NULL; gboolean any_capable = FALSE; pe_node_t *node = NULL; colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL); if (colocated_rscs) { GHashTable *unallocated_utilization = NULL; char *rscs_id = crm_strdup_printf("%s and its colocated resources", rsc->id); pe_node_t *most_capable_node = NULL; - unallocated_utilization = sum_unallocated_utilization(rsc, colocated_rscs); + // If rsc isn't in the list, add it so we include its utilization + if (g_list_find(colocated_rscs, rsc) == NULL) { + colocated_rscs = g_list_append(colocated_rscs, rsc); + } + + unallocated_utilization = sum_resource_utilization(rsc, colocated_rscs); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (!pcmk__node_available(node) || (node->weight < 0)) { continue; } if (have_enough_capacity(node, rscs_id, unallocated_utilization)) { any_capable = TRUE; } if (most_capable_node == NULL || pcmk__compare_node_capacities(node, most_capable_node) < 0) { /* < 0 means 'node' is more capable */ most_capable_node = node; } } if (any_capable) { g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (!pcmk__node_available(node) || (node->weight < 0)) { continue; } if (!have_enough_capacity(node, rscs_id, unallocated_utilization)) { pe_rsc_debug(rsc, "Resource %s and its colocated resources" " cannot be allocated to node %s: not enough capacity", rsc->id, node->details->uname); resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set); } } } else if (*prefer == NULL) { *prefer = most_capable_node; } if (unallocated_utilization) { g_hash_table_destroy(unallocated_utilization); } g_list_free(colocated_rscs); free(rscs_id); } if (any_capable == FALSE) { g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (!pcmk__node_available(node) || (node->weight < 0)) { continue; } if (!have_enough_capacity(node, rsc->id, rsc->utilization)) { pe_rsc_debug(rsc, "Resource %s cannot be allocated to node %s:" " not enough capacity", rsc->id, node->details->uname); resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set); } } } pe__show_node_weights(true, rsc, "Post-utilization", rsc->allowed_nodes, data_set); } }