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

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