LCOV - code coverage report
Current view: top level - pacemaker - pcmk_sched_utilization.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 145 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 13 0.0 %

          Line data    Source code
       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           0 : utilization_value(const char *s)
      30             : {
      31           0 :     int value = 0;
      32             : 
      33           0 :     if ((s != NULL) && (pcmk__scan_min_int(s, &value, INT_MIN) == EINVAL)) {
      34           0 :         pcmk__config_warn("Using 0 for utilization instead of "
      35             :                           "invalid value '%s'", value);
      36           0 :         value = 0;
      37             :     }
      38           0 :     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           0 : compare_utilization_value(gpointer key, gpointer value, gpointer user_data)
      67             : {
      68           0 :     int node1_capacity = 0;
      69           0 :     int node2_capacity = 0;
      70           0 :     struct compare_data *data = user_data;
      71           0 :     const char *node2_value = NULL;
      72             : 
      73           0 :     if (data->node2_only) {
      74           0 :         if (g_hash_table_lookup(data->node1->details->utilization, key)) {
      75           0 :             return; // We've already compared this attribute
      76             :         }
      77             :     } else {
      78           0 :         node1_capacity = utilization_value((const char *) value);
      79             :     }
      80             : 
      81           0 :     node2_value = g_hash_table_lookup(data->node2->details->utilization, key);
      82           0 :     node2_capacity = utilization_value(node2_value);
      83             : 
      84           0 :     if (node1_capacity > node2_capacity) {
      85           0 :         data->result--;
      86           0 :     } else if (node1_capacity < node2_capacity) {
      87           0 :         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           0 : pcmk__compare_node_capacities(const pcmk_node_t *node1,
     104             :                               const pcmk_node_t *node2)
     105             : {
     106           0 :     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           0 :     g_hash_table_foreach(node1->details->utilization, compare_utilization_value,
     115             :                          &data);
     116             : 
     117             :     // Compare utilization values that only node2 has
     118           0 :     data.node2_only = true;
     119           0 :     g_hash_table_foreach(node2->details->utilization, compare_utilization_value,
     120             :                          &data);
     121             : 
     122           0 :     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           0 : update_utilization_value(gpointer key, gpointer value, gpointer user_data)
     145             : {
     146           0 :     int result = 0;
     147           0 :     const char *current = NULL;
     148           0 :     struct calculate_data *data = user_data;
     149             : 
     150           0 :     current = g_hash_table_lookup(data->current_utilization, key);
     151           0 :     if (data->plus) {
     152           0 :         result = utilization_value(current) + utilization_value(value);
     153           0 :     } else if (current) {
     154           0 :         result = utilization_value(current) - utilization_value(value);
     155             :     }
     156           0 :     g_hash_table_replace(data->current_utilization,
     157           0 :                          strdup(key), pcmk__itoa(result));
     158           0 : }
     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           0 : pcmk__consume_node_capacity(GHashTable *current_utilization,
     169             :                             const pcmk_resource_t *rsc)
     170             : {
     171           0 :     struct calculate_data data = {
     172             :         .current_utilization = current_utilization,
     173             :         .plus = false,
     174             :     };
     175             : 
     176           0 :     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
     177           0 : }
     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           0 : pcmk__release_node_capacity(GHashTable *current_utilization,
     188             :                             const pcmk_resource_t *rsc)
     189             : {
     190           0 :     struct calculate_data data = {
     191             :         .current_utilization = current_utilization,
     192             :         .plus = true,
     193             :     };
     194             : 
     195           0 :     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
     196           0 : }
     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           0 : check_capacity(gpointer key, gpointer value, gpointer user_data)
     219             : {
     220           0 :     int required = 0;
     221           0 :     int remaining = 0;
     222           0 :     const char *node_value_s = NULL;
     223           0 :     struct capacity_data *data = user_data;
     224             : 
     225           0 :     node_value_s = g_hash_table_lookup(data->node->details->utilization, key);
     226             : 
     227           0 :     required = utilization_value(value);
     228           0 :     remaining = utilization_value(node_value_s);
     229             : 
     230           0 :     if (required > remaining) {
     231           0 :         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           0 :         data->is_enough = false;
     236             :     }
     237           0 : }
     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           0 : have_enough_capacity(const pcmk_node_t *node, const char *rsc_id,
     251             :                      GHashTable *utilization)
     252             : {
     253           0 :     struct capacity_data data = {
     254             :         .node = node,
     255             :         .rsc_id = rsc_id,
     256             :         .is_enough = true,
     257             :     };
     258             : 
     259           0 :     g_hash_table_foreach(utilization, check_capacity, &data);
     260           0 :     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           0 : sum_resource_utilization(const pcmk_resource_t *orig_rsc, GList *rscs)
     276             : {
     277           0 :     GHashTable *utilization = pcmk__strkey_table(free, free);
     278             : 
     279           0 :     for (GList *iter = rscs; iter != NULL; iter = iter->next) {
     280           0 :         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
     281             : 
     282           0 :         rsc->cmds->add_utilization(rsc, orig_rsc, rscs, utilization);
     283             :     }
     284           0 :     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           0 : pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc)
     298             : {
     299           0 :     bool any_capable = false;
     300           0 :     char *rscs_id = NULL;
     301           0 :     pcmk_node_t *node = NULL;
     302           0 :     const pcmk_node_t *most_capable_node = NULL;
     303           0 :     GList *colocated_rscs = NULL;
     304           0 :     GHashTable *unassigned_utilization = NULL;
     305             :     GHashTableIter iter;
     306             : 
     307           0 :     CRM_CHECK(rsc != NULL, return NULL);
     308             : 
     309             :     // The default placement strategy ignores utilization
     310           0 :     if (pcmk__str_eq(rsc->cluster->placement_strategy, PCMK_VALUE_DEFAULT,
     311             :                      pcmk__str_casei)) {
     312           0 :         return NULL;
     313             :     }
     314             : 
     315             :     // Check whether any resources are colocated with this one
     316           0 :     colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL);
     317           0 :     if (colocated_rscs == NULL) {
     318           0 :         return NULL;
     319             :     }
     320             : 
     321           0 :     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           0 :     if (g_list_find(colocated_rscs, rsc) == NULL) {
     325           0 :         colocated_rscs = g_list_append(colocated_rscs, rsc);
     326             :     }
     327             : 
     328             :     // Sum utilization of colocated resources that haven't been assigned yet
     329           0 :     unassigned_utilization = sum_resource_utilization(rsc, colocated_rscs);
     330             : 
     331             :     // Check whether any node has enough capacity for all the resources
     332           0 :     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     333           0 :     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
     334           0 :         if (!pcmk__node_available(node, true, false)) {
     335           0 :             continue;
     336             :         }
     337             : 
     338           0 :         if (have_enough_capacity(node, rscs_id, unassigned_utilization)) {
     339           0 :             any_capable = true;
     340             :         }
     341             : 
     342             :         // Keep track of node with most free capacity
     343           0 :         if ((most_capable_node == NULL)
     344           0 :             || (pcmk__compare_node_capacities(node, most_capable_node) < 0)) {
     345           0 :             most_capable_node = node;
     346             :         }
     347             :     }
     348             : 
     349           0 :     if (any_capable) {
     350             :         // If so, ban resource from any node with insufficient capacity
     351           0 :         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     352           0 :         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
     353           0 :             if (pcmk__node_available(node, true, false)
     354           0 :                 && !have_enough_capacity(node, rscs_id,
     355             :                                          unassigned_utilization)) {
     356           0 :                 pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
     357             :                                 pcmk__node_name(node), rscs_id);
     358           0 :                 resource_location(rsc, node, -PCMK_SCORE_INFINITY,
     359             :                                   "__limit_utilization__", rsc->cluster);
     360             :             }
     361             :         }
     362           0 :         most_capable_node = NULL;
     363             : 
     364             :     } else {
     365             :         // Otherwise, ban from nodes with insufficient capacity for rsc alone
     366           0 :         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     367           0 :         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
     368           0 :             if (pcmk__node_available(node, true, false)
     369           0 :                 && !have_enough_capacity(node, rsc->id, rsc->utilization)) {
     370           0 :                 pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
     371             :                                 pcmk__node_name(node), rsc->id);
     372           0 :                 resource_location(rsc, node, -PCMK_SCORE_INFINITY,
     373             :                                   "__limit_utilization__", rsc->cluster);
     374             :             }
     375             :         }
     376             :     }
     377             : 
     378           0 :     g_hash_table_destroy(unassigned_utilization);
     379           0 :     g_list_free(colocated_rscs);
     380           0 :     free(rscs_id);
     381             : 
     382           0 :     pe__show_node_scores(true, rsc, "Post-utilization", rsc->allowed_nodes,
     383             :                          rsc->cluster);
     384           0 :     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           0 : new_load_stopped_op(pcmk_node_t *node)
     397             : {
     398           0 :     char *load_stopped_task = crm_strdup_printf(PCMK_ACTION_LOAD_STOPPED "_%s",
     399           0 :                                                 node->details->uname);
     400           0 :     pcmk_action_t *load_stopped = get_pseudo_op(load_stopped_task,
     401           0 :                                               node->details->data_set);
     402             : 
     403           0 :     if (load_stopped->node == NULL) {
     404           0 :         load_stopped->node = pe__copy_node(node);
     405           0 :         pcmk__clear_action_flags(load_stopped, pcmk_action_optional);
     406             :     }
     407           0 :     free(load_stopped_task);
     408           0 :     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           0 : pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
     420             :                                      const GList *allowed_nodes)
     421             : {
     422           0 :     const GList *iter = NULL;
     423           0 :     pcmk_action_t *load_stopped = NULL;
     424             : 
     425           0 :     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           0 :     for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
     431           0 :         load_stopped = new_load_stopped_op(iter->data);
     432           0 :         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           0 :     for (iter = allowed_nodes; iter; iter = iter->next) {
     438           0 :         load_stopped = new_load_stopped_op(iter->data);
     439           0 :         pcmk__new_ordering(NULL, NULL, load_stopped, rsc, start_key(rsc), NULL,
     440             :                            pcmk__ar_if_on_same_node_or_target, rsc->cluster);
     441           0 :         pcmk__new_ordering(NULL, NULL, load_stopped,
     442             :                            rsc,
     443           0 :                            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           0 : }
     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           0 : pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler)
     458             : {
     459           0 :     if (!pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
     460           0 :         return;
     461             :     }
     462           0 :     for (const GList *iter = scheduler->nodes;
     463           0 :          iter != NULL; iter = iter->next) {
     464           0 :         const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
     465           0 :         pcmk__output_t *out = scheduler->priv;
     466             : 
     467           0 :         out->message(out, "node-capacity", node, desc);
     468             :     }
     469             : }

Generated by: LCOV version 1.14