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

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