pacemaker  2.1.2-ada5c3b36
Scalable High-Availability cluster resource manager
pcmk_sched_utilization.c
Go to the documentation of this file.
1 /*
2  * Copyright 2014-2021 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 static void group_add_unallocated_utilization(GHashTable * all_utilization, pe_resource_t * rsc,
17  GList *all_rscs);
18 
19 struct compare_data {
20  const pe_node_t *node1;
21  const pe_node_t *node2;
22  int result;
23 };
24 
25 static int
26 utilization_value(const char *s)
27 {
28  int value = 0;
29 
30  /* @TODO It would make sense to restrict utilization values to nonnegative
31  * integers, but the documentation just says "integers" and we didn't
32  * restrict them initially, so for backward compatibility, allow any
33  * integer.
34  */
35  if (s != NULL) {
36  pcmk__scan_min_int(s, &value, INT_MIN);
37  }
38  return value;
39 }
40 
41 static void
42 do_compare_capacity1(gpointer key, gpointer value, gpointer user_data)
43 {
44  int node1_capacity = 0;
45  int node2_capacity = 0;
46  struct compare_data *data = user_data;
47 
48  node1_capacity = utilization_value(value);
49  node2_capacity = utilization_value(g_hash_table_lookup(data->node2->details->utilization, key));
50 
51  if (node1_capacity > node2_capacity) {
52  data->result--;
53  } else if (node1_capacity < node2_capacity) {
54  data->result++;
55  }
56 }
57 
58 static void
59 do_compare_capacity2(gpointer key, gpointer value, gpointer user_data)
60 {
61  int node1_capacity = 0;
62  int node2_capacity = 0;
63  struct compare_data *data = user_data;
64 
65  if (g_hash_table_lookup_extended(data->node1->details->utilization, key, NULL, NULL)) {
66  return;
67  }
68 
69  node1_capacity = 0;
70  node2_capacity = utilization_value(value);
71 
72  if (node1_capacity > node2_capacity) {
73  data->result--;
74  } else if (node1_capacity < node2_capacity) {
75  data->result++;
76  }
77 }
78 
79 /* rc < 0 if 'node1' has more capacity remaining
80  * rc > 0 if 'node1' has less capacity remaining
81  */
82 int
83 compare_capacity(const pe_node_t * node1, const pe_node_t * node2)
84 {
85  struct compare_data data;
86 
87  data.node1 = node1;
88  data.node2 = node2;
89  data.result = 0;
90 
91  g_hash_table_foreach(node1->details->utilization, do_compare_capacity1, &data);
92  g_hash_table_foreach(node2->details->utilization, do_compare_capacity2, &data);
93 
94  return data.result;
95 }
96 
97 struct calculate_data {
98  GHashTable *current_utilization;
99  gboolean plus;
100 };
101 
102 static void
103 do_calculate_utilization(gpointer key, gpointer value, gpointer user_data)
104 {
105  const char *current = NULL;
106  char *result = NULL;
107  struct calculate_data *data = user_data;
108 
109  current = g_hash_table_lookup(data->current_utilization, key);
110  if (data->plus) {
111  result = pcmk__itoa(utilization_value(current) + utilization_value(value));
112  g_hash_table_replace(data->current_utilization, strdup(key), result);
113 
114  } else if (current) {
115  result = pcmk__itoa(utilization_value(current) - utilization_value(value));
116  g_hash_table_replace(data->current_utilization, strdup(key), result);
117  }
118 }
119 
120 /* Specify 'plus' to FALSE when allocating
121  * Otherwise to TRUE when deallocating
122  */
123 void
124 calculate_utilization(GHashTable * current_utilization,
125  GHashTable * utilization, gboolean plus)
126 {
127  struct calculate_data data;
128 
129  data.current_utilization = current_utilization;
130  data.plus = plus;
131 
132  g_hash_table_foreach(utilization, do_calculate_utilization, &data);
133 }
134 
135 
136 struct capacity_data {
137  pe_node_t *node;
138  const char *rsc_id;
139  gboolean is_enough;
140 };
141 
142 static void
143 check_capacity(gpointer key, gpointer value, gpointer user_data)
144 {
145  int required = 0;
146  int remaining = 0;
147  struct capacity_data *data = user_data;
148 
149  required = utilization_value(value);
150  remaining = utilization_value(g_hash_table_lookup(data->node->details->utilization, key));
151 
152  if (required > remaining) {
153  CRM_ASSERT(data->rsc_id);
154  CRM_ASSERT(data->node);
155 
156  crm_debug("Node %s does not have enough %s for %s: required=%d remaining=%d",
157  data->node->details->uname, (char *)key, data->rsc_id, required, remaining);
158  data->is_enough = FALSE;
159  }
160 }
161 
162 static gboolean
163 have_enough_capacity(pe_node_t * node, const char * rsc_id, GHashTable * utilization)
164 {
165  struct capacity_data data;
166 
167  data.node = node;
168  data.rsc_id = rsc_id;
169  data.is_enough = TRUE;
170 
171  g_hash_table_foreach(utilization, check_capacity, &data);
172 
173  return data.is_enough;
174 }
175 
176 
177 static void
178 native_add_unallocated_utilization(GHashTable * all_utilization, pe_resource_t * rsc)
179 {
180  if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
181  return;
182  }
183 
184  calculate_utilization(all_utilization, rsc->utilization, TRUE);
185 }
186 
187 static void
188 add_unallocated_utilization(GHashTable * all_utilization, pe_resource_t * rsc,
189  GList *all_rscs, pe_resource_t * orig_rsc)
190 {
191  if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
192  return;
193  }
194 
195  if (rsc->variant == pe_native) {
196  pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
197  orig_rsc->id, rsc->id);
198  native_add_unallocated_utilization(all_utilization, rsc);
199 
200  } else if (rsc->variant == pe_group) {
201  pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
202  orig_rsc->id, rsc->id);
203  group_add_unallocated_utilization(all_utilization, rsc, all_rscs);
204 
205  } else if (pe_rsc_is_clone(rsc)) {
206  GList *gIter1 = NULL;
207  gboolean existing = FALSE;
208 
209  /* Check if there's any child already existing in the list */
210  gIter1 = rsc->children;
211  for (; gIter1 != NULL; gIter1 = gIter1->next) {
212  pe_resource_t *child = (pe_resource_t *) gIter1->data;
213  GList *gIter2 = NULL;
214 
215  if (g_list_find(all_rscs, child)) {
216  existing = TRUE;
217 
218  } else {
219  /* Check if there's any child of another cloned group already existing in the list */
220  gIter2 = child->children;
221  for (; gIter2 != NULL; gIter2 = gIter2->next) {
222  pe_resource_t *grandchild = (pe_resource_t *) gIter2->data;
223 
224  if (g_list_find(all_rscs, grandchild)) {
225  pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
226  orig_rsc->id, child->id);
227  add_unallocated_utilization(all_utilization, child, all_rscs, orig_rsc);
228  existing = TRUE;
229  break;
230  }
231  }
232  }
233  }
234 
235  // rsc->children is always non-NULL but this makes static analysis happy
236  if (!existing && (rsc->children != NULL)) {
237  pe_resource_t *first_child = (pe_resource_t *) rsc->children->data;
238 
239  pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
240  orig_rsc->id, ID(first_child->xml));
241  add_unallocated_utilization(all_utilization, first_child, all_rscs, orig_rsc);
242  }
243  }
244 }
245 
246 static GHashTable *
247 sum_unallocated_utilization(pe_resource_t * rsc, GList *colocated_rscs)
248 {
249  GList *gIter = NULL;
250  GList *all_rscs = NULL;
251  GHashTable *all_utilization = pcmk__strkey_table(free, free);
252 
253  all_rscs = g_list_copy(colocated_rscs);
254  if (g_list_find(all_rscs, rsc) == FALSE) {
255  all_rscs = g_list_append(all_rscs, rsc);
256  }
257 
258  for (gIter = all_rscs; gIter != NULL; gIter = gIter->next) {
259  pe_resource_t *listed_rsc = (pe_resource_t *) gIter->data;
260 
261  if (!pcmk_is_set(listed_rsc->flags, pe_rsc_provisional)) {
262  continue;
263  }
264 
265  pe_rsc_trace(rsc, "%s: Processing unallocated colocated %s", rsc->id, listed_rsc->id);
266  add_unallocated_utilization(all_utilization, listed_rsc, all_rscs, rsc);
267  }
268 
269  g_list_free(all_rscs);
270 
271  return all_utilization;
272 }
273 
274 void
276 {
277  CRM_CHECK(rsc && prefer && data_set, return);
278  if (!pcmk__str_eq(data_set->placement_strategy, "default", pcmk__str_casei)) {
279  GHashTableIter iter;
280  GList *colocated_rscs = NULL;
281  gboolean any_capable = FALSE;
282  pe_node_t *node = NULL;
283 
284  colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL);
285  if (colocated_rscs) {
286  GHashTable *unallocated_utilization = NULL;
287  char *rscs_id = crm_strdup_printf("%s and its colocated resources",
288  rsc->id);
289  pe_node_t *most_capable_node = NULL;
290 
291  unallocated_utilization = sum_unallocated_utilization(rsc, colocated_rscs);
292 
293  g_hash_table_iter_init(&iter, rsc->allowed_nodes);
294  while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
295  if (can_run_resources(node) == FALSE || node->weight < 0) {
296  continue;
297  }
298 
299  if (have_enough_capacity(node, rscs_id, unallocated_utilization)) {
300  any_capable = TRUE;
301  }
302 
303  if (most_capable_node == NULL ||
304  compare_capacity(node, most_capable_node) < 0) {
305  /* < 0 means 'node' is more capable */
306  most_capable_node = node;
307  }
308  }
309 
310  if (any_capable) {
311  g_hash_table_iter_init(&iter, rsc->allowed_nodes);
312  while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
313  if (can_run_resources(node) == FALSE || node->weight < 0) {
314  continue;
315  }
316 
317  if (have_enough_capacity(node, rscs_id, unallocated_utilization) == FALSE) {
318  pe_rsc_debug(rsc,
319  "Resource %s and its colocated resources"
320  " cannot be allocated to node %s: not enough capacity",
321  rsc->id, node->details->uname);
322  resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set);
323  }
324  }
325 
326  } else if (*prefer == NULL) {
327  *prefer = most_capable_node;
328  }
329 
330  if (unallocated_utilization) {
331  g_hash_table_destroy(unallocated_utilization);
332  }
333 
334  g_list_free(colocated_rscs);
335  free(rscs_id);
336  }
337 
338  if (any_capable == FALSE) {
339  g_hash_table_iter_init(&iter, rsc->allowed_nodes);
340  while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
341  if (can_run_resources(node) == FALSE || node->weight < 0) {
342  continue;
343  }
344 
345  if (have_enough_capacity(node, rsc->id, rsc->utilization) == FALSE) {
346  pe_rsc_debug(rsc,
347  "Resource %s cannot be allocated to node %s:"
348  " not enough capacity",
349  rsc->id, node->details->uname);
350  resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set);
351  }
352  }
353  }
354  pe__show_node_weights(true, rsc, "Post-utilization", rsc->allowed_nodes, data_set);
355  }
356 }
357 
358 #define VARIANT_GROUP 1
359 #include <lib/pengine/variant.h>
360 
361 static void
362 group_add_unallocated_utilization(GHashTable * all_utilization, pe_resource_t * rsc,
363  GList *all_rscs)
364 {
365  group_variant_data_t *group_data = NULL;
366 
367  get_group_variant_data(group_data, rsc);
368  if (group_data->colocated || pe_rsc_is_clone(rsc->parent)) {
369  GList *gIter = rsc->children;
370 
371  for (; gIter != NULL; gIter = gIter->next) {
372  pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
373 
374  if (pcmk_is_set(child_rsc->flags, pe_rsc_provisional) &&
375  g_list_find(all_rscs, child_rsc) == FALSE) {
376  native_add_unallocated_utilization(all_utilization, child_rsc);
377  }
378  }
379 
380  } else {
381  if (group_data->first_child &&
382  pcmk_is_set(group_data->first_child->flags, pe_rsc_provisional) &&
383  g_list_find(all_rscs, group_data->first_child) == FALSE) {
384  native_add_unallocated_utilization(all_utilization, group_data->first_child);
385  }
386  }
387 }
388 
389 
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:225
void process_utilization(pe_resource_t *rsc, pe_node_t **prefer, pe_working_set_t *data_set)
#define pe_rsc_debug(rsc, fmt, args...)
Definition: internal.h:19
char data[0]
Definition: cpg.c:55
#define INFINITY
Definition: crm.h:99
#define pe__show_node_weights(level, rsc, text, nodes, data_set)
Definition: internal.h:353
int pcmk__scan_min_int(const char *text, int *result, int minimum)
Definition: strings.c:127
GList * children
Definition: pe_types.h:377
void calculate_utilization(GHashTable *current_utilization, GHashTable *utilization, gboolean plus)
resource_alloc_functions_t * cmds
Definition: pe_types.h:334
xmlNode * xml
Definition: pe_types.h:324
void resource_location(pe_resource_t *rsc, pe_node_t *node, int score, const char *tag, pe_working_set_t *data_set)
Definition: utils.c:1691
gboolean can_run_resources(const pe_node_t *node)
#define pe_rsc_provisional
Definition: pe_types.h:258
#define crm_debug(fmt, args...)
Definition: logging.h:362
int compare_capacity(const pe_node_t *node1, const pe_node_t *node2)
GList *(* colocated_resources)(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *colocated_rscs)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:114
struct pe_node_shared_s * details
Definition: pe_types.h:244
unsigned long long flags
Definition: pe_types.h:348
GHashTable * utilization
Definition: pe_types.h:375
enum pe_obj_types variant
Definition: pe_types.h:331
const char * placement_strategy
Definition: pe_types.h:144
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:611
#define CRM_ASSERT(expr)
Definition: results.h:42
GHashTable * utilization
Definition: pe_types.h:235
#define pe_rsc_trace(rsc, fmt, args...)
Definition: internal.h:20
#define ID(x)
Definition: msg_xml.h:456
pe_resource_t * parent
Definition: pe_types.h:329
char * id
Definition: pe_types.h:322
GHashTable * allowed_nodes
Definition: pe_types.h:368