pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
pcmk_sched_utilization.c
Go to the documentation of this file.
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 
31 static int
32 utilization_value(const char *s)
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 
68 static void
69 compare_utilization_value(gpointer key, gpointer value, gpointer user_data)
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 
105 int
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 
146 static void
147 update_utilization_value(gpointer key, gpointer value, gpointer user_data)
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 
170 void
171 pcmk__consume_node_capacity(GHashTable *current_utilization,
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 
190 void
191 pcmk__release_node_capacity(GHashTable *current_utilization,
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 
222 static void
223 check_capacity(gpointer key, gpointer value, gpointer user_data)
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 
254 static bool
255 have_enough_capacity(const pcmk_node_t *node, const char *rsc_id,
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 
279 static GHashTable *
280 sum_resource_utilization(const pcmk_resource_t *orig_rsc, GList *rscs)
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 
301 const pcmk_node_t *
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,
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);
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);
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 
403 static pcmk_action_t *
404 new_load_stopped_op(pcmk_node_t *node)
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);
414  }
415  free(load_stopped_task);
416  return load_stopped;
417 }
418 
426 void
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,
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,
450  rsc->priv->scheduler);
451  pcmk__new_ordering(NULL, NULL, load_stopped,
452  rsc,
454  NULL,
456  rsc->priv->scheduler);
457  }
458 }
459 
467 void
469 {
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 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
pcmk_node_t * pe__copy_node(const pcmk_node_t *this_node)
Definition: utils.c:91
Actions are ordered if on same node (or migration target for migrate_to)
char data[0]
Definition: cpg.c:58
pcmk_action_t * get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
Definition: pe_actions.c:1140
int pcmk__scan_min_int(const char *text, int *result, int minimum)
Definition: strings.c:116
G_GNUC_INTERNAL void pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_task, pcmk_action_t *first_action, pcmk_resource_t *then_rsc, char *then_task, pcmk_action_t *then_action, uint32_t flags, pcmk_scheduler_t *sched)
void pcmk__consume_node_capacity(GHashTable *current_utilization, const pcmk_resource_t *rsc)
int(* message)(pcmk__output_t *out, const char *message_id,...)
#define pcmk__config_warn(fmt...)
#define pcmk__rsc_trace(rsc, fmt, args...)
void pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler)
const pcmk_node_t * pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc)
#define PCMK_VALUE_DEFAULT
Definition: options.h:143
pcmk__scheduler_private_t * priv
Definition: scheduler.h:99
#define PCMK_ACTION_MIGRATE_TO
Definition: actions.h:50
void(* add_utilization)(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization)
uint64_t flags
Definition: scheduler.h:89
pcmk_node_t node2
G_GNUC_INTERNAL bool pcmk__node_available(const pcmk_node_t *node, bool consider_score, bool consider_guest)
#define pcmk__rsc_debug(rsc, fmt, args...)
pcmk__node_private_t * priv
Definition: nodes.h:85
void pcmk__create_utilization_constraints(pcmk_resource_t *rsc, const GList *allowed_nodes)
#define crm_debug(fmt, args...)
Definition: logging.h:370
pcmk_scheduler_t * scheduler
#define pcmk__clear_action_flags(action, flags_to_clear)
void resource_location(pcmk_resource_t *rsc, const pcmk_node_t *node, int score, const char *tag, pcmk_scheduler_t *scheduler)
Definition: utils.c:365
#define stop_key(rsc)
Definition: internal.h:196
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
pcmk__resource_private_t * priv
Definition: resources.h:61
Wrappers for and extensions to libxml2.
pcmk_scheduler_t * scheduler
char * pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
Generate an operation key (RESOURCE_ACTION_INTERVAL)
Definition: actions.c:195
#define PCMK_ACTION_LOAD_STOPPED
Definition: actions.h:45
pcmk_node_t * node
GList * nodes
Definition: scheduler.h:97
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:685
pcmk__action_result_t result
Definition: pcmk_fence.c:37
int pcmk__compare_node_capacities(const pcmk_node_t *node1, const pcmk_node_t *node2)
pcmk_scheduler_t * scheduler
void pcmk__release_node_capacity(GHashTable *current_utilization, const pcmk_resource_t *rsc)
This structure contains everything that makes up a single output formatter.
#define pe__show_node_scores(level, rsc, text, nodes, scheduler)
Definition: internal.h:164
GList *(* colocated_resources)(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList *colocated_rscs)
#define start_key(rsc)
Definition: internal.h:198
pcmk_node_t node1
const pcmk__assignment_methods_t * cmds
GHashTable * utilization
#define PCMK_SCORE_INFINITY
Integer score to use to represent "infinity".
Definition: scores.h:26
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1