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 : }
|