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

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