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 <inttypes.h> // PRIx32
13 : #include <stdbool.h>
14 : #include <glib.h>
15 :
16 : #include <crm/crm.h>
17 : #include <pacemaker-internal.h>
18 : #include "libpacemaker_private.h"
19 :
20 : enum pe_order_kind {
21 : pe_order_kind_optional,
22 : pe_order_kind_mandatory,
23 : pe_order_kind_serialize,
24 : };
25 :
26 : enum ordering_symmetry {
27 : ordering_asymmetric, // the only relation in an asymmetric ordering
28 : ordering_symmetric, // the normal relation in a symmetric ordering
29 : ordering_symmetric_inverse, // the inverse relation in a symmetric ordering
30 : };
31 :
32 : #define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do { \
33 : __rsc = pcmk__find_constraint_resource(scheduler->resources, \
34 : __name); \
35 : if (__rsc == NULL) { \
36 : pcmk__config_err("%s: No resource found for %s", __set, __name);\
37 : return pcmk_rc_unpack_error; \
38 : } \
39 : } while (0)
40 :
41 : static const char *
42 0 : invert_action(const char *action)
43 : {
44 0 : if (pcmk__str_eq(action, PCMK_ACTION_START, pcmk__str_none)) {
45 0 : return PCMK_ACTION_STOP;
46 :
47 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_STOP, pcmk__str_none)) {
48 0 : return PCMK_ACTION_START;
49 :
50 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
51 0 : return PCMK_ACTION_DEMOTE;
52 :
53 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
54 0 : return PCMK_ACTION_PROMOTE;
55 :
56 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_PROMOTED, pcmk__str_none)) {
57 0 : return PCMK_ACTION_DEMOTED;
58 :
59 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_DEMOTED, pcmk__str_none)) {
60 0 : return PCMK_ACTION_PROMOTED;
61 :
62 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_RUNNING, pcmk__str_none)) {
63 0 : return PCMK_ACTION_STOPPED;
64 :
65 0 : } else if (pcmk__str_eq(action, PCMK_ACTION_STOPPED, pcmk__str_none)) {
66 0 : return PCMK_ACTION_RUNNING;
67 : }
68 0 : pcmk__config_warn("Unknown action '%s' specified in order constraint",
69 : action);
70 0 : return NULL;
71 : }
72 :
73 : static enum pe_order_kind
74 0 : get_ordering_type(const xmlNode *xml_obj)
75 : {
76 0 : enum pe_order_kind kind_e = pe_order_kind_mandatory;
77 0 : const char *kind = crm_element_value(xml_obj, PCMK_XA_KIND);
78 :
79 0 : if (kind == NULL) {
80 0 : const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
81 :
82 0 : kind_e = pe_order_kind_mandatory;
83 :
84 0 : if (score) {
85 : // @COMPAT deprecated informally since 1.0.7, formally since 2.0.1
86 0 : int score_i = char2score(score);
87 :
88 0 : if (score_i == 0) {
89 0 : kind_e = pe_order_kind_optional;
90 : }
91 0 : pcmk__warn_once(pcmk__wo_order_score,
92 : "Support for '" PCMK_XA_SCORE "' in "
93 : PCMK_XE_RSC_ORDER " is deprecated and will be "
94 : "removed in a future release "
95 : "(use '" PCMK_XA_KIND "' instead)");
96 : }
97 :
98 0 : } else if (pcmk__str_eq(kind, PCMK_VALUE_MANDATORY, pcmk__str_none)) {
99 0 : kind_e = pe_order_kind_mandatory;
100 :
101 0 : } else if (pcmk__str_eq(kind, PCMK_VALUE_OPTIONAL, pcmk__str_none)) {
102 0 : kind_e = pe_order_kind_optional;
103 :
104 0 : } else if (pcmk__str_eq(kind, PCMK_VALUE_SERIALIZE, pcmk__str_none)) {
105 0 : kind_e = pe_order_kind_serialize;
106 :
107 : } else {
108 0 : pcmk__config_err("Resetting '" PCMK_XA_KIND "' for constraint %s to "
109 : "'" PCMK_VALUE_MANDATORY "' because '%s' is not valid",
110 : pcmk__s(pcmk__xe_id(xml_obj), "missing ID"), kind);
111 : }
112 0 : return kind_e;
113 : }
114 :
115 : /*!
116 : * \internal
117 : * \brief Get ordering symmetry from XML
118 : *
119 : * \param[in] xml_obj Ordering XML
120 : * \param[in] parent_kind Default ordering kind
121 : * \param[in] parent_symmetrical_s Parent element's \c PCMK_XA_SYMMETRICAL
122 : * setting, if any
123 : *
124 : * \retval ordering_symmetric Ordering is symmetric
125 : * \retval ordering_asymmetric Ordering is asymmetric
126 : */
127 : static enum ordering_symmetry
128 0 : get_ordering_symmetry(const xmlNode *xml_obj, enum pe_order_kind parent_kind,
129 : const char *parent_symmetrical_s)
130 : {
131 0 : int rc = pcmk_rc_ok;
132 0 : bool symmetric = false;
133 0 : enum pe_order_kind kind = parent_kind; // Default to parent's kind
134 :
135 : // Check ordering XML for explicit kind
136 0 : if ((crm_element_value(xml_obj, PCMK_XA_KIND) != NULL)
137 0 : || (crm_element_value(xml_obj, PCMK_XA_SCORE) != NULL)) {
138 0 : kind = get_ordering_type(xml_obj);
139 : }
140 :
141 : // Check ordering XML (and parent) for explicit PCMK_XA_SYMMETRICAL setting
142 0 : rc = pcmk__xe_get_bool_attr(xml_obj, PCMK_XA_SYMMETRICAL, &symmetric);
143 :
144 0 : if (rc != pcmk_rc_ok && parent_symmetrical_s != NULL) {
145 0 : symmetric = crm_is_true(parent_symmetrical_s);
146 0 : rc = pcmk_rc_ok;
147 : }
148 :
149 0 : if (rc == pcmk_rc_ok) {
150 0 : if (symmetric) {
151 0 : if (kind == pe_order_kind_serialize) {
152 0 : pcmk__config_warn("Ignoring " PCMK_XA_SYMMETRICAL
153 : " for '%s' because not valid with "
154 : PCMK_XA_KIND " of '" PCMK_VALUE_SERIALIZE "'",
155 : pcmk__xe_id(xml_obj));
156 : } else {
157 0 : return ordering_symmetric;
158 : }
159 : }
160 0 : return ordering_asymmetric;
161 : }
162 :
163 : // Use default symmetry
164 0 : if (kind == pe_order_kind_serialize) {
165 0 : return ordering_asymmetric;
166 : }
167 0 : return ordering_symmetric;
168 : }
169 :
170 : /*!
171 : * \internal
172 : * \brief Get ordering flags appropriate to ordering kind
173 : *
174 : * \param[in] kind Ordering kind
175 : * \param[in] first Action name for 'first' action
176 : * \param[in] symmetry This ordering's symmetry role
177 : *
178 : * \return Minimal ordering flags appropriate to \p kind
179 : */
180 : static uint32_t
181 0 : ordering_flags_for_kind(enum pe_order_kind kind, const char *first,
182 : enum ordering_symmetry symmetry)
183 : {
184 0 : uint32_t flags = pcmk__ar_none; // so we trace-log all flags set
185 :
186 0 : switch (kind) {
187 0 : case pe_order_kind_optional:
188 0 : pcmk__set_relation_flags(flags, pcmk__ar_ordered);
189 0 : break;
190 :
191 0 : case pe_order_kind_serialize:
192 : /* This flag is not used anywhere directly but means the relation
193 : * will not match an equality comparison against pcmk__ar_none or
194 : * pcmk__ar_ordered.
195 : */
196 0 : pcmk__set_relation_flags(flags, pcmk__ar_serialize);
197 0 : break;
198 :
199 0 : case pe_order_kind_mandatory:
200 0 : pcmk__set_relation_flags(flags, pcmk__ar_ordered);
201 : switch (symmetry) {
202 0 : case ordering_asymmetric:
203 0 : pcmk__set_relation_flags(flags, pcmk__ar_asymmetric);
204 0 : break;
205 :
206 0 : case ordering_symmetric:
207 0 : pcmk__set_relation_flags(flags,
208 : pcmk__ar_first_implies_then);
209 0 : if (pcmk__strcase_any_of(first, PCMK_ACTION_START,
210 : PCMK_ACTION_PROMOTE, NULL)) {
211 0 : pcmk__set_relation_flags(flags,
212 : pcmk__ar_unrunnable_first_blocks);
213 : }
214 0 : break;
215 :
216 0 : case ordering_symmetric_inverse:
217 0 : pcmk__set_relation_flags(flags,
218 : pcmk__ar_then_implies_first);
219 0 : break;
220 : }
221 0 : break;
222 : }
223 0 : return flags;
224 : }
225 :
226 : /*!
227 : * \internal
228 : * \brief Find resource corresponding to ID specified in ordering
229 : *
230 : * \param[in] xml Ordering XML
231 : * \param[in] resource_attr XML attribute name for resource ID
232 : * \param[in] instance_attr XML attribute name for instance number.
233 : * This option is deprecated and will be removed in a
234 : * future release.
235 : * \param[in] scheduler Scheduler data
236 : *
237 : * \return Resource corresponding to \p id, or NULL if none
238 : */
239 : static pcmk_resource_t *
240 0 : get_ordering_resource(const xmlNode *xml, const char *resource_attr,
241 : const char *instance_attr,
242 : const pcmk_scheduler_t *scheduler)
243 : {
244 : // @COMPAT: instance_attr and instance_id variables deprecated since 2.1.5
245 0 : pcmk_resource_t *rsc = NULL;
246 0 : const char *rsc_id = crm_element_value(xml, resource_attr);
247 0 : const char *instance_id = crm_element_value(xml, instance_attr);
248 :
249 0 : if (rsc_id == NULL) {
250 0 : pcmk__config_err("Ignoring constraint '%s' without %s",
251 : pcmk__xe_id(xml), resource_attr);
252 0 : return NULL;
253 : }
254 :
255 0 : rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
256 0 : if (rsc == NULL) {
257 0 : pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
258 : "does not exist", pcmk__xe_id(xml), rsc_id);
259 0 : return NULL;
260 : }
261 :
262 0 : if (instance_id != NULL) {
263 0 : pcmk__warn_once(pcmk__wo_order_inst,
264 : "Support for " PCMK__XA_FIRST_INSTANCE " and "
265 : PCMK__XA_THEN_INSTANCE " is deprecated and will be "
266 : "removed in a future release.");
267 :
268 0 : if (!pcmk__is_clone(rsc)) {
269 0 : pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
270 : "is not a clone but instance '%s' was requested",
271 : pcmk__xe_id(xml), rsc_id, instance_id);
272 0 : return NULL;
273 : }
274 0 : rsc = find_clone_instance(rsc, instance_id);
275 0 : if (rsc == NULL) {
276 0 : pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
277 : "does not have an instance '%s'",
278 : pcmk__xe_id(xml), rsc_id, instance_id);
279 0 : return NULL;
280 : }
281 : }
282 0 : return rsc;
283 : }
284 :
285 : /*!
286 : * \internal
287 : * \brief Determine minimum number of 'first' instances required in ordering
288 : *
289 : * \param[in] rsc 'First' resource in ordering
290 : * \param[in] xml Ordering XML
291 : *
292 : * \return Minimum 'first' instances required (or 0 if not applicable)
293 : */
294 : static int
295 0 : get_minimum_first_instances(const pcmk_resource_t *rsc, const xmlNode *xml)
296 : {
297 0 : const char *clone_min = NULL;
298 0 : bool require_all = false;
299 :
300 0 : if (!pcmk__is_clone(rsc)) {
301 0 : return 0;
302 : }
303 :
304 0 : clone_min = g_hash_table_lookup(rsc->meta, PCMK_META_CLONE_MIN);
305 0 : if (clone_min != NULL) {
306 0 : int clone_min_int = 0;
307 :
308 0 : pcmk__scan_min_int(clone_min, &clone_min_int, 0);
309 0 : return clone_min_int;
310 : }
311 :
312 : /* @COMPAT 1.1.13:
313 : * PCMK_XA_REQUIRE_ALL=PCMK_VALUE_FALSE is deprecated equivalent of
314 : * PCMK_META_CLONE_MIN=1
315 : */
316 0 : if (pcmk__xe_get_bool_attr(xml, PCMK_XA_REQUIRE_ALL,
317 : &require_all) != ENODATA) {
318 0 : pcmk__warn_once(pcmk__wo_require_all,
319 : "Support for " PCMK_XA_REQUIRE_ALL " in ordering "
320 : "constraints is deprecated and will be removed in a "
321 : "future release (use " PCMK_META_CLONE_MIN " clone "
322 : "meta-attribute instead)");
323 0 : if (!require_all) {
324 0 : return 1;
325 : }
326 : }
327 :
328 0 : return 0;
329 : }
330 :
331 : /*!
332 : * \internal
333 : * \brief Create orderings for a constraint with \c PCMK_META_CLONE_MIN > 0
334 : *
335 : * \param[in] id Ordering ID
336 : * \param[in,out] rsc_first 'First' resource in ordering (a clone)
337 : * \param[in] action_first 'First' action in ordering
338 : * \param[in] rsc_then 'Then' resource in ordering
339 : * \param[in] action_then 'Then' action in ordering
340 : * \param[in] flags Ordering flags
341 : * \param[in] clone_min Minimum required instances of 'first'
342 : */
343 : static void
344 0 : clone_min_ordering(const char *id,
345 : pcmk_resource_t *rsc_first, const char *action_first,
346 : pcmk_resource_t *rsc_then, const char *action_then,
347 : uint32_t flags, int clone_min)
348 : {
349 : // Create a pseudo-action for when the minimum instances are active
350 0 : char *task = crm_strdup_printf(PCMK_ACTION_CLONE_ONE_OR_MORE ":%s", id);
351 0 : pcmk_action_t *clone_min_met = get_pseudo_op(task, rsc_first->cluster);
352 :
353 0 : free(task);
354 :
355 : /* Require the pseudo-action to have the required number of actions to be
356 : * considered runnable before allowing the pseudo-action to be runnable.
357 : */
358 0 : clone_min_met->required_runnable_before = clone_min;
359 0 : pcmk__set_action_flags(clone_min_met, pcmk_action_min_runnable);
360 :
361 : // Order the actions for each clone instance before the pseudo-action
362 0 : for (GList *iter = rsc_first->children; iter != NULL; iter = iter->next) {
363 0 : pcmk_resource_t *child = iter->data;
364 :
365 0 : pcmk__new_ordering(child, pcmk__op_key(child->id, action_first, 0),
366 : NULL, NULL, NULL, clone_min_met,
367 : pcmk__ar_min_runnable
368 : |pcmk__ar_first_implies_then_graphed,
369 : rsc_first->cluster);
370 : }
371 :
372 : // Order "then" action after the pseudo-action (if runnable)
373 0 : pcmk__new_ordering(NULL, NULL, clone_min_met, rsc_then,
374 0 : pcmk__op_key(rsc_then->id, action_then, 0),
375 : NULL, flags|pcmk__ar_unrunnable_first_blocks,
376 : rsc_first->cluster);
377 0 : }
378 :
379 : /*!
380 : * \internal
381 : * \brief Update ordering flags for restart-type=restart
382 : *
383 : * \param[in] rsc 'Then' resource in ordering
384 : * \param[in] kind Ordering kind
385 : * \param[in] flag Ordering flag to set (when applicable)
386 : * \param[in,out] flags Ordering flag set to update
387 : *
388 : * \compat The \c PCMK__META_RESTART_TYPE resource meta-attribute is deprecated.
389 : * Eventually, it will be removed, and \c pe_restart_ignore will be the
390 : * only behavior, at which time this can just be removed entirely.
391 : */
392 : #define handle_restart_type(rsc, kind, flag, flags) do { \
393 : if (((kind) == pe_order_kind_optional) \
394 : && ((rsc)->restart_type == pe_restart_restart)) { \
395 : pcmk__set_relation_flags((flags), (flag)); \
396 : } \
397 : } while (0)
398 :
399 : /*!
400 : * \internal
401 : * \brief Create new ordering for inverse of symmetric constraint
402 : *
403 : * \param[in] id Ordering ID (for logging only)
404 : * \param[in] kind Ordering kind
405 : * \param[in] rsc_first 'First' resource in ordering (a clone)
406 : * \param[in] action_first 'First' action in ordering
407 : * \param[in,out] rsc_then 'Then' resource in ordering
408 : * \param[in] action_then 'Then' action in ordering
409 : */
410 : static void
411 0 : inverse_ordering(const char *id, enum pe_order_kind kind,
412 : pcmk_resource_t *rsc_first, const char *action_first,
413 : pcmk_resource_t *rsc_then, const char *action_then)
414 : {
415 0 : action_then = invert_action(action_then);
416 0 : action_first = invert_action(action_first);
417 0 : if ((action_then == NULL) || (action_first == NULL)) {
418 0 : pcmk__config_warn("Cannot invert constraint '%s' "
419 : "(please specify inverse manually)", id);
420 : } else {
421 0 : uint32_t flags = ordering_flags_for_kind(kind, action_first,
422 : ordering_symmetric_inverse);
423 :
424 0 : handle_restart_type(rsc_then, kind, pcmk__ar_then_implies_first, flags);
425 0 : pcmk__order_resource_actions(rsc_then, action_then, rsc_first,
426 : action_first, flags);
427 : }
428 0 : }
429 :
430 : static void
431 0 : unpack_simple_rsc_order(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
432 : {
433 0 : pcmk_resource_t *rsc_then = NULL;
434 0 : pcmk_resource_t *rsc_first = NULL;
435 0 : int min_required_before = 0;
436 0 : enum pe_order_kind kind = pe_order_kind_mandatory;
437 0 : uint32_t flags = pcmk__ar_none;
438 : enum ordering_symmetry symmetry;
439 :
440 0 : const char *action_then = NULL;
441 0 : const char *action_first = NULL;
442 0 : const char *id = NULL;
443 :
444 0 : CRM_CHECK(xml_obj != NULL, return);
445 :
446 0 : id = crm_element_value(xml_obj, PCMK_XA_ID);
447 0 : if (id == NULL) {
448 0 : pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
449 : xml_obj->name);
450 0 : return;
451 : }
452 :
453 0 : rsc_first = get_ordering_resource(xml_obj, PCMK_XA_FIRST,
454 : PCMK__XA_FIRST_INSTANCE, scheduler);
455 0 : if (rsc_first == NULL) {
456 0 : return;
457 : }
458 :
459 0 : rsc_then = get_ordering_resource(xml_obj, PCMK_XA_THEN,
460 : PCMK__XA_THEN_INSTANCE, scheduler);
461 0 : if (rsc_then == NULL) {
462 0 : return;
463 : }
464 :
465 0 : action_first = crm_element_value(xml_obj, PCMK_XA_FIRST_ACTION);
466 0 : if (action_first == NULL) {
467 0 : action_first = PCMK_ACTION_START;
468 : }
469 :
470 0 : action_then = crm_element_value(xml_obj, PCMK_XA_THEN_ACTION);
471 0 : if (action_then == NULL) {
472 0 : action_then = action_first;
473 : }
474 :
475 0 : kind = get_ordering_type(xml_obj);
476 :
477 0 : symmetry = get_ordering_symmetry(xml_obj, kind, NULL);
478 0 : flags = ordering_flags_for_kind(kind, action_first, symmetry);
479 :
480 0 : handle_restart_type(rsc_then, kind, pcmk__ar_first_implies_then, flags);
481 :
482 : /* If there is a minimum number of instances that must be runnable before
483 : * the 'then' action is runnable, we use a pseudo-action for convenience:
484 : * minimum number of clone instances have runnable actions ->
485 : * pseudo-action is runnable -> dependency is runnable.
486 : */
487 0 : min_required_before = get_minimum_first_instances(rsc_first, xml_obj);
488 0 : if (min_required_before > 0) {
489 0 : clone_min_ordering(id, rsc_first, action_first, rsc_then, action_then,
490 : flags, min_required_before);
491 : } else {
492 0 : pcmk__order_resource_actions(rsc_first, action_first, rsc_then,
493 : action_then, flags);
494 : }
495 :
496 0 : if (symmetry == ordering_symmetric) {
497 0 : inverse_ordering(id, kind, rsc_first, action_first,
498 : rsc_then, action_then);
499 : }
500 : }
501 :
502 : /*!
503 : * \internal
504 : * \brief Create a new ordering between two actions
505 : *
506 : * \param[in,out] first_rsc Resource for 'first' action (if NULL and
507 : * \p first_action is a resource action, that
508 : * resource will be used)
509 : * \param[in,out] first_action_task Action key for 'first' action (if NULL and
510 : * \p first_action is not NULL, its UUID will
511 : * be used)
512 : * \param[in,out] first_action 'first' action (if NULL, \p first_rsc and
513 : * \p first_action_task must be set)
514 : *
515 : * \param[in] then_rsc Resource for 'then' action (if NULL and
516 : * \p then_action is a resource action, that
517 : * resource will be used)
518 : * \param[in,out] then_action_task Action key for 'then' action (if NULL and
519 : * \p then_action is not NULL, its UUID will
520 : * be used)
521 : * \param[in] then_action 'then' action (if NULL, \p then_rsc and
522 : * \p then_action_task must be set)
523 : *
524 : * \param[in] flags Group of enum pcmk__action_relation_flags
525 : * \param[in,out] sched Scheduler data to add ordering to
526 : *
527 : * \note This function takes ownership of first_action_task and
528 : * then_action_task, which do not need to be freed by the caller.
529 : */
530 : void
531 0 : pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_action_task,
532 : pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
533 : char *then_action_task, pcmk_action_t *then_action,
534 : uint32_t flags, pcmk_scheduler_t *sched)
535 : {
536 0 : pcmk__action_relation_t *order = NULL;
537 :
538 : // One of action or resource must be specified for each side
539 0 : CRM_CHECK(((first_action != NULL) || (first_rsc != NULL))
540 : && ((then_action != NULL) || (then_rsc != NULL)),
541 : free(first_action_task); free(then_action_task); return);
542 :
543 0 : if ((first_rsc == NULL) && (first_action != NULL)) {
544 0 : first_rsc = first_action->rsc;
545 : }
546 0 : if ((then_rsc == NULL) && (then_action != NULL)) {
547 0 : then_rsc = then_action->rsc;
548 : }
549 :
550 0 : order = pcmk__assert_alloc(1, sizeof(pcmk__action_relation_t));
551 :
552 0 : order->id = sched->order_id++;
553 0 : order->flags = flags;
554 0 : order->rsc1 = first_rsc;
555 0 : order->rsc2 = then_rsc;
556 0 : order->action1 = first_action;
557 0 : order->action2 = then_action;
558 0 : order->task1 = first_action_task;
559 0 : order->task2 = then_action_task;
560 :
561 0 : if ((order->task1 == NULL) && (first_action != NULL)) {
562 0 : order->task1 = strdup(first_action->uuid);
563 : }
564 :
565 0 : if ((order->task2 == NULL) && (then_action != NULL)) {
566 0 : order->task2 = strdup(then_action->uuid);
567 : }
568 :
569 0 : if ((order->rsc1 == NULL) && (first_action != NULL)) {
570 0 : order->rsc1 = first_action->rsc;
571 : }
572 :
573 0 : if ((order->rsc2 == NULL) && (then_action != NULL)) {
574 0 : order->rsc2 = then_action->rsc;
575 : }
576 :
577 0 : pcmk__rsc_trace(first_rsc, "Created ordering %d for %s then %s",
578 : (sched->order_id - 1),
579 : pcmk__s(order->task1, "an underspecified action"),
580 : pcmk__s(order->task2, "an underspecified action"));
581 :
582 0 : sched->ordering_constraints = g_list_prepend(sched->ordering_constraints,
583 : order);
584 0 : pcmk__order_migration_equivalents(order);
585 : }
586 :
587 : /*!
588 : * \brief Unpack a set in an ordering constraint
589 : *
590 : * \param[in] set Set XML to unpack
591 : * \param[in] parent_kind \c PCMK_XE_RSC_ORDER XML \c PCMK_XA_KIND
592 : * attribute
593 : * \param[in] parent_symmetrical_s \c PCMK_XE_RSC_ORDER XML
594 : * \c PCMK_XA_SYMMETRICAL attribute
595 : * \param[in,out] scheduler Scheduler data
596 : *
597 : * \return Standard Pacemaker return code
598 : */
599 : static int
600 0 : unpack_order_set(const xmlNode *set, enum pe_order_kind parent_kind,
601 : const char *parent_symmetrical_s, pcmk_scheduler_t *scheduler)
602 : {
603 0 : GList *set_iter = NULL;
604 0 : GList *resources = NULL;
605 :
606 0 : pcmk_resource_t *last = NULL;
607 0 : pcmk_resource_t *resource = NULL;
608 :
609 0 : int local_kind = parent_kind;
610 0 : bool sequential = false;
611 0 : uint32_t flags = pcmk__ar_ordered;
612 : enum ordering_symmetry symmetry;
613 :
614 0 : char *key = NULL;
615 0 : const char *id = pcmk__xe_id(set);
616 0 : const char *action = crm_element_value(set, PCMK_XA_ACTION);
617 0 : const char *sequential_s = crm_element_value(set, PCMK_XA_SEQUENTIAL);
618 0 : const char *kind_s = crm_element_value(set, PCMK_XA_KIND);
619 :
620 0 : if (action == NULL) {
621 0 : action = PCMK_ACTION_START;
622 : }
623 :
624 0 : if (kind_s) {
625 0 : local_kind = get_ordering_type(set);
626 : }
627 0 : if (sequential_s == NULL) {
628 0 : sequential_s = "1";
629 : }
630 :
631 0 : sequential = crm_is_true(sequential_s);
632 :
633 0 : symmetry = get_ordering_symmetry(set, parent_kind, parent_symmetrical_s);
634 0 : flags = ordering_flags_for_kind(local_kind, action, symmetry);
635 :
636 0 : for (const xmlNode *xml_rsc = pcmk__xe_first_child(set,
637 : PCMK_XE_RESOURCE_REF,
638 : NULL, NULL);
639 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
640 :
641 0 : EXPAND_CONSTRAINT_IDREF(id, resource, pcmk__xe_id(xml_rsc));
642 0 : resources = g_list_append(resources, resource);
643 : }
644 :
645 0 : if (pcmk__list_of_1(resources)) {
646 0 : crm_trace("Single set: %s", id);
647 0 : goto done;
648 : }
649 :
650 0 : set_iter = resources;
651 0 : while (set_iter != NULL) {
652 0 : resource = (pcmk_resource_t *) set_iter->data;
653 0 : set_iter = set_iter->next;
654 :
655 0 : key = pcmk__op_key(resource->id, action, 0);
656 :
657 0 : if (local_kind == pe_order_kind_serialize) {
658 : /* Serialize before everything that comes after */
659 :
660 0 : for (GList *iter = set_iter; iter != NULL; iter = iter->next) {
661 0 : pcmk_resource_t *then_rsc = iter->data;
662 0 : char *then_key = pcmk__op_key(then_rsc->id, action, 0);
663 :
664 0 : pcmk__new_ordering(resource, strdup(key), NULL, then_rsc,
665 : then_key, NULL, flags, scheduler);
666 : }
667 :
668 0 : } else if (sequential) {
669 0 : if (last != NULL) {
670 0 : pcmk__order_resource_actions(last, action, resource, action,
671 : flags);
672 : }
673 0 : last = resource;
674 : }
675 0 : free(key);
676 : }
677 :
678 0 : if (symmetry == ordering_asymmetric) {
679 0 : goto done;
680 : }
681 :
682 0 : last = NULL;
683 0 : action = invert_action(action);
684 :
685 0 : flags = ordering_flags_for_kind(local_kind, action,
686 : ordering_symmetric_inverse);
687 :
688 0 : set_iter = resources;
689 0 : while (set_iter != NULL) {
690 0 : resource = (pcmk_resource_t *) set_iter->data;
691 0 : set_iter = set_iter->next;
692 :
693 0 : if (sequential) {
694 0 : if (last != NULL) {
695 0 : pcmk__order_resource_actions(resource, action, last, action,
696 : flags);
697 : }
698 0 : last = resource;
699 : }
700 : }
701 :
702 0 : done:
703 0 : g_list_free(resources);
704 0 : return pcmk_rc_ok;
705 : }
706 :
707 : /*!
708 : * \brief Order two resource sets relative to each other
709 : *
710 : * \param[in] id Ordering ID (for logging)
711 : * \param[in] set1 First listed set
712 : * \param[in] set2 Second listed set
713 : * \param[in] kind Ordering kind
714 : * \param[in,out] scheduler Scheduler data
715 : * \param[in] symmetry Which ordering symmetry applies to this relation
716 : *
717 : * \return Standard Pacemaker return code
718 : */
719 : static int
720 0 : order_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
721 : enum pe_order_kind kind, pcmk_scheduler_t *scheduler,
722 : enum ordering_symmetry symmetry)
723 : {
724 :
725 0 : const xmlNode *xml_rsc = NULL;
726 0 : const xmlNode *xml_rsc_2 = NULL;
727 :
728 0 : pcmk_resource_t *rsc_1 = NULL;
729 0 : pcmk_resource_t *rsc_2 = NULL;
730 :
731 0 : const char *action_1 = crm_element_value(set1, PCMK_XA_ACTION);
732 0 : const char *action_2 = crm_element_value(set2, PCMK_XA_ACTION);
733 :
734 0 : uint32_t flags = pcmk__ar_none;
735 :
736 0 : bool require_all = true;
737 :
738 0 : (void) pcmk__xe_get_bool_attr(set1, PCMK_XA_REQUIRE_ALL, &require_all);
739 :
740 0 : if (action_1 == NULL) {
741 0 : action_1 = PCMK_ACTION_START;
742 : }
743 :
744 0 : if (action_2 == NULL) {
745 0 : action_2 = PCMK_ACTION_START;
746 : }
747 :
748 0 : if (symmetry == ordering_symmetric_inverse) {
749 0 : action_1 = invert_action(action_1);
750 0 : action_2 = invert_action(action_2);
751 : }
752 :
753 0 : if (pcmk__str_eq(PCMK_ACTION_STOP, action_1, pcmk__str_none)
754 0 : || pcmk__str_eq(PCMK_ACTION_DEMOTE, action_1, pcmk__str_none)) {
755 : /* Assuming: A -> ( B || C) -> D
756 : * The one-or-more logic only applies during the start/promote phase.
757 : * During shutdown neither B nor can shutdown until D is down, so simply
758 : * turn require_all back on.
759 : */
760 0 : require_all = true;
761 : }
762 :
763 0 : flags = ordering_flags_for_kind(kind, action_1, symmetry);
764 :
765 : /* If we have an unordered set1, whether it is sequential or not is
766 : * irrelevant in regards to set2.
767 : */
768 0 : if (!require_all) {
769 0 : char *task = crm_strdup_printf(PCMK_ACTION_ONE_OR_MORE ":%s",
770 : pcmk__xe_id(set1));
771 0 : pcmk_action_t *unordered_action = get_pseudo_op(task, scheduler);
772 :
773 0 : free(task);
774 0 : pcmk__set_action_flags(unordered_action, pcmk_action_min_runnable);
775 :
776 0 : for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
777 : NULL);
778 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
779 :
780 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
781 :
782 : /* Add an ordering constraint between every element in set1 and the
783 : * pseudo action. If any action in set1 is runnable the pseudo
784 : * action will be runnable.
785 : */
786 0 : pcmk__new_ordering(rsc_1, pcmk__op_key(rsc_1->id, action_1, 0),
787 : NULL, NULL, NULL, unordered_action,
788 : pcmk__ar_min_runnable
789 : |pcmk__ar_first_implies_then_graphed,
790 : scheduler);
791 : }
792 0 : for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
793 : NULL);
794 0 : xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
795 :
796 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc_2));
797 :
798 : /* Add an ordering constraint between the pseudo-action and every
799 : * element in set2. If the pseudo-action is runnable, every action
800 : * in set2 will be runnable.
801 : */
802 0 : pcmk__new_ordering(NULL, NULL, unordered_action,
803 0 : rsc_2, pcmk__op_key(rsc_2->id, action_2, 0),
804 : NULL, flags|pcmk__ar_unrunnable_first_blocks,
805 : scheduler);
806 : }
807 :
808 0 : return pcmk_rc_ok;
809 : }
810 :
811 0 : if (pcmk__xe_attr_is_true(set1, PCMK_XA_SEQUENTIAL)) {
812 0 : if (symmetry == ordering_symmetric_inverse) {
813 : // Get the first one
814 0 : xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
815 : NULL);
816 0 : if (xml_rsc != NULL) {
817 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
818 : }
819 :
820 : } else {
821 : // Get the last one
822 0 : const char *rid = NULL;
823 :
824 0 : for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF,
825 : NULL, NULL);
826 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
827 :
828 0 : rid = pcmk__xe_id(xml_rsc);
829 : }
830 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_1, rid);
831 : }
832 : }
833 :
834 0 : if (pcmk__xe_attr_is_true(set2, PCMK_XA_SEQUENTIAL)) {
835 0 : if (symmetry == ordering_symmetric_inverse) {
836 : // Get the last one
837 0 : const char *rid = NULL;
838 :
839 0 : for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF,
840 : NULL, NULL);
841 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
842 :
843 0 : rid = pcmk__xe_id(xml_rsc);
844 : }
845 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
846 :
847 : } else {
848 : // Get the first one
849 0 : xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
850 : NULL);
851 0 : if (xml_rsc != NULL) {
852 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc));
853 : }
854 : }
855 : }
856 :
857 0 : if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
858 0 : pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2, flags);
859 :
860 0 : } else if (rsc_1 != NULL) {
861 0 : for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
862 : NULL);
863 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
864 :
865 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc));
866 0 : pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
867 : flags);
868 : }
869 :
870 0 : } else if (rsc_2 != NULL) {
871 0 : for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
872 : NULL);
873 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
874 :
875 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
876 0 : pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
877 : flags);
878 : }
879 :
880 : } else {
881 0 : for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
882 : NULL);
883 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
884 :
885 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_1, pcmk__xe_id(xml_rsc));
886 :
887 0 : for (xmlNode *xml_rsc_2 = pcmk__xe_first_child(set2,
888 : PCMK_XE_RESOURCE_REF,
889 : NULL, NULL);
890 0 : xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
891 :
892 0 : EXPAND_CONSTRAINT_IDREF(id, rsc_2, pcmk__xe_id(xml_rsc_2));
893 0 : pcmk__order_resource_actions(rsc_1, action_1, rsc_2,
894 : action_2, flags);
895 : }
896 : }
897 : }
898 :
899 0 : return pcmk_rc_ok;
900 : }
901 :
902 : /*!
903 : * \internal
904 : * \brief If an ordering constraint uses resource tags, expand them
905 : *
906 : * \param[in,out] xml_obj Ordering constraint XML
907 : * \param[out] expanded_xml Equivalent XML with tags expanded
908 : * \param[in] scheduler Scheduler data
909 : *
910 : * \return Standard Pacemaker return code (specifically, pcmk_rc_ok on success,
911 : * and pcmk_rc_unpack_error on invalid configuration)
912 : */
913 : static int
914 0 : unpack_order_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
915 : const pcmk_scheduler_t *scheduler)
916 : {
917 0 : const char *id_first = NULL;
918 0 : const char *id_then = NULL;
919 0 : const char *action_first = NULL;
920 0 : const char *action_then = NULL;
921 :
922 0 : pcmk_resource_t *rsc_first = NULL;
923 0 : pcmk_resource_t *rsc_then = NULL;
924 0 : pcmk_tag_t *tag_first = NULL;
925 0 : pcmk_tag_t *tag_then = NULL;
926 :
927 0 : xmlNode *rsc_set_first = NULL;
928 0 : xmlNode *rsc_set_then = NULL;
929 0 : bool any_sets = false;
930 :
931 : // Check whether there are any resource sets with template or tag references
932 0 : *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
933 0 : if (*expanded_xml != NULL) {
934 0 : crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_ORDER);
935 0 : return pcmk_rc_ok;
936 : }
937 :
938 0 : id_first = crm_element_value(xml_obj, PCMK_XA_FIRST);
939 0 : id_then = crm_element_value(xml_obj, PCMK_XA_THEN);
940 0 : if ((id_first == NULL) || (id_then == NULL)) {
941 0 : return pcmk_rc_ok;
942 : }
943 :
944 0 : if (!pcmk__valid_resource_or_tag(scheduler, id_first, &rsc_first,
945 : &tag_first)) {
946 0 : pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
947 : "valid resource or tag",
948 : pcmk__xe_id(xml_obj), id_first);
949 0 : return pcmk_rc_unpack_error;
950 : }
951 :
952 0 : if (!pcmk__valid_resource_or_tag(scheduler, id_then, &rsc_then,
953 : &tag_then)) {
954 0 : pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
955 : "valid resource or tag",
956 : pcmk__xe_id(xml_obj), id_then);
957 0 : return pcmk_rc_unpack_error;
958 : }
959 :
960 0 : if ((rsc_first != NULL) && (rsc_then != NULL)) {
961 : // Neither side references a template or tag
962 0 : return pcmk_rc_ok;
963 : }
964 :
965 0 : action_first = crm_element_value(xml_obj, PCMK_XA_FIRST_ACTION);
966 0 : action_then = crm_element_value(xml_obj, PCMK_XA_THEN_ACTION);
967 :
968 0 : *expanded_xml = pcmk__xml_copy(NULL, xml_obj);
969 :
970 : /* Convert template/tag reference in PCMK_XA_FIRST into constraint
971 : * PCMK_XE_RESOURCE_SET
972 : */
973 0 : if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_first, PCMK_XA_FIRST, true,
974 : scheduler)) {
975 0 : free_xml(*expanded_xml);
976 0 : *expanded_xml = NULL;
977 0 : return pcmk_rc_unpack_error;
978 : }
979 :
980 0 : if (rsc_set_first != NULL) {
981 0 : if (action_first != NULL) {
982 : /* Move PCMK_XA_FIRST_ACTION into converted PCMK_XE_RESOURCE_SET as
983 : * PCMK_XA_ACTION
984 : */
985 0 : crm_xml_add(rsc_set_first, PCMK_XA_ACTION, action_first);
986 0 : pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_FIRST_ACTION);
987 : }
988 0 : any_sets = true;
989 : }
990 :
991 : /* Convert template/tag reference in PCMK_XA_THEN into constraint
992 : * PCMK_XE_RESOURCE_SET
993 : */
994 0 : if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_then, PCMK_XA_THEN, true,
995 : scheduler)) {
996 0 : free_xml(*expanded_xml);
997 0 : *expanded_xml = NULL;
998 0 : return pcmk_rc_unpack_error;
999 : }
1000 :
1001 0 : if (rsc_set_then != NULL) {
1002 0 : if (action_then != NULL) {
1003 : /* Move PCMK_XA_THEN_ACTION into converted PCMK_XE_RESOURCE_SET as
1004 : * PCMK_XA_ACTION
1005 : */
1006 0 : crm_xml_add(rsc_set_then, PCMK_XA_ACTION, action_then);
1007 0 : pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_THEN_ACTION);
1008 : }
1009 0 : any_sets = true;
1010 : }
1011 :
1012 0 : if (any_sets) {
1013 0 : crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_ORDER);
1014 : } else {
1015 0 : free_xml(*expanded_xml);
1016 0 : *expanded_xml = NULL;
1017 : }
1018 :
1019 0 : return pcmk_rc_ok;
1020 : }
1021 :
1022 : /*!
1023 : * \internal
1024 : * \brief Unpack ordering constraint XML
1025 : *
1026 : * \param[in,out] xml_obj Ordering constraint XML to unpack
1027 : * \param[in,out] scheduler Scheduler data
1028 : */
1029 : void
1030 0 : pcmk__unpack_ordering(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
1031 : {
1032 0 : xmlNode *set = NULL;
1033 0 : xmlNode *last = NULL;
1034 :
1035 0 : xmlNode *orig_xml = NULL;
1036 0 : xmlNode *expanded_xml = NULL;
1037 :
1038 0 : const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
1039 0 : const char *invert = crm_element_value(xml_obj, PCMK_XA_SYMMETRICAL);
1040 0 : enum pe_order_kind kind = get_ordering_type(xml_obj);
1041 :
1042 0 : enum ordering_symmetry symmetry = get_ordering_symmetry(xml_obj, kind,
1043 : NULL);
1044 :
1045 : // Expand any resource tags in the constraint XML
1046 0 : if (unpack_order_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) {
1047 0 : return;
1048 : }
1049 0 : if (expanded_xml != NULL) {
1050 0 : orig_xml = xml_obj;
1051 0 : xml_obj = expanded_xml;
1052 : }
1053 :
1054 : // If the constraint has resource sets, unpack them
1055 0 : for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
1056 0 : set != NULL; set = pcmk__xe_next_same(set)) {
1057 :
1058 0 : set = expand_idref(set, scheduler->input);
1059 0 : if ((set == NULL) // Configuration error, message already logged
1060 0 : || (unpack_order_set(set, kind, invert, scheduler) != pcmk_rc_ok)) {
1061 :
1062 0 : if (expanded_xml != NULL) {
1063 0 : free_xml(expanded_xml);
1064 : }
1065 0 : return;
1066 : }
1067 :
1068 0 : if (last != NULL) {
1069 :
1070 0 : if (order_rsc_sets(id, last, set, kind, scheduler,
1071 : symmetry) != pcmk_rc_ok) {
1072 0 : if (expanded_xml != NULL) {
1073 0 : free_xml(expanded_xml);
1074 : }
1075 0 : return;
1076 : }
1077 :
1078 0 : if ((symmetry == ordering_symmetric)
1079 0 : && (order_rsc_sets(id, set, last, kind, scheduler,
1080 : ordering_symmetric_inverse) != pcmk_rc_ok)) {
1081 0 : if (expanded_xml != NULL) {
1082 0 : free_xml(expanded_xml);
1083 : }
1084 0 : return;
1085 : }
1086 :
1087 : }
1088 0 : last = set;
1089 : }
1090 :
1091 0 : if (expanded_xml) {
1092 0 : free_xml(expanded_xml);
1093 0 : xml_obj = orig_xml;
1094 : }
1095 :
1096 : // If the constraint has no resource sets, unpack it as a simple ordering
1097 0 : if (last == NULL) {
1098 0 : return unpack_simple_rsc_order(xml_obj, scheduler);
1099 : }
1100 : }
1101 :
1102 : static bool
1103 0 : ordering_is_invalid(pcmk_action_t *action, pcmk__related_action_t *input)
1104 : {
1105 : /* Prevent user-defined ordering constraints between resources
1106 : * running in a guest node and the resource that defines that node.
1107 : */
1108 0 : if (!pcmk_is_set(input->type, pcmk__ar_guest_allowed)
1109 0 : && (input->action->rsc != NULL)
1110 0 : && pcmk__rsc_corresponds_to_guest(action->rsc, input->action->node)) {
1111 :
1112 0 : pcmk__config_warn("Invalid ordering constraint between %s and %s",
1113 : input->action->rsc->id, action->rsc->id);
1114 0 : return true;
1115 : }
1116 :
1117 : /* If there's an order like
1118 : * "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
1119 : *
1120 : * then rscA is being migrated from node1 to node2, while rscB is being
1121 : * migrated from node2 to node1. If there would be a graph loop,
1122 : * break the order "load_stopped_node2" -> "rscA_migrate_to node1".
1123 : */
1124 0 : if (((uint32_t) input->type == pcmk__ar_if_on_same_node_or_target)
1125 0 : && (action->rsc != NULL)
1126 0 : && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO, pcmk__str_none)
1127 0 : && pcmk__graph_has_loop(action, action, input)) {
1128 0 : return true;
1129 : }
1130 :
1131 0 : return false;
1132 : }
1133 :
1134 : void
1135 0 : pcmk__disable_invalid_orderings(pcmk_scheduler_t *scheduler)
1136 : {
1137 0 : for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) {
1138 0 : pcmk_action_t *action = (pcmk_action_t *) iter->data;
1139 0 : pcmk__related_action_t *input = NULL;
1140 :
1141 0 : for (GList *input_iter = action->actions_before;
1142 0 : input_iter != NULL; input_iter = input_iter->next) {
1143 :
1144 0 : input = input_iter->data;
1145 0 : if (ordering_is_invalid(action, input)) {
1146 0 : input->type = (enum pe_ordering) pcmk__ar_none;
1147 : }
1148 : }
1149 : }
1150 0 : }
1151 :
1152 : /*!
1153 : * \internal
1154 : * \brief Order stops on a node before the node's shutdown
1155 : *
1156 : * \param[in,out] node Node being shut down
1157 : * \param[in] shutdown_op Shutdown action for node
1158 : */
1159 : void
1160 0 : pcmk__order_stops_before_shutdown(pcmk_node_t *node, pcmk_action_t *shutdown_op)
1161 : {
1162 0 : for (GList *iter = node->details->data_set->actions;
1163 0 : iter != NULL; iter = iter->next) {
1164 :
1165 0 : pcmk_action_t *action = (pcmk_action_t *) iter->data;
1166 :
1167 : // Only stops on the node shutting down are relevant
1168 0 : if (!pcmk__same_node(action->node, node)
1169 0 : || !pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
1170 0 : continue;
1171 : }
1172 :
1173 : // Resources and nodes in maintenance mode won't be touched
1174 :
1175 0 : if (pcmk_is_set(action->rsc->flags, pcmk_rsc_maintenance)) {
1176 0 : pcmk__rsc_trace(action->rsc,
1177 : "Not ordering %s before shutdown of %s because "
1178 : "resource in maintenance mode",
1179 : action->uuid, pcmk__node_name(node));
1180 0 : continue;
1181 :
1182 0 : } else if (node->details->maintenance) {
1183 0 : pcmk__rsc_trace(action->rsc,
1184 : "Not ordering %s before shutdown of %s because "
1185 : "node in maintenance mode",
1186 : action->uuid, pcmk__node_name(node));
1187 0 : continue;
1188 : }
1189 :
1190 : /* Don't touch a resource that is unmanaged or blocked, to avoid
1191 : * blocking the shutdown (though if another action depends on this one,
1192 : * we may still end up blocking)
1193 : */
1194 0 : if (!pcmk_any_flags_set(action->rsc->flags,
1195 : pcmk_rsc_managed|pcmk_rsc_blocked)) {
1196 0 : pcmk__rsc_trace(action->rsc,
1197 : "Not ordering %s before shutdown of %s because "
1198 : "resource is unmanaged or blocked",
1199 : action->uuid, pcmk__node_name(node));
1200 0 : continue;
1201 : }
1202 :
1203 0 : pcmk__rsc_trace(action->rsc, "Ordering %s before shutdown of %s",
1204 : action->uuid, pcmk__node_name(node));
1205 0 : pcmk__clear_action_flags(action, pcmk_action_optional);
1206 0 : pcmk__new_ordering(action->rsc, NULL, action, NULL,
1207 : strdup(PCMK_ACTION_DO_SHUTDOWN), shutdown_op,
1208 : pcmk__ar_ordered|pcmk__ar_unrunnable_first_blocks,
1209 0 : node->details->data_set);
1210 : }
1211 0 : }
1212 :
1213 : /*!
1214 : * \brief Find resource actions matching directly or as child
1215 : *
1216 : * \param[in] rsc Resource to check
1217 : * \param[in] original_key Action key to search for (possibly referencing
1218 : * parent of \rsc)
1219 : *
1220 : * \return Newly allocated list of matching actions
1221 : * \note It is the caller's responsibility to free the result with g_list_free()
1222 : */
1223 : static GList *
1224 0 : find_actions_by_task(const pcmk_resource_t *rsc, const char *original_key)
1225 : {
1226 : // Search under given task key directly
1227 0 : GList *list = find_actions(rsc->actions, original_key, NULL);
1228 :
1229 0 : if (list == NULL) {
1230 : // Search again using this resource's ID
1231 0 : char *key = NULL;
1232 0 : char *task = NULL;
1233 0 : guint interval_ms = 0;
1234 :
1235 0 : CRM_CHECK(parse_op_key(original_key, NULL, &task, &interval_ms),
1236 : return NULL);
1237 0 : key = pcmk__op_key(rsc->id, task, interval_ms);
1238 0 : list = find_actions(rsc->actions, key, NULL);
1239 0 : free(key);
1240 0 : free(task);
1241 : }
1242 0 : return list;
1243 : }
1244 :
1245 : /*!
1246 : * \internal
1247 : * \brief Order relevant resource actions after a given action
1248 : *
1249 : * \param[in,out] first_action Action to order after (or NULL if none runnable)
1250 : * \param[in] rsc Resource whose actions should be ordered
1251 : * \param[in,out] order Ordering constraint being applied
1252 : */
1253 : static void
1254 0 : order_resource_actions_after(pcmk_action_t *first_action,
1255 : const pcmk_resource_t *rsc,
1256 : pcmk__action_relation_t *order)
1257 : {
1258 0 : GList *then_actions = NULL;
1259 0 : uint32_t flags = pcmk__ar_none;
1260 :
1261 0 : CRM_CHECK((rsc != NULL) && (order != NULL), return);
1262 :
1263 0 : flags = order->flags;
1264 0 : pcmk__rsc_trace(rsc, "Applying ordering %d for 'then' resource %s",
1265 : order->id, rsc->id);
1266 :
1267 0 : if (order->action2 != NULL) {
1268 0 : then_actions = g_list_prepend(NULL, order->action2);
1269 :
1270 : } else {
1271 0 : then_actions = find_actions_by_task(rsc, order->task2);
1272 : }
1273 :
1274 0 : if (then_actions == NULL) {
1275 0 : pcmk__rsc_trace(rsc, "Ignoring ordering %d: no %s actions found for %s",
1276 : order->id, order->task2, rsc->id);
1277 0 : return;
1278 : }
1279 :
1280 0 : if ((first_action != NULL) && (first_action->rsc == rsc)
1281 0 : && pcmk_is_set(first_action->flags, pcmk_action_migration_abort)) {
1282 :
1283 0 : pcmk__rsc_trace(rsc,
1284 : "Detected dangling migration ordering (%s then %s %s)",
1285 : first_action->uuid, order->task2, rsc->id);
1286 0 : pcmk__clear_relation_flags(flags, pcmk__ar_first_implies_then);
1287 : }
1288 :
1289 0 : if ((first_action == NULL)
1290 0 : && !pcmk_is_set(flags, pcmk__ar_first_implies_then)) {
1291 :
1292 0 : pcmk__rsc_debug(rsc,
1293 : "Ignoring ordering %d for %s: No first action found",
1294 : order->id, rsc->id);
1295 0 : g_list_free(then_actions);
1296 0 : return;
1297 : }
1298 :
1299 0 : for (GList *iter = then_actions; iter != NULL; iter = iter->next) {
1300 0 : pcmk_action_t *then_action_iter = (pcmk_action_t *) iter->data;
1301 :
1302 0 : if (first_action != NULL) {
1303 0 : order_actions(first_action, then_action_iter, flags);
1304 : } else {
1305 0 : pcmk__clear_action_flags(then_action_iter, pcmk_action_runnable);
1306 0 : crm_warn("%s of %s is unrunnable because there is no %s of %s "
1307 : "to order it after", then_action_iter->task, rsc->id,
1308 : order->task1, order->rsc1->id);
1309 : }
1310 : }
1311 :
1312 0 : g_list_free(then_actions);
1313 : }
1314 :
1315 : static void
1316 0 : rsc_order_first(pcmk_resource_t *first_rsc, pcmk__action_relation_t *order)
1317 : {
1318 0 : GList *first_actions = NULL;
1319 0 : pcmk_action_t *first_action = order->action1;
1320 0 : pcmk_resource_t *then_rsc = order->rsc2;
1321 :
1322 0 : CRM_ASSERT(first_rsc != NULL);
1323 0 : pcmk__rsc_trace(first_rsc, "Applying ordering constraint %d (first: %s)",
1324 : order->id, first_rsc->id);
1325 :
1326 0 : if (first_action != NULL) {
1327 0 : first_actions = g_list_prepend(NULL, first_action);
1328 :
1329 : } else {
1330 0 : first_actions = find_actions_by_task(first_rsc, order->task1);
1331 : }
1332 :
1333 0 : if ((first_actions == NULL) && (first_rsc == then_rsc)) {
1334 0 : pcmk__rsc_trace(first_rsc,
1335 : "Ignoring constraint %d: first (%s for %s) not found",
1336 : order->id, order->task1, first_rsc->id);
1337 :
1338 0 : } else if (first_actions == NULL) {
1339 0 : char *key = NULL;
1340 0 : char *op_type = NULL;
1341 0 : guint interval_ms = 0;
1342 :
1343 0 : parse_op_key(order->task1, NULL, &op_type, &interval_ms);
1344 0 : key = pcmk__op_key(first_rsc->id, op_type, interval_ms);
1345 :
1346 0 : if ((first_rsc->fns->state(first_rsc, TRUE) == pcmk_role_stopped)
1347 0 : && pcmk__str_eq(op_type, PCMK_ACTION_STOP, pcmk__str_none)) {
1348 0 : free(key);
1349 0 : pcmk__rsc_trace(first_rsc,
1350 : "Ignoring constraint %d: first (%s for %s) "
1351 : "not found",
1352 : order->id, order->task1, first_rsc->id);
1353 :
1354 0 : } else if ((first_rsc->fns->state(first_rsc,
1355 : TRUE) == pcmk_role_unpromoted)
1356 0 : && pcmk__str_eq(op_type, PCMK_ACTION_DEMOTE,
1357 : pcmk__str_none)) {
1358 0 : free(key);
1359 0 : pcmk__rsc_trace(first_rsc,
1360 : "Ignoring constraint %d: first (%s for %s) "
1361 : "not found",
1362 : order->id, order->task1, first_rsc->id);
1363 :
1364 : } else {
1365 0 : pcmk__rsc_trace(first_rsc,
1366 : "Creating first (%s for %s) for constraint %d ",
1367 : order->task1, first_rsc->id, order->id);
1368 0 : first_action = custom_action(first_rsc, key, op_type, NULL, TRUE,
1369 : first_rsc->cluster);
1370 0 : first_actions = g_list_prepend(NULL, first_action);
1371 : }
1372 :
1373 0 : free(op_type);
1374 : }
1375 :
1376 0 : if (then_rsc == NULL) {
1377 0 : if (order->action2 == NULL) {
1378 0 : pcmk__rsc_trace(first_rsc, "Ignoring constraint %d: then not found",
1379 : order->id);
1380 0 : return;
1381 : }
1382 0 : then_rsc = order->action2->rsc;
1383 : }
1384 0 : for (GList *iter = first_actions; iter != NULL; iter = iter->next) {
1385 0 : first_action = iter->data;
1386 :
1387 0 : if (then_rsc == NULL) {
1388 0 : order_actions(first_action, order->action2, order->flags);
1389 :
1390 : } else {
1391 0 : order_resource_actions_after(first_action, then_rsc, order);
1392 : }
1393 : }
1394 :
1395 0 : g_list_free(first_actions);
1396 : }
1397 :
1398 : // GFunc to call pcmk__block_colocation_dependents()
1399 : static void
1400 0 : block_colocation_dependents(gpointer data, gpointer user_data)
1401 : {
1402 0 : pcmk__block_colocation_dependents(data);
1403 0 : }
1404 :
1405 : // GFunc to call pcmk__update_action_for_orderings()
1406 : static void
1407 0 : update_action_for_orderings(gpointer data, gpointer user_data)
1408 : {
1409 0 : pcmk__update_action_for_orderings((pcmk_action_t *) data,
1410 : (pcmk_scheduler_t *) user_data);
1411 0 : }
1412 :
1413 : /*!
1414 : * \internal
1415 : * \brief Apply all ordering constraints
1416 : *
1417 : * \param[in,out] sched Scheduler data
1418 : */
1419 : void
1420 0 : pcmk__apply_orderings(pcmk_scheduler_t *sched)
1421 : {
1422 0 : crm_trace("Applying ordering constraints");
1423 :
1424 : /* Ordering constraints need to be processed in the order they were created.
1425 : * rsc_order_first() and order_resource_actions_after() require the relevant
1426 : * actions to already exist in some cases, but rsc_order_first() will create
1427 : * the 'first' action in certain cases. Thus calling rsc_order_first() can
1428 : * change the behavior of later-created orderings.
1429 : *
1430 : * Also, g_list_append() should be avoided for performance reasons, so we
1431 : * prepend orderings when creating them and reverse the list here.
1432 : *
1433 : * @TODO This is brittle and should be carefully redesigned so that the
1434 : * order of creation doesn't matter, and the reverse becomes unneeded.
1435 : */
1436 0 : sched->ordering_constraints = g_list_reverse(sched->ordering_constraints);
1437 :
1438 0 : for (GList *iter = sched->ordering_constraints;
1439 0 : iter != NULL; iter = iter->next) {
1440 :
1441 0 : pcmk__action_relation_t *order = iter->data;
1442 0 : pcmk_resource_t *rsc = order->rsc1;
1443 :
1444 0 : if (rsc != NULL) {
1445 0 : rsc_order_first(rsc, order);
1446 0 : continue;
1447 : }
1448 :
1449 0 : rsc = order->rsc2;
1450 0 : if (rsc != NULL) {
1451 0 : order_resource_actions_after(order->action1, rsc, order);
1452 :
1453 : } else {
1454 0 : crm_trace("Applying ordering constraint %d (non-resource actions)",
1455 : order->id);
1456 0 : order_actions(order->action1, order->action2, order->flags);
1457 : }
1458 : }
1459 :
1460 0 : g_list_foreach(sched->actions, block_colocation_dependents, NULL);
1461 :
1462 0 : crm_trace("Ordering probes");
1463 0 : pcmk__order_probes(sched);
1464 :
1465 0 : crm_trace("Updating %d actions", g_list_length(sched->actions));
1466 0 : g_list_foreach(sched->actions, update_action_for_orderings, sched);
1467 :
1468 0 : pcmk__disable_invalid_orderings(sched);
1469 0 : }
1470 :
1471 : /*!
1472 : * \internal
1473 : * \brief Order a given action after each action in a given list
1474 : *
1475 : * \param[in,out] after "After" action
1476 : * \param[in,out] list List of "before" actions
1477 : */
1478 : void
1479 0 : pcmk__order_after_each(pcmk_action_t *after, GList *list)
1480 : {
1481 0 : const char *after_desc = (after->task == NULL)? after->uuid : after->task;
1482 :
1483 0 : for (GList *iter = list; iter != NULL; iter = iter->next) {
1484 0 : pcmk_action_t *before = (pcmk_action_t *) iter->data;
1485 0 : const char *before_desc = before->task? before->task : before->uuid;
1486 :
1487 0 : crm_debug("Ordering %s on %s before %s on %s",
1488 : before_desc, pcmk__node_name(before->node),
1489 : after_desc, pcmk__node_name(after->node));
1490 0 : order_actions(before, after, pcmk__ar_ordered);
1491 : }
1492 0 : }
1493 :
1494 : /*!
1495 : * \internal
1496 : * \brief Order promotions and demotions for restarts of a clone or bundle
1497 : *
1498 : * \param[in,out] rsc Clone or bundle to order
1499 : */
1500 : void
1501 0 : pcmk__promotable_restart_ordering(pcmk_resource_t *rsc)
1502 : {
1503 : // Order start and promote after all instances are stopped
1504 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
1505 : rsc, PCMK_ACTION_START,
1506 : pcmk__ar_ordered);
1507 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
1508 : rsc, PCMK_ACTION_PROMOTE,
1509 : pcmk__ar_ordered);
1510 :
1511 : // Order stop, start, and promote after all instances are demoted
1512 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
1513 : rsc, PCMK_ACTION_STOP,
1514 : pcmk__ar_ordered);
1515 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
1516 : rsc, PCMK_ACTION_START,
1517 : pcmk__ar_ordered);
1518 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
1519 : rsc, PCMK_ACTION_PROMOTE,
1520 : pcmk__ar_ordered);
1521 :
1522 : // Order promote after all instances are started
1523 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_RUNNING,
1524 : rsc, PCMK_ACTION_PROMOTE,
1525 : pcmk__ar_ordered);
1526 :
1527 : // Order demote after all instances are demoted
1528 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTE,
1529 : rsc, PCMK_ACTION_DEMOTED,
1530 : pcmk__ar_ordered);
1531 0 : }
|