root/lib/pacemaker/pcmk_sched_utilization.c

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

DEFINITIONS

This source file includes following definitions.
  1. utilization_value
  2. compare_utilization_value
  3. pcmk__compare_node_capacities
  4. update_utilization_value
  5. pcmk__consume_node_capacity
  6. pcmk__release_node_capacity
  7. check_capacity
  8. have_enough_capacity
  9. sum_resource_utilization
  10. pcmk__ban_insufficient_capacity
  11. new_load_stopped_op
  12. pcmk__create_utilization_constraints
  13. pcmk__show_node_capacities

   1 /*
   2  * Copyright 2014-2023 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 General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <crm/msg_xml.h>
  12 #include <pacemaker-internal.h>
  13 
  14 #include "libpacemaker_private.h"
  15 
  16 // Name for a pseudo-op to use in ordering constraints for utilization
  17 #define LOAD_STOPPED "load_stopped"
  18 
  19 /*!
  20  * \internal
  21  * \brief Get integer utilization from a string
  22  *
  23  * \param[in] s  String representation of a node utilization value
  24  *
  25  * \return Integer equivalent of \p s
  26  * \todo It would make sense to restrict utilization values to nonnegative
  27  *       integers, but the documentation just says "integers" and we didn't
  28  *       restrict them initially, so for backward compatibility, allow any
  29  *       integer.
  30  */
  31 static int
  32 utilization_value(const char *s)
     /* [previous][next][first][last][top][bottom][index][help] */
  33 {
  34     int value = 0;
  35 
  36     if ((s != NULL) && (pcmk__scan_min_int(s, &value, INT_MIN) == EINVAL)) {
  37         pe_warn("Using 0 for utilization instead of invalid value '%s'", value);
  38         value = 0;
  39     }
  40     return value;
  41 }
  42 
  43 
  44 /*
  45  * Functions for comparing node capacities
  46  */
  47 
  48 struct compare_data {
  49     const pe_node_t *node1;
  50     const pe_node_t *node2;
  51     bool node2_only;
  52     int result;
  53 };
  54 
  55 /*!
  56  * \internal
  57  * \brief Compare a single utilization attribute for two nodes
  58  *
  59  * Compare one utilization attribute for two nodes, incrementing the result if
  60  * the first node has greater capacity, and decrementing it if the second node
  61  * has greater capacity.
  62  *
  63  * \param[in]     key        Utilization attribute name to compare
  64  * \param[in]     value      Utilization attribute value to compare
  65  * \param[in,out] user_data  Comparison data (as struct compare_data*)
  66  */
  67 static void
  68 compare_utilization_value(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
  69 {
  70     int node1_capacity = 0;
  71     int node2_capacity = 0;
  72     struct compare_data *data = user_data;
  73     const char *node2_value = NULL;
  74 
  75     if (data->node2_only) {
  76         if (g_hash_table_lookup(data->node1->details->utilization, key)) {
  77             return; // We've already compared this attribute
  78         }
  79     } else {
  80         node1_capacity = utilization_value((const char *) value);
  81     }
  82 
  83     node2_value = g_hash_table_lookup(data->node2->details->utilization, key);
  84     node2_capacity = utilization_value(node2_value);
  85 
  86     if (node1_capacity > node2_capacity) {
  87         data->result--;
  88     } else if (node1_capacity < node2_capacity) {
  89         data->result++;
  90     }
  91 }
  92 
  93 /*!
  94  * \internal
  95  * \brief Compare utilization capacities of two nodes
  96  *
  97  * \param[in] node1  First node to compare
  98  * \param[in] node2  Second node to compare
  99  *
 100  * \return Negative integer if node1 has more free capacity,
 101  *         0 if the capacities are equal, or a positive integer
 102  *         if node2 has more free capacity
 103  */
 104 int
 105 pcmk__compare_node_capacities(const pe_node_t *node1, const pe_node_t *node2)
     /* [previous][next][first][last][top][bottom][index][help] */
 106 {
 107     struct compare_data data = {
 108         .node1      = node1,
 109         .node2      = node2,
 110         .node2_only = false,
 111         .result     = 0,
 112     };
 113 
 114     // Compare utilization values that node1 and maybe node2 have
 115     g_hash_table_foreach(node1->details->utilization, compare_utilization_value,
 116                          &data);
 117 
 118     // Compare utilization values that only node2 has
 119     data.node2_only = true;
 120     g_hash_table_foreach(node2->details->utilization, compare_utilization_value,
 121                          &data);
 122 
 123     return data.result;
 124 }
 125 
 126 
 127 /*
 128  * Functions for updating node capacities
 129  */
 130 
 131 struct calculate_data {
 132     GHashTable *current_utilization;
 133     bool plus;
 134 };
 135 
 136 /*!
 137  * \internal
 138  * \brief Update a single utilization attribute with a new value
 139  *
 140  * \param[in]     key        Name of utilization attribute to update
 141  * \param[in]     value      Value to add or substract
 142  * \param[in,out] user_data  Calculation data (as struct calculate_data *)
 143  */
 144 static void
 145 update_utilization_value(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 146 {
 147     int result = 0;
 148     const char *current = NULL;
 149     struct calculate_data *data = user_data;
 150 
 151     current = g_hash_table_lookup(data->current_utilization, key);
 152     if (data->plus) {
 153         result = utilization_value(current) + utilization_value(value);
 154     } else if (current) {
 155         result = utilization_value(current) - utilization_value(value);
 156     }
 157     g_hash_table_replace(data->current_utilization,
 158                          strdup(key), pcmk__itoa(result));
 159 }
 160 
 161 /*!
 162  * \internal
 163  * \brief Subtract a resource's utilization from node capacity
 164  *
 165  * \param[in,out] current_utilization  Current node utilization attributes
 166  * \param[in]     rsc                  Resource with utilization to subtract
 167  */
 168 void
 169 pcmk__consume_node_capacity(GHashTable *current_utilization,
     /* [previous][next][first][last][top][bottom][index][help] */
 170                             const pe_resource_t *rsc)
 171 {
 172     struct calculate_data data = {
 173         .current_utilization = current_utilization,
 174         .plus = false,
 175     };
 176 
 177     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
 178 }
 179 
 180 /*!
 181  * \internal
 182  * \brief Add a resource's utilization to node capacity
 183  *
 184  * \param[in,out] current_utilization  Current node utilization attributes
 185  * \param[in]     rsc                  Resource with utilization to add
 186  */
 187 void
 188 pcmk__release_node_capacity(GHashTable *current_utilization,
     /* [previous][next][first][last][top][bottom][index][help] */
 189                             const pe_resource_t *rsc)
 190 {
 191     struct calculate_data data = {
 192         .current_utilization = current_utilization,
 193         .plus = true,
 194     };
 195 
 196     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
 197 }
 198 
 199 
 200 /*
 201  * Functions for checking for sufficient node capacity
 202  */
 203 
 204 struct capacity_data {
 205     const pe_node_t *node;
 206     const char *rsc_id;
 207     bool is_enough;
 208 };
 209 
 210 /*!
 211  * \internal
 212  * \brief Check whether a single utilization attribute has sufficient capacity
 213  *
 214  * \param[in]     key        Name of utilization attribute to check
 215  * \param[in]     value      Amount of utilization required
 216  * \param[in,out] user_data  Capacity data (as struct capacity_data *)
 217  */
 218 static void
 219 check_capacity(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 220 {
 221     int required = 0;
 222     int remaining = 0;
 223     const char *node_value_s = NULL;
 224     struct capacity_data *data = user_data;
 225 
 226     node_value_s = g_hash_table_lookup(data->node->details->utilization, key);
 227 
 228     required = utilization_value(value);
 229     remaining = utilization_value(node_value_s);
 230 
 231     if (required > remaining) {
 232         crm_debug("Remaining capacity for %s on %s (%d) is insufficient "
 233                   "for resource %s usage (%d)",
 234                   (const char *) key, pe__node_name(data->node), remaining,
 235                   data->rsc_id, required);
 236         data->is_enough = false;
 237     }
 238 }
 239 
 240 /*!
 241  * \internal
 242  * \brief Check whether a node has sufficient capacity for a resource
 243  *
 244  * \param[in] node         Node to check
 245  * \param[in] rsc_id       ID of resource to check (for debug logs only)
 246  * \param[in] utilization  Required utilization amounts
 247  *
 248  * \return true if node has sufficient capacity for resource, otherwise false
 249  */
 250 static bool
 251 have_enough_capacity(const pe_node_t *node, const char *rsc_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 252                      GHashTable *utilization)
 253 {
 254     struct capacity_data data = {
 255         .node = node,
 256         .rsc_id = rsc_id,
 257         .is_enough = true,
 258     };
 259 
 260     g_hash_table_foreach(utilization, check_capacity, &data);
 261     return data.is_enough;
 262 }
 263 
 264 /*!
 265  * \internal
 266  * \brief Sum the utilization requirements of a list of resources
 267  *
 268  * \param[in] orig_rsc  Resource being allocated (for logging purposes)
 269  * \param[in] rscs      Resources whose utilization should be summed
 270  *
 271  * \return Newly allocated hash table with sum of all utilization values
 272  * \note It is the caller's responsibility to free the return value using
 273  *       g_hash_table_destroy().
 274  */
 275 static GHashTable *
 276 sum_resource_utilization(const pe_resource_t *orig_rsc, GList *rscs)
     /* [previous][next][first][last][top][bottom][index][help] */
 277 {
 278     GHashTable *utilization = pcmk__strkey_table(free, free);
 279 
 280     for (GList *iter = rscs; iter != NULL; iter = iter->next) {
 281         pe_resource_t *rsc = (pe_resource_t *) iter->data;
 282 
 283         rsc->cmds->add_utilization(rsc, orig_rsc, rscs, utilization);
 284     }
 285     return utilization;
 286 }
 287 
 288 /*!
 289  * \internal
 290  * \brief Ban resource from nodes with insufficient utilization capacity
 291  *
 292  * \param[in,out] rsc  Resource to check
 293  *
 294  * \return Allowed node for \p rsc with most spare capacity, if there are no
 295  *         nodes with enough capacity for \p rsc and all its colocated resources
 296  */
 297 const pe_node_t *
 298 pcmk__ban_insufficient_capacity(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 299 {
 300     bool any_capable = false;
 301     char *rscs_id = NULL;
 302     pe_node_t *node = NULL;
 303     const pe_node_t *most_capable_node = NULL;
 304     GList *colocated_rscs = NULL;
 305     GHashTable *unallocated_utilization = NULL;
 306     GHashTableIter iter;
 307 
 308     CRM_CHECK(rsc != NULL, return NULL);
 309 
 310     // The default placement strategy ignores utilization
 311     if (pcmk__str_eq(rsc->cluster->placement_strategy, "default",
 312                      pcmk__str_casei)) {
 313         return NULL;
 314     }
 315 
 316     // Check whether any resources are colocated with this one
 317     colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL);
 318     if (colocated_rscs == NULL) {
 319         return NULL;
 320     }
 321 
 322     rscs_id = crm_strdup_printf("%s and its colocated resources", rsc->id);
 323 
 324     // If rsc isn't in the list, add it so we include its utilization
 325     if (g_list_find(colocated_rscs, rsc) == NULL) {
 326         colocated_rscs = g_list_append(colocated_rscs, rsc);
 327     }
 328 
 329     // Sum utilization of colocated resources that haven't been allocated yet
 330     unallocated_utilization = sum_resource_utilization(rsc, colocated_rscs);
 331 
 332     // Check whether any node has enough capacity for all the resources
 333     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
 334     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
 335         if (!pcmk__node_available(node, true, false)) {
 336             continue;
 337         }
 338 
 339         if (have_enough_capacity(node, rscs_id, unallocated_utilization)) {
 340             any_capable = true;
 341         }
 342 
 343         // Keep track of node with most free capacity
 344         if ((most_capable_node == NULL)
 345             || (pcmk__compare_node_capacities(node, most_capable_node) < 0)) {
 346             most_capable_node = node;
 347         }
 348     }
 349 
 350     if (any_capable) {
 351         // If so, ban resource from any node with insufficient capacity
 352         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
 353         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
 354             if (pcmk__node_available(node, true, false)
 355                 && !have_enough_capacity(node, rscs_id,
 356                                          unallocated_utilization)) {
 357                 pe_rsc_debug(rsc, "%s does not have enough capacity for %s",
 358                              pe__node_name(node), rscs_id);
 359                 resource_location(rsc, node, -INFINITY, "__limit_utilization__",
 360                                   rsc->cluster);
 361             }
 362         }
 363         most_capable_node = NULL;
 364 
 365     } else {
 366         // Otherwise, ban from nodes with insufficient capacity for rsc alone
 367         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
 368         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
 369             if (pcmk__node_available(node, true, false)
 370                 && !have_enough_capacity(node, rsc->id, rsc->utilization)) {
 371                 pe_rsc_debug(rsc, "%s does not have enough capacity for %s",
 372                              pe__node_name(node), rsc->id);
 373                 resource_location(rsc, node, -INFINITY, "__limit_utilization__",
 374                                   rsc->cluster);
 375             }
 376         }
 377     }
 378 
 379     g_hash_table_destroy(unallocated_utilization);
 380     g_list_free(colocated_rscs);
 381     free(rscs_id);
 382 
 383     pe__show_node_weights(true, rsc, "Post-utilization",
 384                           rsc->allowed_nodes, rsc->cluster);
 385     return most_capable_node;
 386 }
 387 
 388 /*!
 389  * \internal
 390  * \brief Create a new load_stopped pseudo-op for a node
 391  *
 392  * \param[in]     node      Node to create op for
 393  * \param[in,out] data_set  Cluster working set
 394  *
 395  * \return Newly created load_stopped op
 396  */
 397 static pe_action_t *
 398 new_load_stopped_op(const pe_node_t *node, pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 399 {
 400     char *load_stopped_task = crm_strdup_printf(LOAD_STOPPED "_%s",
 401                                                 node->details->uname);
 402     pe_action_t *load_stopped = get_pseudo_op(load_stopped_task, data_set);
 403 
 404     if (load_stopped->node == NULL) {
 405         load_stopped->node = pe__copy_node(node);
 406         pe__clear_action_flags(load_stopped, pe_action_optional);
 407     }
 408     free(load_stopped_task);
 409     return load_stopped;
 410 }
 411 
 412 /*!
 413  * \internal
 414  * \brief Create utilization-related internal constraints for a resource
 415  *
 416  * \param[in,out] rsc            Resource to create constraints for
 417  * \param[in]     allowed_nodes  List of allowed next nodes for \p rsc
 418  */
 419 void
 420 pcmk__create_utilization_constraints(pe_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 421                                      const GList *allowed_nodes)
 422 {
 423     const GList *iter = NULL;
 424     const pe_node_t *node = NULL;
 425     pe_action_t *load_stopped = NULL;
 426 
 427     pe_rsc_trace(rsc, "Creating utilization constraints for %s - strategy: %s",
 428                  rsc->id, rsc->cluster->placement_strategy);
 429 
 430     // "stop rsc then load_stopped" constraints for current nodes
 431     for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
 432         node = (const pe_node_t *) iter->data;
 433         load_stopped = new_load_stopped_op(node, rsc->cluster);
 434         pcmk__new_ordering(rsc, stop_key(rsc), NULL, NULL, NULL, load_stopped,
 435                            pe_order_load, rsc->cluster);
 436     }
 437 
 438     // "load_stopped then start/migrate_to rsc" constraints for allowed nodes
 439     for (iter = allowed_nodes; iter; iter = iter->next) {
 440         node = (const pe_node_t *) iter->data;
 441         load_stopped = new_load_stopped_op(node, rsc->cluster);
 442         pcmk__new_ordering(NULL, NULL, load_stopped, rsc, start_key(rsc), NULL,
 443                            pe_order_load, rsc->cluster);
 444         pcmk__new_ordering(NULL, NULL, load_stopped,
 445                            rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0), NULL,
 446                            pe_order_load, rsc->cluster);
 447     }
 448 }
 449 
 450 /*!
 451  * \internal
 452  * \brief Output node capacities if enabled
 453  *
 454  * \param[in]     desc      Prefix for output
 455  * \param[in,out] data_set  Cluster working set
 456  */
 457 void
 458 pcmk__show_node_capacities(const char *desc, pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 459 {
 460     if (!pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
 461         return;
 462     }
 463     for (const GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
 464         const pe_node_t *node = (const pe_node_t *) iter->data;
 465         pcmk__output_t *out = data_set->priv;
 466 
 467         out->message(out, "node-capacity", node, desc);
 468     }
 469 }

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