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 General Public License version 2
7 : * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <sys/param.h>
13 : #include <crm/crm.h>
14 : #include <crm/cib.h>
15 : #include <crm/common/xml.h>
16 :
17 : #include <glib.h>
18 :
19 : #include <pacemaker-internal.h>
20 :
21 : #include "libpacemaker_private.h"
22 :
23 : // Convenience macros for logging action properties
24 :
25 : #define action_type_str(flags) \
26 : (pcmk_is_set((flags), pcmk_action_pseudo)? "pseudo-action" : "action")
27 :
28 : #define action_optional_str(flags) \
29 : (pcmk_is_set((flags), pcmk_action_optional)? "optional" : "required")
30 :
31 : #define action_runnable_str(flags) \
32 : (pcmk_is_set((flags), pcmk_action_runnable)? "runnable" : "unrunnable")
33 :
34 : #define action_node_str(a) \
35 : (((a)->node == NULL)? "no node" : (a)->node->details->uname)
36 :
37 : /*!
38 : * \internal
39 : * \brief Add an XML node tag for a specified ID
40 : *
41 : * \param[in] id Node UUID to add
42 : * \param[in,out] xml Parent XML tag to add to
43 : */
44 : static xmlNode*
45 0 : add_node_to_xml_by_id(const char *id, xmlNode *xml)
46 : {
47 : xmlNode *node_xml;
48 :
49 0 : node_xml = pcmk__xe_create(xml, PCMK_XE_NODE);
50 0 : crm_xml_add(node_xml, PCMK_XA_ID, id);
51 :
52 0 : return node_xml;
53 : }
54 :
55 : /*!
56 : * \internal
57 : * \brief Add an XML node tag for a specified node
58 : *
59 : * \param[in] node Node to add
60 : * \param[in,out] xml XML to add node to
61 : */
62 : static void
63 0 : add_node_to_xml(const pcmk_node_t *node, void *xml)
64 : {
65 0 : add_node_to_xml_by_id(node->details->id, (xmlNode *) xml);
66 0 : }
67 :
68 : /*!
69 : * \internal
70 : * \brief Count (optionally add to XML) nodes needing maintenance state update
71 : *
72 : * \param[in,out] xml Parent XML tag to add to, if any
73 : * \param[in] scheduler Scheduler data
74 : *
75 : * \return Count of nodes added
76 : * \note Only Pacemaker Remote nodes are considered currently
77 : */
78 : static int
79 0 : add_maintenance_nodes(xmlNode *xml, const pcmk_scheduler_t *scheduler)
80 : {
81 0 : xmlNode *maintenance = NULL;
82 0 : int count = 0;
83 :
84 0 : if (xml != NULL) {
85 0 : maintenance = pcmk__xe_create(xml, PCMK__XE_MAINTENANCE);
86 : }
87 0 : for (const GList *iter = scheduler->nodes;
88 0 : iter != NULL; iter = iter->next) {
89 0 : const pcmk_node_t *node = iter->data;
90 :
91 0 : if (pcmk__is_pacemaker_remote_node(node) &&
92 0 : (node->details->maintenance != node->details->remote_maintenance)) {
93 :
94 0 : if (maintenance != NULL) {
95 0 : crm_xml_add(add_node_to_xml_by_id(node->details->id,
96 : maintenance),
97 : PCMK__XA_NODE_IN_MAINTENANCE,
98 0 : (node->details->maintenance? "1" : "0"));
99 : }
100 0 : count++;
101 : }
102 : }
103 0 : crm_trace("%s %d nodes in need of maintenance mode update in state",
104 : ((maintenance == NULL)? "Counted" : "Added"), count);
105 0 : return count;
106 : }
107 :
108 : /*!
109 : * \internal
110 : * \brief Add pseudo action with nodes needing maintenance state update
111 : *
112 : * \param[in,out] scheduler Scheduler data
113 : */
114 : static void
115 0 : add_maintenance_update(pcmk_scheduler_t *scheduler)
116 : {
117 0 : pcmk_action_t *action = NULL;
118 :
119 0 : if (add_maintenance_nodes(NULL, scheduler) != 0) {
120 0 : action = get_pseudo_op(PCMK_ACTION_MAINTENANCE_NODES, scheduler);
121 0 : pcmk__set_action_flags(action, pcmk_action_always_in_graph);
122 : }
123 0 : }
124 :
125 : /*!
126 : * \internal
127 : * \brief Add XML with nodes that an action is expected to bring down
128 : *
129 : * If a specified action is expected to bring any nodes down, add an XML block
130 : * with their UUIDs. When a node is lost, this allows the controller to
131 : * determine whether it was expected.
132 : *
133 : * \param[in,out] xml Parent XML tag to add to
134 : * \param[in] action Action to check for downed nodes
135 : */
136 : static void
137 0 : add_downed_nodes(xmlNode *xml, const pcmk_action_t *action)
138 : {
139 0 : CRM_CHECK((xml != NULL) && (action != NULL) && (action->node != NULL),
140 : return);
141 :
142 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none)) {
143 :
144 : /* Shutdown makes the action's node down */
145 0 : xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
146 0 : add_node_to_xml_by_id(action->node->details->id, downed);
147 :
148 0 : } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
149 : pcmk__str_none)) {
150 :
151 : /* Fencing makes the action's node and any hosted guest nodes down */
152 0 : const char *fence = g_hash_table_lookup(action->meta,
153 : PCMK__META_STONITH_ACTION);
154 :
155 0 : if (pcmk__is_fencing_action(fence)) {
156 0 : xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
157 0 : add_node_to_xml_by_id(action->node->details->id, downed);
158 0 : pe_foreach_guest_node(action->node->details->data_set,
159 0 : action->node, add_node_to_xml, downed);
160 : }
161 :
162 0 : } else if (action->rsc && action->rsc->is_remote_node
163 0 : && pcmk__str_eq(action->task, PCMK_ACTION_STOP,
164 : pcmk__str_none)) {
165 :
166 : /* Stopping a remote connection resource makes connected node down,
167 : * unless it's part of a migration
168 : */
169 : GList *iter;
170 : pcmk_action_t *input;
171 0 : bool migrating = false;
172 :
173 0 : for (iter = action->actions_before; iter != NULL; iter = iter->next) {
174 0 : input = ((pcmk__related_action_t *) iter->data)->action;
175 0 : if ((input->rsc != NULL)
176 0 : && pcmk__str_eq(action->rsc->id, input->rsc->id, pcmk__str_none)
177 0 : && pcmk__str_eq(input->task, PCMK_ACTION_MIGRATE_FROM,
178 : pcmk__str_none)) {
179 0 : migrating = true;
180 0 : break;
181 : }
182 : }
183 0 : if (!migrating) {
184 0 : xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
185 0 : add_node_to_xml_by_id(action->rsc->id, downed);
186 : }
187 : }
188 : }
189 :
190 : /*!
191 : * \internal
192 : * \brief Create a transition graph operation key for a clone action
193 : *
194 : * \param[in] action Clone action
195 : * \param[in] interval_ms Action interval in milliseconds
196 : *
197 : * \return Newly allocated string with transition graph operation key
198 : */
199 : static char *
200 0 : clone_op_key(const pcmk_action_t *action, guint interval_ms)
201 : {
202 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
203 0 : const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
204 0 : const char *n_task = g_hash_table_lookup(action->meta,
205 : "notify_operation");
206 :
207 0 : CRM_LOG_ASSERT((n_type != NULL) && (n_task != NULL));
208 0 : return pcmk__notify_key(action->rsc->clone_name, n_type, n_task);
209 :
210 0 : } else if (action->cancel_task != NULL) {
211 0 : return pcmk__op_key(action->rsc->clone_name, action->cancel_task,
212 : interval_ms);
213 : } else {
214 0 : return pcmk__op_key(action->rsc->clone_name, action->task, interval_ms);
215 : }
216 : }
217 :
218 : /*!
219 : * \internal
220 : * \brief Add node details to transition graph action XML
221 : *
222 : * \param[in] action Scheduled action
223 : * \param[in,out] xml Transition graph action XML for \p action
224 : */
225 : static void
226 0 : add_node_details(const pcmk_action_t *action, xmlNode *xml)
227 : {
228 0 : pcmk_node_t *router_node = pcmk__connection_host_for_action(action);
229 :
230 0 : crm_xml_add(xml, PCMK__META_ON_NODE, action->node->details->uname);
231 0 : crm_xml_add(xml, PCMK__META_ON_NODE_UUID, action->node->details->id);
232 0 : if (router_node != NULL) {
233 0 : crm_xml_add(xml, PCMK__XA_ROUTER_NODE, router_node->details->uname);
234 : }
235 0 : }
236 :
237 : /*!
238 : * \internal
239 : * \brief Add resource details to transition graph action XML
240 : *
241 : * \param[in] action Scheduled action
242 : * \param[in,out] action_xml Transition graph action XML for \p action
243 : */
244 : static void
245 0 : add_resource_details(const pcmk_action_t *action, xmlNode *action_xml)
246 : {
247 0 : xmlNode *rsc_xml = NULL;
248 0 : const char *attr_list[] = {
249 : PCMK_XA_CLASS,
250 : PCMK_XA_PROVIDER,
251 : PCMK_XA_TYPE,
252 : };
253 :
254 : /* If a resource is locked to a node via PCMK_OPT_SHUTDOWN_LOCK, mark its
255 : * actions so the controller can preserve the lock when the action
256 : * completes.
257 : */
258 0 : if (pcmk__action_locks_rsc_to_node(action)) {
259 0 : crm_xml_add_ll(action_xml, PCMK_OPT_SHUTDOWN_LOCK,
260 0 : (long long) action->rsc->lock_time);
261 : }
262 :
263 : // List affected resource
264 :
265 0 : rsc_xml = pcmk__xe_create(action_xml,
266 0 : (const char *) action->rsc->xml->name);
267 0 : if (pcmk_is_set(action->rsc->flags, pcmk_rsc_removed)
268 0 : && (action->rsc->clone_name != NULL)) {
269 : /* Use the numbered instance name here, because if there is more
270 : * than one instance on a node, we need to make sure the command
271 : * goes to the right one.
272 : *
273 : * This is important even for anonymous clones, because the clone's
274 : * unique meta-attribute might have just been toggled from on to
275 : * off.
276 : */
277 0 : crm_debug("Using orphan clone name %s instead of %s",
278 : action->rsc->id, action->rsc->clone_name);
279 0 : crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->clone_name);
280 0 : crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
281 :
282 0 : } else if (!pcmk_is_set(action->rsc->flags, pcmk_rsc_unique)) {
283 0 : const char *xml_id = pcmk__xe_id(action->rsc->xml);
284 :
285 0 : crm_debug("Using anonymous clone name %s for %s (aka %s)",
286 : xml_id, action->rsc->id, action->rsc->clone_name);
287 :
288 : /* ID is what we'd like client to use
289 : * LONG_ID is what they might know it as instead
290 : *
291 : * LONG_ID is only strictly needed /here/ during the
292 : * transition period until all nodes in the cluster
293 : * are running the new software /and/ have rebooted
294 : * once (meaning that they've only ever spoken to a DC
295 : * supporting this feature).
296 : *
297 : * If anyone toggles the unique flag to 'on', the
298 : * 'instance free' name will correspond to an orphan
299 : * and fall into the clause above instead
300 : */
301 0 : crm_xml_add(rsc_xml, PCMK_XA_ID, xml_id);
302 0 : if ((action->rsc->clone_name != NULL)
303 0 : && !pcmk__str_eq(xml_id, action->rsc->clone_name,
304 : pcmk__str_none)) {
305 0 : crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->clone_name);
306 : } else {
307 0 : crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
308 : }
309 :
310 : } else {
311 0 : CRM_ASSERT(action->rsc->clone_name == NULL);
312 0 : crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->id);
313 : }
314 :
315 0 : for (int lpc = 0; lpc < PCMK__NELEM(attr_list); lpc++) {
316 0 : crm_xml_add(rsc_xml, attr_list[lpc],
317 0 : g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
318 : }
319 0 : }
320 :
321 : /*!
322 : * \internal
323 : * \brief Add action attributes to transition graph action XML
324 : *
325 : * \param[in,out] action Scheduled action
326 : * \param[in,out] action_xml Transition graph action XML for \p action
327 : */
328 : static void
329 0 : add_action_attributes(pcmk_action_t *action, xmlNode *action_xml)
330 : {
331 0 : xmlNode *args_xml = NULL;
332 :
333 : /* We create free-standing XML to start, so we can sort the attributes
334 : * before adding it to action_xml, which keeps the scheduler regression
335 : * test graphs comparable.
336 : */
337 0 : args_xml = pcmk__xe_create(NULL, PCMK__XE_ATTRIBUTES);
338 :
339 0 : crm_xml_add(args_xml, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
340 0 : g_hash_table_foreach(action->extra, hash2field, args_xml);
341 :
342 0 : if ((action->rsc != NULL) && (action->node != NULL)) {
343 : // Get the resource instance attributes, evaluated properly for node
344 0 : GHashTable *params = pe_rsc_params(action->rsc, action->node,
345 0 : action->rsc->cluster);
346 :
347 0 : pcmk__substitute_remote_addr(action->rsc, params);
348 :
349 0 : g_hash_table_foreach(params, hash2smartfield, args_xml);
350 :
351 0 : } else if ((action->rsc != NULL)
352 0 : && (action->rsc->variant <= pcmk_rsc_variant_primitive)) {
353 0 : GHashTable *params = pe_rsc_params(action->rsc, NULL,
354 0 : action->rsc->cluster);
355 :
356 0 : g_hash_table_foreach(params, hash2smartfield, args_xml);
357 : }
358 :
359 0 : g_hash_table_foreach(action->meta, hash2metafield, args_xml);
360 0 : if (action->rsc != NULL) {
361 0 : pcmk_resource_t *parent = action->rsc;
362 :
363 0 : while (parent != NULL) {
364 0 : parent->cmds->add_graph_meta(parent, args_xml);
365 0 : parent = parent->parent;
366 : }
367 :
368 0 : pcmk__add_guest_meta_to_xml(args_xml, action);
369 :
370 0 : } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)
371 0 : && (action->node != NULL)) {
372 : /* Pass the node's attributes as meta-attributes.
373 : *
374 : * @TODO: Determine whether it is still necessary to do this. It was
375 : * added in 33d99707, probably for the libfence-based implementation in
376 : * c9a90bd, which is no longer used.
377 : */
378 0 : g_hash_table_foreach(action->node->details->attrs, hash2metafield,
379 : args_xml);
380 : }
381 :
382 0 : sorted_xml(args_xml, action_xml, FALSE);
383 0 : free_xml(args_xml);
384 0 : }
385 :
386 : /*!
387 : * \internal
388 : * \brief Create the transition graph XML for a scheduled action
389 : *
390 : * \param[in,out] parent Parent XML element to add action to
391 : * \param[in,out] action Scheduled action
392 : * \param[in] skip_details If false, add action details as sub-elements
393 : * \param[in] scheduler Scheduler data
394 : */
395 : static void
396 0 : create_graph_action(xmlNode *parent, pcmk_action_t *action, bool skip_details,
397 : const pcmk_scheduler_t *scheduler)
398 : {
399 0 : bool needs_node_info = true;
400 0 : bool needs_maintenance_info = false;
401 0 : xmlNode *action_xml = NULL;
402 :
403 0 : if ((action == NULL) || (scheduler == NULL)) {
404 0 : return;
405 : }
406 :
407 : // Create the top-level element based on task
408 :
409 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) {
410 : /* All fences need node info; guest node fences are pseudo-events */
411 0 : if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
412 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
413 : } else {
414 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
415 : }
416 :
417 0 : } else if (pcmk__str_any_of(action->task,
418 : PCMK_ACTION_DO_SHUTDOWN,
419 : PCMK_ACTION_CLEAR_FAILCOUNT, NULL)) {
420 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
421 :
422 0 : } else if (pcmk__str_eq(action->task, PCMK_ACTION_LRM_DELETE,
423 : pcmk__str_none)) {
424 : // CIB-only clean-up for shutdown locks
425 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
426 0 : crm_xml_add(action_xml, PCMK__XA_MODE, PCMK__VALUE_CIB);
427 :
428 0 : } else if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
429 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_MAINTENANCE_NODES,
430 : pcmk__str_none)) {
431 0 : needs_maintenance_info = true;
432 : }
433 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
434 0 : needs_node_info = false;
435 :
436 : } else {
437 0 : action_xml = pcmk__xe_create(parent, PCMK__XE_RSC_OP);
438 : }
439 :
440 0 : crm_xml_add_int(action_xml, PCMK_XA_ID, action->id);
441 0 : crm_xml_add(action_xml, PCMK_XA_OPERATION, action->task);
442 :
443 0 : if ((action->rsc != NULL) && (action->rsc->clone_name != NULL)) {
444 0 : char *clone_key = NULL;
445 : guint interval_ms;
446 :
447 0 : if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
448 : &interval_ms) != pcmk_rc_ok) {
449 0 : interval_ms = 0;
450 : }
451 0 : clone_key = clone_op_key(action, interval_ms);
452 0 : crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, clone_key);
453 0 : crm_xml_add(action_xml, "internal_" PCMK__XA_OPERATION_KEY,
454 0 : action->uuid);
455 0 : free(clone_key);
456 : } else {
457 0 : crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, action->uuid);
458 : }
459 :
460 0 : if (needs_node_info && (action->node != NULL)) {
461 0 : add_node_details(action, action_xml);
462 0 : pcmk__insert_dup(action->meta, PCMK__META_ON_NODE,
463 0 : action->node->details->uname);
464 0 : pcmk__insert_dup(action->meta, PCMK__META_ON_NODE_UUID,
465 0 : action->node->details->id);
466 : }
467 :
468 0 : if (skip_details) {
469 0 : return;
470 : }
471 :
472 0 : if ((action->rsc != NULL)
473 0 : && !pcmk_is_set(action->flags, pcmk_action_pseudo)) {
474 :
475 : // This is a real resource action, so add resource details
476 0 : add_resource_details(action, action_xml);
477 : }
478 :
479 : /* List any attributes in effect */
480 0 : add_action_attributes(action, action_xml);
481 :
482 : /* List any nodes this action is expected to make down */
483 0 : if (needs_node_info && (action->node != NULL)) {
484 0 : add_downed_nodes(action_xml, action);
485 : }
486 :
487 0 : if (needs_maintenance_info) {
488 0 : add_maintenance_nodes(action_xml, scheduler);
489 : }
490 : }
491 :
492 : /*!
493 : * \internal
494 : * \brief Check whether an action should be added to the transition graph
495 : *
496 : * \param[in] action Action to check
497 : *
498 : * \return true if action should be added to graph, otherwise false
499 : */
500 : static bool
501 0 : should_add_action_to_graph(const pcmk_action_t *action)
502 : {
503 0 : if (!pcmk_is_set(action->flags, pcmk_action_runnable)) {
504 0 : crm_trace("Ignoring action %s (%d): unrunnable",
505 : action->uuid, action->id);
506 0 : return false;
507 : }
508 :
509 0 : if (pcmk_is_set(action->flags, pcmk_action_optional)
510 0 : && !pcmk_is_set(action->flags, pcmk_action_always_in_graph)) {
511 0 : crm_trace("Ignoring action %s (%d): optional",
512 : action->uuid, action->id);
513 0 : return false;
514 : }
515 :
516 : /* Actions for unmanaged resources should be excluded from the graph,
517 : * with the exception of monitors and cancellation of recurring monitors.
518 : */
519 0 : if ((action->rsc != NULL)
520 0 : && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
521 0 : && !pcmk__str_eq(action->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
522 :
523 : const char *interval_ms_s;
524 :
525 : /* A cancellation of a recurring monitor will get here because the task
526 : * is cancel rather than monitor, but the interval can still be used to
527 : * recognize it. The interval has been normalized to milliseconds by
528 : * this point, so a string comparison is sufficient.
529 : */
530 0 : interval_ms_s = g_hash_table_lookup(action->meta, PCMK_META_INTERVAL);
531 0 : if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) {
532 0 : crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)",
533 : action->uuid, action->id, action->rsc->id);
534 0 : return false;
535 : }
536 : }
537 :
538 : /* Always add pseudo-actions, fence actions, and shutdown actions (already
539 : * determined to be required and runnable by this point)
540 : */
541 0 : if (pcmk_is_set(action->flags, pcmk_action_pseudo)
542 0 : || pcmk__strcase_any_of(action->task, PCMK_ACTION_STONITH,
543 : PCMK_ACTION_DO_SHUTDOWN, NULL)) {
544 0 : return true;
545 : }
546 :
547 0 : if (action->node == NULL) {
548 0 : pcmk__sched_err("Skipping action %s (%d) "
549 : "because it was not assigned to a node (bug?)",
550 : action->uuid, action->id);
551 0 : pcmk__log_action("Unassigned", action, false);
552 0 : return false;
553 : }
554 :
555 0 : if (pcmk_is_set(action->flags, pcmk_action_on_dc)) {
556 0 : crm_trace("Action %s (%d) should be dumped: "
557 : "can run on DC instead of %s",
558 : action->uuid, action->id, pcmk__node_name(action->node));
559 :
560 0 : } else if (pcmk__is_guest_or_bundle_node(action->node)
561 0 : && !action->node->details->remote_requires_reset) {
562 0 : crm_trace("Action %s (%d) should be dumped: "
563 : "assuming will be runnable on guest %s",
564 : action->uuid, action->id, pcmk__node_name(action->node));
565 :
566 0 : } else if (!action->node->details->online) {
567 0 : pcmk__sched_err("Skipping action %s (%d) "
568 : "because it was scheduled for offline node (bug?)",
569 : action->uuid, action->id);
570 0 : pcmk__log_action("Offline node", action, false);
571 0 : return false;
572 :
573 0 : } else if (action->node->details->unclean) {
574 0 : pcmk__sched_err("Skipping action %s (%d) "
575 : "because it was scheduled for unclean node (bug?)",
576 : action->uuid, action->id);
577 0 : pcmk__log_action("Unclean node", action, false);
578 0 : return false;
579 : }
580 0 : return true;
581 : }
582 :
583 : /*!
584 : * \internal
585 : * \brief Check whether an ordering's flags can change an action
586 : *
587 : * \param[in] ordering Ordering to check
588 : *
589 : * \return true if ordering has flags that can change an action, false otherwise
590 : */
591 : static bool
592 0 : ordering_can_change_actions(const pcmk__related_action_t *ordering)
593 : {
594 0 : return pcmk_any_flags_set(ordering->type,
595 : ~(pcmk__ar_then_implies_first_graphed
596 : |pcmk__ar_first_implies_then_graphed
597 : |pcmk__ar_ordered));
598 : }
599 :
600 : /*!
601 : * \internal
602 : * \brief Check whether an action input should be in the transition graph
603 : *
604 : * \param[in] action Action to check
605 : * \param[in,out] input Action input to check
606 : *
607 : * \return true if input should be in graph, false otherwise
608 : * \note This function may not only check an input, but disable it under certian
609 : * circumstances (load or anti-colocation orderings that are not needed).
610 : */
611 : static bool
612 0 : should_add_input_to_graph(const pcmk_action_t *action,
613 : pcmk__related_action_t *input)
614 : {
615 0 : if (input->state == pe_link_dumped) {
616 0 : return true;
617 : }
618 :
619 0 : if ((uint32_t) input->type == pcmk__ar_none) {
620 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
621 : "ordering disabled",
622 : action->uuid, action->id,
623 : input->action->uuid, input->action->id);
624 0 : return false;
625 :
626 0 : } else if (!pcmk_is_set(input->action->flags, pcmk_action_runnable)
627 0 : && !ordering_can_change_actions(input)) {
628 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
629 : "optional and input unrunnable",
630 : action->uuid, action->id,
631 : input->action->uuid, input->action->id);
632 0 : return false;
633 :
634 0 : } else if (!pcmk_is_set(input->action->flags, pcmk_action_runnable)
635 0 : && pcmk_is_set(input->type, pcmk__ar_min_runnable)) {
636 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
637 : "minimum number of instances required but input unrunnable",
638 : action->uuid, action->id,
639 : input->action->uuid, input->action->id);
640 0 : return false;
641 :
642 0 : } else if (pcmk_is_set(input->type, pcmk__ar_unmigratable_then_blocks)
643 0 : && !pcmk_is_set(input->action->flags, pcmk_action_runnable)) {
644 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
645 : "input blocked if 'then' unmigratable",
646 : action->uuid, action->id,
647 : input->action->uuid, input->action->id);
648 0 : return false;
649 :
650 0 : } else if (pcmk_is_set(input->type, pcmk__ar_if_first_unmigratable)
651 0 : && pcmk_is_set(input->action->flags, pcmk_action_migratable)) {
652 0 : crm_trace("Ignoring %s (%d) input %s (%d): ordering applies "
653 : "only if input is unmigratable, but it is migratable",
654 : action->uuid, action->id,
655 : input->action->uuid, input->action->id);
656 0 : return false;
657 :
658 0 : } else if (((uint32_t) input->type == pcmk__ar_ordered)
659 0 : && pcmk_is_set(input->action->flags, pcmk_action_migratable)
660 0 : && pcmk__ends_with(input->action->uuid, "_stop_0")) {
661 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
662 : "optional but stop in migration",
663 : action->uuid, action->id,
664 : input->action->uuid, input->action->id);
665 0 : return false;
666 :
667 0 : } else if ((uint32_t) input->type == pcmk__ar_if_on_same_node_or_target) {
668 0 : pcmk_node_t *input_node = input->action->node;
669 :
670 0 : if ((action->rsc != NULL)
671 0 : && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO,
672 0 : pcmk__str_none)) {
673 :
674 0 : pcmk_node_t *assigned = action->rsc->allocated_to;
675 :
676 : /* For load_stopped -> migrate_to orderings, we care about where
677 : * the resource has been assigned, not where migrate_to will be
678 : * executed.
679 : */
680 0 : if (!pcmk__same_node(input_node, assigned)) {
681 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
682 : "migration target %s is not same as input node %s",
683 : action->uuid, action->id,
684 : input->action->uuid, input->action->id,
685 : (assigned? assigned->details->uname : "<none>"),
686 : (input_node? input_node->details->uname : "<none>"));
687 0 : input->type = (enum pe_ordering) pcmk__ar_none;
688 0 : return false;
689 : }
690 :
691 0 : } else if (!pcmk__same_node(input_node, action->node)) {
692 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
693 : "not on same node (%s vs %s)",
694 : action->uuid, action->id,
695 : input->action->uuid, input->action->id,
696 : (action->node? action->node->details->uname : "<none>"),
697 : (input_node? input_node->details->uname : "<none>"));
698 0 : input->type = (enum pe_ordering) pcmk__ar_none;
699 0 : return false;
700 :
701 0 : } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)) {
702 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
703 : "ordering optional",
704 : action->uuid, action->id,
705 : input->action->uuid, input->action->id);
706 0 : input->type = (enum pe_ordering) pcmk__ar_none;
707 0 : return false;
708 : }
709 :
710 0 : } else if ((uint32_t) input->type == pcmk__ar_if_required_on_same_node) {
711 0 : if (input->action->node && action->node
712 0 : && !pcmk__same_node(input->action->node, action->node)) {
713 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
714 : "not on same node (%s vs %s)",
715 : action->uuid, action->id,
716 : input->action->uuid, input->action->id,
717 : pcmk__node_name(action->node),
718 : pcmk__node_name(input->action->node));
719 0 : input->type = (enum pe_ordering) pcmk__ar_none;
720 0 : return false;
721 :
722 0 : } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)) {
723 0 : crm_trace("Ignoring %s (%d) input %s (%d): optional",
724 : action->uuid, action->id,
725 : input->action->uuid, input->action->id);
726 0 : input->type = (enum pe_ordering) pcmk__ar_none;
727 0 : return false;
728 : }
729 :
730 0 : } else if (input->action->rsc
731 0 : && input->action->rsc != action->rsc
732 0 : && pcmk_is_set(input->action->rsc->flags, pcmk_rsc_failed)
733 0 : && !pcmk_is_set(input->action->rsc->flags, pcmk_rsc_managed)
734 0 : && pcmk__ends_with(input->action->uuid, "_stop_0")
735 0 : && pcmk__is_clone(action->rsc)) {
736 0 : crm_warn("Ignoring requirement that %s complete before %s:"
737 : " unmanaged failed resources cannot prevent clone shutdown",
738 : input->action->uuid, action->uuid);
739 0 : return false;
740 :
741 0 : } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)
742 0 : && !pcmk_any_flags_set(input->action->flags,
743 : pcmk_action_always_in_graph
744 : |pcmk_action_added_to_graph)
745 0 : && !should_add_action_to_graph(input->action)) {
746 0 : crm_trace("Ignoring %s (%d) input %s (%d): "
747 : "input optional",
748 : action->uuid, action->id,
749 : input->action->uuid, input->action->id);
750 0 : return false;
751 : }
752 :
753 0 : crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s %#.6x",
754 : action->uuid, action->id, action_type_str(input->action->flags),
755 : input->action->uuid, input->action->id,
756 : action_node_str(input->action),
757 : action_runnable_str(input->action->flags),
758 : action_optional_str(input->action->flags), input->type);
759 0 : return true;
760 : }
761 :
762 : /*!
763 : * \internal
764 : * \brief Check whether an ordering creates an ordering loop
765 : *
766 : * \param[in] init_action "First" action in ordering
767 : * \param[in] action Callers should always set this the same as
768 : * \p init_action (this function may use a different
769 : * value for recursive calls)
770 : * \param[in,out] input Action wrapper for "then" action in ordering
771 : *
772 : * \return true if the ordering creates a loop, otherwise false
773 : */
774 : bool
775 0 : pcmk__graph_has_loop(const pcmk_action_t *init_action,
776 : const pcmk_action_t *action, pcmk__related_action_t *input)
777 : {
778 0 : bool has_loop = false;
779 :
780 0 : if (pcmk_is_set(input->action->flags, pcmk_action_detect_loop)) {
781 0 : crm_trace("Breaking tracking loop: %s@%s -> %s@%s (%#.6x)",
782 : input->action->uuid,
783 : input->action->node? input->action->node->details->uname : "",
784 : action->uuid,
785 : action->node? action->node->details->uname : "",
786 : input->type);
787 0 : return false;
788 : }
789 :
790 : // Don't need to check inputs that won't be used
791 0 : if (!should_add_input_to_graph(action, input)) {
792 0 : return false;
793 : }
794 :
795 0 : if (input->action == init_action) {
796 0 : crm_debug("Input loop found in %s@%s ->...-> %s@%s",
797 : action->uuid,
798 : action->node? action->node->details->uname : "",
799 : init_action->uuid,
800 : init_action->node? init_action->node->details->uname : "");
801 0 : return true;
802 : }
803 :
804 0 : pcmk__set_action_flags(input->action, pcmk_action_detect_loop);
805 :
806 0 : crm_trace("Checking inputs of action %s@%s input %s@%s (%#.6x)"
807 : "for graph loop with %s@%s ",
808 : action->uuid,
809 : action->node? action->node->details->uname : "",
810 : input->action->uuid,
811 : input->action->node? input->action->node->details->uname : "",
812 : input->type,
813 : init_action->uuid,
814 : init_action->node? init_action->node->details->uname : "");
815 :
816 : // Recursively check input itself for loops
817 0 : for (GList *iter = input->action->actions_before;
818 0 : iter != NULL; iter = iter->next) {
819 :
820 0 : if (pcmk__graph_has_loop(init_action, input->action,
821 0 : (pcmk__related_action_t *) iter->data)) {
822 : // Recursive call already logged a debug message
823 0 : has_loop = true;
824 0 : break;
825 : }
826 : }
827 :
828 0 : pcmk__clear_action_flags(input->action, pcmk_action_detect_loop);
829 :
830 0 : if (!has_loop) {
831 0 : crm_trace("No input loop found in %s@%s -> %s@%s (%#.6x)",
832 : input->action->uuid,
833 : input->action->node? input->action->node->details->uname : "",
834 : action->uuid,
835 : action->node? action->node->details->uname : "",
836 : input->type);
837 : }
838 0 : return has_loop;
839 : }
840 :
841 : /*!
842 : * \internal
843 : * \brief Create a synapse XML element for a transition graph
844 : *
845 : * \param[in] action Action that synapse is for
846 : * \param[in,out] scheduler Scheduler data containing graph
847 : *
848 : * \return Newly added XML element for new graph synapse
849 : */
850 : static xmlNode *
851 0 : create_graph_synapse(const pcmk_action_t *action, pcmk_scheduler_t *scheduler)
852 : {
853 0 : int synapse_priority = 0;
854 0 : xmlNode *syn = pcmk__xe_create(scheduler->graph, "synapse");
855 :
856 0 : crm_xml_add_int(syn, PCMK_XA_ID, scheduler->num_synapse);
857 0 : scheduler->num_synapse++;
858 :
859 0 : if (action->rsc != NULL) {
860 0 : synapse_priority = action->rsc->priority;
861 : }
862 0 : if (action->priority > synapse_priority) {
863 0 : synapse_priority = action->priority;
864 : }
865 0 : if (synapse_priority > 0) {
866 0 : crm_xml_add_int(syn, PCMK__XA_PRIORITY, synapse_priority);
867 : }
868 0 : return syn;
869 : }
870 :
871 : /*!
872 : * \internal
873 : * \brief Add an action to the transition graph XML if appropriate
874 : *
875 : * \param[in,out] data Action to possibly add
876 : * \param[in,out] user_data Scheduler data
877 : *
878 : * \note This will de-duplicate the action inputs, meaning that the
879 : * pcmk__related_action_t:type flags can no longer be relied on to retain
880 : * their original settings. That means this MUST be called after
881 : * pcmk__apply_orderings() is complete, and nothing after this should rely
882 : * on those type flags. (For example, some code looks for type equal to
883 : * some flag rather than whether the flag is set, and some code looks for
884 : * particular combinations of flags -- such code must be done before
885 : * pcmk__create_graph().)
886 : */
887 : static void
888 0 : add_action_to_graph(gpointer data, gpointer user_data)
889 : {
890 0 : pcmk_action_t *action = (pcmk_action_t *) data;
891 0 : pcmk_scheduler_t *scheduler = (pcmk_scheduler_t *) user_data;
892 :
893 0 : xmlNode *syn = NULL;
894 0 : xmlNode *set = NULL;
895 0 : xmlNode *in = NULL;
896 :
897 : /* If we haven't already, de-duplicate inputs (even if we won't be adding
898 : * the action to the graph, so that crm_simulate's dot graphs don't have
899 : * duplicates).
900 : */
901 0 : if (!pcmk_is_set(action->flags, pcmk_action_inputs_deduplicated)) {
902 0 : pcmk__deduplicate_action_inputs(action);
903 0 : pcmk__set_action_flags(action, pcmk_action_inputs_deduplicated);
904 : }
905 :
906 0 : if (pcmk_is_set(action->flags, pcmk_action_added_to_graph)
907 0 : || !should_add_action_to_graph(action)) {
908 0 : return; // Already added, or shouldn't be
909 : }
910 0 : pcmk__set_action_flags(action, pcmk_action_added_to_graph);
911 :
912 0 : crm_trace("Adding action %d (%s%s%s) to graph",
913 : action->id, action->uuid,
914 : ((action->node == NULL)? "" : " on "),
915 : ((action->node == NULL)? "" : action->node->details->uname));
916 :
917 0 : syn = create_graph_synapse(action, scheduler);
918 0 : set = pcmk__xe_create(syn, "action_set");
919 0 : in = pcmk__xe_create(syn, "inputs");
920 :
921 0 : create_graph_action(set, action, false, scheduler);
922 :
923 0 : for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
924 0 : pcmk__related_action_t *input = lpc->data;
925 :
926 0 : if (should_add_input_to_graph(action, input)) {
927 0 : xmlNode *input_xml = pcmk__xe_create(in, "trigger");
928 :
929 0 : input->state = pe_link_dumped;
930 0 : create_graph_action(input_xml, input->action, true, scheduler);
931 : }
932 : }
933 : }
934 :
935 : static int transition_id = -1;
936 :
937 : /*!
938 : * \internal
939 : * \brief Log a message after calculating a transition
940 : *
941 : * \param[in] filename Where transition input is stored
942 : */
943 : void
944 0 : pcmk__log_transition_summary(const char *filename)
945 : {
946 0 : if (was_processing_error || crm_config_error) {
947 0 : crm_err("Calculated transition %d (with errors)%s%s",
948 : transition_id,
949 : (filename == NULL)? "" : ", saving inputs in ",
950 : (filename == NULL)? "" : filename);
951 :
952 0 : } else if (was_processing_warning || crm_config_warning) {
953 0 : crm_warn("Calculated transition %d (with warnings)%s%s",
954 : transition_id,
955 : (filename == NULL)? "" : ", saving inputs in ",
956 : (filename == NULL)? "" : filename);
957 :
958 : } else {
959 0 : crm_notice("Calculated transition %d%s%s",
960 : transition_id,
961 : (filename == NULL)? "" : ", saving inputs in ",
962 : (filename == NULL)? "" : filename);
963 : }
964 0 : if (crm_config_error) {
965 0 : crm_notice("Configuration errors found during scheduler processing,"
966 : " please run \"crm_verify -L\" to identify issues");
967 : }
968 0 : }
969 :
970 : /*!
971 : * \internal
972 : * \brief Add a resource's actions to the transition graph
973 : *
974 : * \param[in,out] rsc Resource whose actions should be added
975 : */
976 : void
977 0 : pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc)
978 : {
979 0 : GList *iter = NULL;
980 :
981 0 : CRM_ASSERT(rsc != NULL);
982 0 : pcmk__rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
983 :
984 : // First add the resource's own actions
985 0 : g_list_foreach(rsc->actions, add_action_to_graph, rsc->cluster);
986 :
987 : // Then recursively add its children's actions (appropriate to variant)
988 0 : for (iter = rsc->children; iter != NULL; iter = iter->next) {
989 0 : pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
990 :
991 0 : child_rsc->cmds->add_actions_to_graph(child_rsc);
992 : }
993 0 : }
994 :
995 : /*!
996 : * \internal
997 : * \brief Create a transition graph with all cluster actions needed
998 : *
999 : * \param[in,out] scheduler Scheduler data
1000 : */
1001 : void
1002 0 : pcmk__create_graph(pcmk_scheduler_t *scheduler)
1003 : {
1004 0 : GList *iter = NULL;
1005 0 : const char *value = NULL;
1006 0 : long long limit = 0LL;
1007 0 : GHashTable *config_hash = scheduler->config_hash;
1008 :
1009 0 : transition_id++;
1010 0 : crm_trace("Creating transition graph %d", transition_id);
1011 :
1012 0 : scheduler->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
1013 :
1014 0 : value = pcmk__cluster_option(config_hash, PCMK_OPT_CLUSTER_DELAY);
1015 0 : crm_xml_add(scheduler->graph, PCMK_OPT_CLUSTER_DELAY, value);
1016 :
1017 0 : value = pcmk__cluster_option(config_hash, PCMK_OPT_STONITH_TIMEOUT);
1018 0 : crm_xml_add(scheduler->graph, PCMK_OPT_STONITH_TIMEOUT, value);
1019 :
1020 0 : crm_xml_add(scheduler->graph, "failed-stop-offset", "INFINITY");
1021 :
1022 0 : if (pcmk_is_set(scheduler->flags, pcmk_sched_start_failure_fatal)) {
1023 0 : crm_xml_add(scheduler->graph, "failed-start-offset", "INFINITY");
1024 : } else {
1025 0 : crm_xml_add(scheduler->graph, "failed-start-offset", "1");
1026 : }
1027 :
1028 0 : value = pcmk__cluster_option(config_hash, PCMK_OPT_BATCH_LIMIT);
1029 0 : crm_xml_add(scheduler->graph, PCMK_OPT_BATCH_LIMIT, value);
1030 :
1031 0 : crm_xml_add_int(scheduler->graph, "transition_id", transition_id);
1032 :
1033 0 : value = pcmk__cluster_option(config_hash, PCMK_OPT_MIGRATION_LIMIT);
1034 0 : if ((pcmk__scan_ll(value, &limit, 0LL) == pcmk_rc_ok) && (limit > 0)) {
1035 0 : crm_xml_add(scheduler->graph, PCMK_OPT_MIGRATION_LIMIT, value);
1036 : }
1037 :
1038 0 : if (scheduler->recheck_by > 0) {
1039 0 : char *recheck_epoch = NULL;
1040 :
1041 0 : recheck_epoch = crm_strdup_printf("%llu",
1042 0 : (long long) scheduler->recheck_by);
1043 0 : crm_xml_add(scheduler->graph, "recheck-by", recheck_epoch);
1044 0 : free(recheck_epoch);
1045 : }
1046 :
1047 : /* The following code will de-duplicate action inputs, so nothing past this
1048 : * should rely on the action input type flags retaining their original
1049 : * values.
1050 : */
1051 :
1052 : // Add resource actions to graph
1053 0 : for (iter = scheduler->resources; iter != NULL; iter = iter->next) {
1054 0 : pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
1055 :
1056 0 : pcmk__rsc_trace(rsc, "Processing actions for %s", rsc->id);
1057 0 : rsc->cmds->add_actions_to_graph(rsc);
1058 : }
1059 :
1060 : // Add pseudo-action for list of nodes with maintenance state update
1061 0 : add_maintenance_update(scheduler);
1062 :
1063 : // Add non-resource (node) actions
1064 0 : for (iter = scheduler->actions; iter != NULL; iter = iter->next) {
1065 0 : pcmk_action_t *action = (pcmk_action_t *) iter->data;
1066 :
1067 0 : if ((action->rsc != NULL)
1068 0 : && (action->node != NULL)
1069 0 : && action->node->details->shutdown
1070 0 : && !pcmk_is_set(action->rsc->flags, pcmk_rsc_maintenance)
1071 0 : && !pcmk_any_flags_set(action->flags,
1072 : pcmk_action_optional|pcmk_action_runnable)
1073 0 : && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
1074 : /* Eventually we should just ignore the 'fence' case, but for now
1075 : * it's the best way to detect (in CTS) when CIB resource updates
1076 : * are being lost.
1077 : */
1078 0 : if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)
1079 0 : || (scheduler->no_quorum_policy == pcmk_no_quorum_ignore)) {
1080 0 : const bool managed = pcmk_is_set(action->rsc->flags,
1081 : pcmk_rsc_managed);
1082 0 : const bool failed = pcmk_is_set(action->rsc->flags,
1083 : pcmk_rsc_failed);
1084 :
1085 0 : crm_crit("Cannot %s %s because of %s:%s%s (%s)",
1086 : action->node->details->unclean? "fence" : "shut down",
1087 : pcmk__node_name(action->node), action->rsc->id,
1088 : (managed? " blocked" : " unmanaged"),
1089 : (failed? " failed" : ""), action->uuid);
1090 : }
1091 : }
1092 :
1093 0 : add_action_to_graph((gpointer) action, (gpointer) scheduler);
1094 : }
1095 :
1096 0 : crm_log_xml_trace(scheduler->graph, "graph");
1097 0 : }
|