Line data Source code
1 : /*
2 : * Copyright 2004-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 Lesser General Public License
7 : * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <sys/param.h>
13 :
14 : #include <crm/crm.h>
15 : #include <crm/common/xml.h>
16 : #include <crm/common/cib_internal.h>
17 :
18 : #include <glib.h>
19 :
20 : #include <crm/pengine/internal.h>
21 : #include <pe_status_private.h>
22 :
23 : /*!
24 : * \brief Create a new object to hold scheduler data
25 : *
26 : * \return New, initialized scheduler data on success, else NULL (and set errno)
27 : * \note Only pcmk_scheduler_t objects created with this function (as opposed
28 : * to statically declared or directly allocated) should be used with the
29 : * functions in this library, to allow for future extensions to the
30 : * data type. The caller is responsible for freeing the memory with
31 : * pe_free_working_set() when the instance is no longer needed.
32 : */
33 : pcmk_scheduler_t *
34 38 : pe_new_working_set(void)
35 : {
36 38 : pcmk_scheduler_t *scheduler = calloc(1, sizeof(pcmk_scheduler_t));
37 :
38 38 : if (scheduler != NULL) {
39 37 : set_working_set_defaults(scheduler);
40 : }
41 38 : return scheduler;
42 : }
43 :
44 : /*!
45 : * \brief Free scheduler data
46 : *
47 : * \param[in,out] scheduler Scheduler data to free
48 : */
49 : void
50 0 : pe_free_working_set(pcmk_scheduler_t *scheduler)
51 : {
52 0 : if (scheduler != NULL) {
53 0 : pe_reset_working_set(scheduler);
54 0 : scheduler->priv = NULL;
55 0 : free(scheduler);
56 : }
57 0 : }
58 :
59 : #define XPATH_DEPRECATED_RULES \
60 : "//" PCMK_XE_OP_DEFAULTS "//" PCMK_XE_EXPRESSION \
61 : "|//" PCMK_XE_OP "//" PCMK_XE_EXPRESSION
62 :
63 : /*!
64 : * \internal
65 : * \brief Log a warning for deprecated rule syntax in operations
66 : *
67 : * \param[in] scheduler Scheduler data
68 : */
69 : static void
70 0 : check_for_deprecated_rules(pcmk_scheduler_t *scheduler)
71 : {
72 : // @COMPAT Drop this function when support for the syntax is dropped
73 0 : xmlNode *deprecated = get_xpath_object(XPATH_DEPRECATED_RULES,
74 : scheduler->input, LOG_NEVER);
75 :
76 0 : if (deprecated != NULL) {
77 0 : pcmk__warn_once(pcmk__wo_op_attr_expr,
78 : "Support for rules with node attribute expressions in "
79 : PCMK_XE_OP " or " PCMK_XE_OP_DEFAULTS " is deprecated "
80 : "and will be dropped in a future release");
81 : }
82 0 : }
83 :
84 : /*
85 : * Unpack everything
86 : * At the end you'll have:
87 : * - A list of nodes
88 : * - A list of resources (each with any dependencies on other resources)
89 : * - A list of constraints between resources and nodes
90 : * - A list of constraints between start/stop actions
91 : * - A list of nodes that need to be stonith'd
92 : * - A list of nodes that need to be shutdown
93 : * - A list of the possible stop/start actions (without dependencies)
94 : */
95 : gboolean
96 0 : cluster_status(pcmk_scheduler_t * scheduler)
97 : {
98 0 : const char *new_version = NULL;
99 0 : xmlNode *section = NULL;
100 :
101 0 : if ((scheduler == NULL) || (scheduler->input == NULL)) {
102 0 : return FALSE;
103 : }
104 :
105 0 : new_version = crm_element_value(scheduler->input, PCMK_XA_CRM_FEATURE_SET);
106 :
107 0 : if (pcmk__check_feature_set(new_version) != pcmk_rc_ok) {
108 0 : pcmk__config_err("Can't process CIB with feature set '%s' greater than our own '%s'",
109 : new_version, CRM_FEATURE_SET);
110 0 : return FALSE;
111 : }
112 :
113 0 : crm_trace("Beginning unpack");
114 :
115 0 : if (scheduler->failed != NULL) {
116 0 : free_xml(scheduler->failed);
117 : }
118 0 : scheduler->failed = pcmk__xe_create(NULL, "failed-ops");
119 :
120 0 : if (scheduler->now == NULL) {
121 0 : scheduler->now = crm_time_new(NULL);
122 : }
123 :
124 0 : if (scheduler->dc_uuid == NULL) {
125 0 : scheduler->dc_uuid = crm_element_value_copy(scheduler->input,
126 : PCMK_XA_DC_UUID);
127 : }
128 :
129 0 : if (pcmk__xe_attr_is_true(scheduler->input, PCMK_XA_HAVE_QUORUM)) {
130 0 : pcmk__set_scheduler_flags(scheduler, pcmk_sched_quorate);
131 : } else {
132 0 : pcmk__clear_scheduler_flags(scheduler, pcmk_sched_quorate);
133 : }
134 :
135 0 : scheduler->op_defaults = get_xpath_object("//" PCMK_XE_OP_DEFAULTS,
136 : scheduler->input, LOG_NEVER);
137 0 : check_for_deprecated_rules(scheduler);
138 :
139 0 : scheduler->rsc_defaults = get_xpath_object("//" PCMK_XE_RSC_DEFAULTS,
140 : scheduler->input, LOG_NEVER);
141 :
142 0 : section = get_xpath_object("//" PCMK_XE_CRM_CONFIG, scheduler->input,
143 : LOG_TRACE);
144 0 : unpack_config(section, scheduler);
145 :
146 0 : if (!pcmk_any_flags_set(scheduler->flags,
147 : pcmk_sched_location_only|pcmk_sched_quorate)
148 0 : && (scheduler->no_quorum_policy != pcmk_no_quorum_ignore)) {
149 0 : pcmk__sched_warn("Fencing and resource management disabled "
150 : "due to lack of quorum");
151 : }
152 :
153 0 : section = get_xpath_object("//" PCMK_XE_NODES, scheduler->input, LOG_TRACE);
154 0 : unpack_nodes(section, scheduler);
155 :
156 0 : section = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input,
157 : LOG_TRACE);
158 0 : if (!pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
159 0 : unpack_remote_nodes(section, scheduler);
160 : }
161 0 : unpack_resources(section, scheduler);
162 :
163 0 : section = get_xpath_object("//" PCMK_XE_TAGS, scheduler->input, LOG_NEVER);
164 0 : unpack_tags(section, scheduler);
165 :
166 0 : if (!pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
167 0 : section = get_xpath_object("//" PCMK_XE_STATUS, scheduler->input,
168 : LOG_TRACE);
169 0 : unpack_status(section, scheduler);
170 : }
171 :
172 0 : if (!pcmk_is_set(scheduler->flags, pcmk_sched_no_counts)) {
173 0 : for (GList *item = scheduler->resources; item != NULL;
174 0 : item = item->next) {
175 0 : ((pcmk_resource_t *) (item->data))->fns->count(item->data);
176 : }
177 0 : crm_trace("Cluster resource count: %d (%d disabled, %d blocked)",
178 : scheduler->ninstances, scheduler->disabled_resources,
179 : scheduler->blocked_resources);
180 : }
181 :
182 0 : pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_status);
183 0 : return TRUE;
184 : }
185 :
186 : /*!
187 : * \internal
188 : * \brief Free a list of pcmk_resource_t
189 : *
190 : * \param[in,out] resources List to free
191 : *
192 : * \note When the scheduler's resource list is freed, that includes the original
193 : * storage for the uname and id of any Pacemaker Remote nodes in the
194 : * scheduler's node list, so take care not to use those afterward.
195 : * \todo Refactor pcmk_node_t to strdup() the node name.
196 : */
197 : static void
198 0 : pe_free_resources(GList *resources)
199 : {
200 0 : pcmk_resource_t *rsc = NULL;
201 0 : GList *iterator = resources;
202 :
203 0 : while (iterator != NULL) {
204 0 : rsc = (pcmk_resource_t *) iterator->data;
205 0 : iterator = iterator->next;
206 0 : rsc->fns->free(rsc);
207 : }
208 0 : if (resources != NULL) {
209 0 : g_list_free(resources);
210 : }
211 0 : }
212 :
213 : static void
214 0 : pe_free_actions(GList *actions)
215 : {
216 0 : GList *iterator = actions;
217 :
218 0 : while (iterator != NULL) {
219 0 : pe_free_action(iterator->data);
220 0 : iterator = iterator->next;
221 : }
222 0 : if (actions != NULL) {
223 0 : g_list_free(actions);
224 : }
225 0 : }
226 :
227 : static void
228 0 : pe_free_nodes(GList *nodes)
229 : {
230 0 : for (GList *iterator = nodes; iterator != NULL; iterator = iterator->next) {
231 0 : pcmk_node_t *node = (pcmk_node_t *) iterator->data;
232 :
233 : // Shouldn't be possible, but to be safe ...
234 0 : if (node == NULL) {
235 0 : continue;
236 : }
237 0 : if (node->details == NULL) {
238 0 : free(node);
239 0 : continue;
240 : }
241 :
242 : /* This is called after pe_free_resources(), which means that we can't
243 : * use node->details->uname for Pacemaker Remote nodes.
244 : */
245 0 : crm_trace("Freeing node %s", (pcmk__is_pacemaker_remote_node(node)?
246 : "(guest or remote)" : pcmk__node_name(node)));
247 :
248 0 : if (node->details->attrs != NULL) {
249 0 : g_hash_table_destroy(node->details->attrs);
250 : }
251 0 : if (node->details->utilization != NULL) {
252 0 : g_hash_table_destroy(node->details->utilization);
253 : }
254 0 : if (node->details->digest_cache != NULL) {
255 0 : g_hash_table_destroy(node->details->digest_cache);
256 : }
257 0 : g_list_free(node->details->running_rsc);
258 0 : g_list_free(node->details->allocated_rsc);
259 0 : free(node->details);
260 0 : free(node);
261 : }
262 0 : if (nodes != NULL) {
263 0 : g_list_free(nodes);
264 : }
265 0 : }
266 :
267 : static void
268 0 : pe__free_ordering(GList *constraints)
269 : {
270 0 : GList *iterator = constraints;
271 :
272 0 : while (iterator != NULL) {
273 0 : pcmk__action_relation_t *order = iterator->data;
274 :
275 0 : iterator = iterator->next;
276 :
277 0 : free(order->task1);
278 0 : free(order->task2);
279 0 : free(order);
280 : }
281 0 : if (constraints != NULL) {
282 0 : g_list_free(constraints);
283 : }
284 0 : }
285 :
286 : static void
287 0 : pe__free_location(GList *constraints)
288 : {
289 0 : GList *iterator = constraints;
290 :
291 0 : while (iterator != NULL) {
292 0 : pcmk__location_t *cons = iterator->data;
293 :
294 0 : iterator = iterator->next;
295 :
296 0 : g_list_free_full(cons->nodes, free);
297 0 : free(cons->id);
298 0 : free(cons);
299 : }
300 0 : if (constraints != NULL) {
301 0 : g_list_free(constraints);
302 : }
303 0 : }
304 :
305 : /*!
306 : * \brief Reset scheduler data to defaults without freeing it or constraints
307 : *
308 : * \param[in,out] scheduler Scheduler data to reset
309 : *
310 : * \deprecated This function is deprecated as part of the API;
311 : * pe_reset_working_set() should be used instead.
312 : */
313 : void
314 0 : cleanup_calculations(pcmk_scheduler_t *scheduler)
315 : {
316 0 : if (scheduler == NULL) {
317 0 : return;
318 : }
319 :
320 0 : pcmk__clear_scheduler_flags(scheduler, pcmk_sched_have_status);
321 0 : if (scheduler->config_hash != NULL) {
322 0 : g_hash_table_destroy(scheduler->config_hash);
323 : }
324 :
325 0 : if (scheduler->singletons != NULL) {
326 0 : g_hash_table_destroy(scheduler->singletons);
327 : }
328 :
329 0 : if (scheduler->tickets) {
330 0 : g_hash_table_destroy(scheduler->tickets);
331 : }
332 :
333 0 : if (scheduler->template_rsc_sets) {
334 0 : g_hash_table_destroy(scheduler->template_rsc_sets);
335 : }
336 :
337 0 : if (scheduler->tags) {
338 0 : g_hash_table_destroy(scheduler->tags);
339 : }
340 :
341 0 : free(scheduler->dc_uuid);
342 :
343 0 : crm_trace("deleting resources");
344 0 : pe_free_resources(scheduler->resources);
345 :
346 0 : crm_trace("deleting actions");
347 0 : pe_free_actions(scheduler->actions);
348 :
349 0 : crm_trace("deleting nodes");
350 0 : pe_free_nodes(scheduler->nodes);
351 :
352 0 : pe__free_param_checks(scheduler);
353 0 : g_list_free(scheduler->stop_needed);
354 0 : free_xml(scheduler->graph);
355 0 : crm_time_free(scheduler->now);
356 0 : free_xml(scheduler->input);
357 0 : free_xml(scheduler->failed);
358 :
359 0 : set_working_set_defaults(scheduler);
360 :
361 0 : CRM_CHECK(scheduler->ordering_constraints == NULL,;
362 : );
363 0 : CRM_CHECK(scheduler->placement_constraints == NULL,;
364 : );
365 : }
366 :
367 : /*!
368 : * \brief Reset scheduler data to default state without freeing it
369 : *
370 : * \param[in,out] scheduler Scheduler data to reset
371 : */
372 : void
373 0 : pe_reset_working_set(pcmk_scheduler_t *scheduler)
374 : {
375 0 : if (scheduler == NULL) {
376 0 : return;
377 : }
378 :
379 0 : crm_trace("Deleting %d ordering constraints",
380 : g_list_length(scheduler->ordering_constraints));
381 0 : pe__free_ordering(scheduler->ordering_constraints);
382 0 : scheduler->ordering_constraints = NULL;
383 :
384 0 : crm_trace("Deleting %d location constraints",
385 : g_list_length(scheduler->placement_constraints));
386 0 : pe__free_location(scheduler->placement_constraints);
387 0 : scheduler->placement_constraints = NULL;
388 :
389 0 : crm_trace("Deleting %d colocation constraints",
390 : g_list_length(scheduler->colocation_constraints));
391 0 : g_list_free_full(scheduler->colocation_constraints, free);
392 0 : scheduler->colocation_constraints = NULL;
393 :
394 0 : crm_trace("Deleting %d ticket constraints",
395 : g_list_length(scheduler->ticket_constraints));
396 0 : g_list_free_full(scheduler->ticket_constraints, free);
397 0 : scheduler->ticket_constraints = NULL;
398 :
399 0 : cleanup_calculations(scheduler);
400 : }
401 :
402 : void
403 74 : set_working_set_defaults(pcmk_scheduler_t *scheduler)
404 : {
405 74 : void *priv = scheduler->priv;
406 :
407 74 : memset(scheduler, 0, sizeof(pcmk_scheduler_t));
408 :
409 74 : scheduler->priv = priv;
410 74 : scheduler->order_id = 1;
411 74 : scheduler->action_id = 1;
412 74 : scheduler->no_quorum_policy = pcmk_no_quorum_stop;
413 :
414 74 : scheduler->flags = 0x0ULL;
415 :
416 74 : pcmk__set_scheduler_flags(scheduler,
417 : pcmk_sched_symmetric_cluster
418 : |pcmk_sched_stop_removed_resources
419 : |pcmk_sched_cancel_removed_actions);
420 74 : if (!strcmp(PCMK__CONCURRENT_FENCING_DEFAULT, PCMK_VALUE_TRUE)) {
421 0 : pcmk__set_scheduler_flags(scheduler, pcmk_sched_concurrent_fencing);
422 : }
423 74 : }
424 :
425 : pcmk_resource_t *
426 0 : pe_find_resource(GList *rsc_list, const char *id)
427 : {
428 0 : return pe_find_resource_with_flags(rsc_list, id, pcmk_rsc_match_history);
429 : }
430 :
431 : pcmk_resource_t *
432 0 : pe_find_resource_with_flags(GList *rsc_list, const char *id, enum pe_find flags)
433 : {
434 0 : GList *rIter = NULL;
435 :
436 0 : for (rIter = rsc_list; id && rIter; rIter = rIter->next) {
437 0 : pcmk_resource_t *parent = rIter->data;
438 :
439 : pcmk_resource_t *match =
440 0 : parent->fns->find_rsc(parent, id, NULL, flags);
441 0 : if (match != NULL) {
442 0 : return match;
443 : }
444 : }
445 0 : crm_trace("No match for %s", id);
446 0 : return NULL;
447 : }
448 :
449 : /*!
450 : * \brief Find a node by name or ID in a list of nodes
451 : *
452 : * \param[in] nodes List of nodes (as pcmk_node_t*)
453 : * \param[in] id If not NULL, ID of node to find
454 : * \param[in] node_name If not NULL, name of node to find
455 : *
456 : * \return Node from \p nodes that matches \p id if any,
457 : * otherwise node from \p nodes that matches \p uname if any,
458 : * otherwise NULL
459 : */
460 : pcmk_node_t *
461 318 : pe_find_node_any(const GList *nodes, const char *id, const char *uname)
462 : {
463 318 : pcmk_node_t *match = NULL;
464 :
465 318 : if (id != NULL) {
466 312 : match = pe_find_node_id(nodes, id);
467 : }
468 318 : if ((match == NULL) && (uname != NULL)) {
469 7 : match = pcmk__find_node_in_list(nodes, uname);
470 : }
471 318 : return match;
472 : }
473 :
474 : /*!
475 : * \brief Find a node by ID in a list of nodes
476 : *
477 : * \param[in] nodes List of nodes (as pcmk_node_t*)
478 : * \param[in] id ID of node to find
479 : *
480 : * \return Node from \p nodes that matches \p id if any, otherwise NULL
481 : */
482 : pcmk_node_t *
483 319 : pe_find_node_id(const GList *nodes, const char *id)
484 : {
485 520 : for (const GList *iter = nodes; iter != NULL; iter = iter->next) {
486 509 : pcmk_node_t *node = (pcmk_node_t *) iter->data;
487 :
488 : /* @TODO Whether node IDs should be considered case-sensitive should
489 : * probably depend on the node type, so functionizing the comparison
490 : * would be worthwhile
491 : */
492 509 : if (pcmk__str_eq(node->details->id, id, pcmk__str_casei)) {
493 308 : return node;
494 : }
495 : }
496 11 : return NULL;
497 : }
498 :
499 : // Deprecated functions kept only for backward API compatibility
500 : // LCOV_EXCL_START
501 :
502 : #include <crm/pengine/status_compat.h>
503 :
504 : /*!
505 : * \brief Find a node by name in a list of nodes
506 : *
507 : * \param[in] nodes List of nodes (as pcmk_node_t*)
508 : * \param[in] node_name Name of node to find
509 : *
510 : * \return Node from \p nodes that matches \p node_name if any, otherwise NULL
511 : */
512 : pcmk_node_t *
513 : pe_find_node(const GList *nodes, const char *node_name)
514 : {
515 : return pcmk__find_node_in_list(nodes, node_name);
516 : }
517 :
518 : // LCOV_EXCL_STOP
519 : // End deprecated API
|