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 :
14 : #include <crm/crm.h>
15 : #include <crm/cib.h>
16 : #include <crm/common/xml.h>
17 : #include <crm/common/xml_internal.h>
18 :
19 : #include <glib.h>
20 :
21 : #include <crm/pengine/status.h>
22 : #include <pacemaker-internal.h>
23 : #include "libpacemaker_private.h"
24 :
25 : enum remote_connection_state {
26 : remote_state_unknown = 0,
27 : remote_state_alive = 1,
28 : remote_state_resting = 2,
29 : remote_state_failed = 3,
30 : remote_state_stopped = 4
31 : };
32 :
33 : static const char *
34 0 : state2text(enum remote_connection_state state)
35 : {
36 0 : switch (state) {
37 0 : case remote_state_unknown:
38 0 : return "unknown";
39 0 : case remote_state_alive:
40 0 : return "alive";
41 0 : case remote_state_resting:
42 0 : return "resting";
43 0 : case remote_state_failed:
44 0 : return "failed";
45 0 : case remote_state_stopped:
46 0 : return "stopped";
47 : }
48 :
49 0 : return "impossible";
50 : }
51 :
52 : /* We always use pcmk__ar_guest_allowed with these convenience functions to
53 : * exempt internally generated constraints from the prohibition of user
54 : * constraints involving remote connection resources.
55 : *
56 : * The start ordering additionally uses pcmk__ar_unrunnable_first_blocks so that
57 : * the specified action is not runnable if the start is not runnable.
58 : */
59 :
60 : static inline void
61 0 : order_start_then_action(pcmk_resource_t *first_rsc, pcmk_action_t *then_action,
62 : uint32_t extra)
63 : {
64 0 : if ((first_rsc != NULL) && (then_action != NULL)) {
65 0 : pcmk__new_ordering(first_rsc, start_key(first_rsc), NULL,
66 : then_action->rsc, NULL, then_action,
67 : pcmk__ar_guest_allowed
68 : |pcmk__ar_unrunnable_first_blocks
69 : |extra,
70 : first_rsc->cluster);
71 : }
72 0 : }
73 :
74 : static inline void
75 0 : order_action_then_stop(pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
76 : uint32_t extra)
77 : {
78 0 : if ((first_action != NULL) && (then_rsc != NULL)) {
79 0 : pcmk__new_ordering(first_action->rsc, NULL, first_action,
80 0 : then_rsc, stop_key(then_rsc), NULL,
81 : pcmk__ar_guest_allowed|extra, then_rsc->cluster);
82 : }
83 0 : }
84 :
85 : static enum remote_connection_state
86 0 : get_remote_node_state(const pcmk_node_t *node)
87 : {
88 0 : const pcmk_resource_t *remote_rsc = NULL;
89 0 : const pcmk_node_t *cluster_node = NULL;
90 :
91 0 : CRM_ASSERT(node != NULL);
92 :
93 0 : remote_rsc = node->details->remote_rsc;
94 0 : CRM_ASSERT(remote_rsc != NULL);
95 :
96 0 : cluster_node = pcmk__current_node(remote_rsc);
97 :
98 : /* If the cluster node the remote connection resource resides on
99 : * is unclean or went offline, we can't process any operations
100 : * on that remote node until after it starts elsewhere.
101 : */
102 0 : if ((remote_rsc->next_role == pcmk_role_stopped)
103 0 : || (remote_rsc->allocated_to == NULL)) {
104 :
105 : // The connection resource is not going to run anywhere
106 :
107 0 : if ((cluster_node != NULL) && cluster_node->details->unclean) {
108 : /* The remote connection is failed because its resource is on a
109 : * failed node and can't be recovered elsewhere, so we must fence.
110 : */
111 0 : return remote_state_failed;
112 : }
113 :
114 0 : if (!pcmk_is_set(remote_rsc->flags, pcmk_rsc_failed)) {
115 : /* Connection resource is cleanly stopped */
116 0 : return remote_state_stopped;
117 : }
118 :
119 : /* Connection resource is failed */
120 :
121 0 : if ((remote_rsc->next_role == pcmk_role_stopped)
122 0 : && remote_rsc->remote_reconnect_ms
123 0 : && node->details->remote_was_fenced
124 0 : && !pe__shutdown_requested(node)) {
125 :
126 : /* We won't know whether the connection is recoverable until the
127 : * reconnect interval expires and we reattempt connection.
128 : */
129 0 : return remote_state_unknown;
130 : }
131 :
132 : /* The remote connection is in a failed state. If there are any
133 : * resources known to be active on it (stop) or in an unknown state
134 : * (probe), we must assume the worst and fence it.
135 : */
136 0 : return remote_state_failed;
137 :
138 0 : } else if (cluster_node == NULL) {
139 : /* Connection is recoverable but not currently running anywhere, so see
140 : * if we can recover it first
141 : */
142 0 : return remote_state_unknown;
143 :
144 0 : } else if (cluster_node->details->unclean
145 0 : || !(cluster_node->details->online)) {
146 : // Connection is running on a dead node, see if we can recover it first
147 0 : return remote_state_resting;
148 :
149 0 : } else if (pcmk__list_of_multiple(remote_rsc->running_on)
150 0 : && (remote_rsc->partial_migration_source != NULL)
151 0 : && (remote_rsc->partial_migration_target != NULL)) {
152 : /* We're in the middle of migrating a connection resource, so wait until
153 : * after the migration completes before performing any actions.
154 : */
155 0 : return remote_state_resting;
156 :
157 : }
158 0 : return remote_state_alive;
159 : }
160 :
161 : /*!
162 : * \internal
163 : * \brief Order actions on remote node relative to actions for the connection
164 : *
165 : * \param[in,out] action An action scheduled on a Pacemaker Remote node
166 : */
167 : static void
168 0 : apply_remote_ordering(pcmk_action_t *action)
169 : {
170 0 : pcmk_resource_t *remote_rsc = NULL;
171 0 : enum action_tasks task = pcmk_parse_action(action->task);
172 0 : enum remote_connection_state state = get_remote_node_state(action->node);
173 :
174 0 : uint32_t order_opts = pcmk__ar_none;
175 :
176 0 : if (action->rsc == NULL) {
177 0 : return;
178 : }
179 :
180 0 : CRM_ASSERT(pcmk__is_pacemaker_remote_node(action->node));
181 :
182 0 : remote_rsc = action->node->details->remote_rsc;
183 0 : CRM_ASSERT(remote_rsc != NULL);
184 :
185 0 : crm_trace("Order %s action %s relative to %s%s (state: %s)",
186 : action->task, action->uuid,
187 : pcmk_is_set(remote_rsc->flags, pcmk_rsc_failed)? "failed " : "",
188 : remote_rsc->id, state2text(state));
189 :
190 0 : if (pcmk__strcase_any_of(action->task, PCMK_ACTION_MIGRATE_TO,
191 : PCMK_ACTION_MIGRATE_FROM, NULL)) {
192 : /* Migration ops map to pcmk_action_unspecified, but we need to apply
193 : * the same ordering as for stop or demote (see get_router_node()).
194 : */
195 0 : task = pcmk_action_stop;
196 : }
197 :
198 0 : switch (task) {
199 0 : case pcmk_action_start:
200 : case pcmk_action_promote:
201 0 : order_opts = pcmk__ar_none;
202 :
203 0 : if (state == remote_state_failed) {
204 : /* Force recovery, by making this action required */
205 0 : pcmk__set_relation_flags(order_opts,
206 : pcmk__ar_first_implies_then);
207 : }
208 :
209 : /* Ensure connection is up before running this action */
210 0 : order_start_then_action(remote_rsc, action, order_opts);
211 0 : break;
212 :
213 0 : case pcmk_action_stop:
214 0 : if (state == remote_state_alive) {
215 0 : order_action_then_stop(action, remote_rsc,
216 : pcmk__ar_then_implies_first);
217 :
218 0 : } else if (state == remote_state_failed) {
219 : /* The resource is active on the node, but since we don't have a
220 : * valid connection, the only way to stop the resource is by
221 : * fencing the node. There is no need to order the stop relative
222 : * to the remote connection, since the stop will become implied
223 : * by the fencing.
224 : */
225 0 : pe_fence_node(remote_rsc->cluster, action->node,
226 : "resources are active but "
227 : "connection is unrecoverable",
228 : FALSE);
229 :
230 0 : } else if (remote_rsc->next_role == pcmk_role_stopped) {
231 : /* State must be remote_state_unknown or remote_state_stopped.
232 : * Since the connection is not coming back up in this
233 : * transition, stop this resource first.
234 : */
235 0 : order_action_then_stop(action, remote_rsc,
236 : pcmk__ar_then_implies_first);
237 :
238 : } else {
239 : /* The connection is going to be started somewhere else, so
240 : * stop this resource after that completes.
241 : */
242 0 : order_start_then_action(remote_rsc, action, pcmk__ar_none);
243 : }
244 0 : break;
245 :
246 0 : case pcmk_action_demote:
247 : /* Only order this demote relative to the connection start if the
248 : * connection isn't being torn down. Otherwise, the demote would be
249 : * blocked because the connection start would not be allowed.
250 : */
251 0 : if ((state == remote_state_resting)
252 0 : || (state == remote_state_unknown)) {
253 :
254 0 : order_start_then_action(remote_rsc, action, pcmk__ar_none);
255 : } /* Otherwise we can rely on the stop ordering */
256 0 : break;
257 :
258 0 : default:
259 : /* Wait for the connection resource to be up */
260 0 : if (pcmk__action_is_recurring(action)) {
261 : /* In case we ever get the recovery logic wrong, force
262 : * recurring monitors to be restarted, even if just
263 : * the connection was re-established
264 : */
265 0 : order_start_then_action(remote_rsc, action,
266 : pcmk__ar_first_implies_then);
267 :
268 : } else {
269 0 : pcmk_node_t *cluster_node = pcmk__current_node(remote_rsc);
270 :
271 0 : if ((task == pcmk_action_monitor) && (state == remote_state_failed)) {
272 : /* We would only be here if we do not know the state of the
273 : * resource on the remote node. Since we have no way to find
274 : * out, it is necessary to fence the node.
275 : */
276 0 : pe_fence_node(remote_rsc->cluster, action->node,
277 : "resources are in unknown state "
278 : "and connection is unrecoverable", FALSE);
279 : }
280 :
281 0 : if ((cluster_node != NULL) && (state == remote_state_stopped)) {
282 : /* The connection is currently up, but is going down
283 : * permanently. Make sure we check services are actually
284 : * stopped _before_ we let the connection get closed.
285 : */
286 0 : order_action_then_stop(action, remote_rsc,
287 : pcmk__ar_unrunnable_first_blocks);
288 :
289 : } else {
290 0 : order_start_then_action(remote_rsc, action, pcmk__ar_none);
291 : }
292 : }
293 0 : break;
294 : }
295 : }
296 :
297 : static void
298 0 : apply_container_ordering(pcmk_action_t *action)
299 : {
300 : /* VMs are also classified as containers for these purposes... in
301 : * that they both involve a 'thing' running on a real or remote
302 : * cluster node.
303 : *
304 : * This allows us to be smarter about the type and extent of
305 : * recovery actions required in various scenarios
306 : */
307 0 : pcmk_resource_t *remote_rsc = NULL;
308 0 : pcmk_resource_t *container = NULL;
309 0 : enum action_tasks task = pcmk_parse_action(action->task);
310 :
311 0 : CRM_ASSERT(action->rsc != NULL);
312 0 : CRM_ASSERT(action->node != NULL);
313 0 : CRM_ASSERT(pcmk__is_pacemaker_remote_node(action->node));
314 :
315 0 : remote_rsc = action->node->details->remote_rsc;
316 0 : CRM_ASSERT(remote_rsc != NULL);
317 :
318 0 : container = remote_rsc->container;
319 0 : CRM_ASSERT(container != NULL);
320 :
321 0 : if (pcmk_is_set(container->flags, pcmk_rsc_failed)) {
322 0 : pe_fence_node(action->rsc->cluster, action->node, "container failed",
323 : FALSE);
324 : }
325 :
326 0 : crm_trace("Order %s action %s relative to %s%s for %s%s",
327 : action->task, action->uuid,
328 : pcmk_is_set(remote_rsc->flags, pcmk_rsc_failed)? "failed " : "",
329 : remote_rsc->id,
330 : pcmk_is_set(container->flags, pcmk_rsc_failed)? "failed " : "",
331 : container->id);
332 :
333 0 : if (pcmk__strcase_any_of(action->task, PCMK_ACTION_MIGRATE_TO,
334 : PCMK_ACTION_MIGRATE_FROM, NULL)) {
335 : /* Migration ops map to pcmk_action_unspecified, but we need to apply
336 : * the same ordering as for stop or demote (see get_router_node()).
337 : */
338 0 : task = pcmk_action_stop;
339 : }
340 :
341 0 : switch (task) {
342 0 : case pcmk_action_start:
343 : case pcmk_action_promote:
344 : // Force resource recovery if the container is recovered
345 0 : order_start_then_action(container, action,
346 : pcmk__ar_first_implies_then);
347 :
348 : // Wait for the connection resource to be up, too
349 0 : order_start_then_action(remote_rsc, action, pcmk__ar_none);
350 0 : break;
351 :
352 0 : case pcmk_action_stop:
353 : case pcmk_action_demote:
354 0 : if (pcmk_is_set(container->flags, pcmk_rsc_failed)) {
355 : /* When the container representing a guest node fails, any stop
356 : * or demote actions for resources running on the guest node
357 : * are implied by the container stopping. This is similar to
358 : * how fencing operations work for cluster nodes and remote
359 : * nodes.
360 : */
361 : } else {
362 : /* Ensure the operation happens before the connection is brought
363 : * down.
364 : *
365 : * If we really wanted to, we could order these after the
366 : * connection start, IFF the container's current role was
367 : * stopped (otherwise we re-introduce an ordering loop when the
368 : * connection is restarting).
369 : */
370 0 : order_action_then_stop(action, remote_rsc, pcmk__ar_none);
371 : }
372 0 : break;
373 :
374 0 : default:
375 : /* Wait for the connection resource to be up */
376 0 : if (pcmk__action_is_recurring(action)) {
377 : /* In case we ever get the recovery logic wrong, force
378 : * recurring monitors to be restarted, even if just
379 : * the connection was re-established
380 : */
381 0 : if (task != pcmk_action_unspecified) {
382 0 : order_start_then_action(remote_rsc, action,
383 : pcmk__ar_first_implies_then);
384 : }
385 : } else {
386 0 : order_start_then_action(remote_rsc, action, pcmk__ar_none);
387 : }
388 0 : break;
389 : }
390 0 : }
391 :
392 : /*!
393 : * \internal
394 : * \brief Order all relevant actions relative to remote connection actions
395 : *
396 : * \param[in,out] scheduler Scheduler data
397 : */
398 : void
399 0 : pcmk__order_remote_connection_actions(pcmk_scheduler_t *scheduler)
400 : {
401 0 : if (!pcmk_is_set(scheduler->flags, pcmk_sched_have_remote_nodes)) {
402 0 : return;
403 : }
404 :
405 0 : crm_trace("Creating remote connection orderings");
406 :
407 0 : for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) {
408 0 : pcmk_action_t *action = iter->data;
409 0 : pcmk_resource_t *remote = NULL;
410 :
411 : // We are only interested in resource actions
412 0 : if (action->rsc == NULL) {
413 0 : continue;
414 : }
415 :
416 : /* Special case: If we are clearing the failcount of an actual
417 : * remote connection resource, then make sure this happens before
418 : * any start of the resource in this transition.
419 : */
420 0 : if (action->rsc->is_remote_node &&
421 0 : pcmk__str_eq(action->task, PCMK_ACTION_CLEAR_FAILCOUNT,
422 : pcmk__str_none)) {
423 :
424 0 : pcmk__new_ordering(action->rsc, NULL, action, action->rsc,
425 0 : pcmk__op_key(action->rsc->id, PCMK_ACTION_START,
426 : 0),
427 : NULL, pcmk__ar_ordered, scheduler);
428 :
429 0 : continue;
430 : }
431 :
432 : // We are only interested in actions assigned to a node
433 0 : if (action->node == NULL) {
434 0 : continue;
435 : }
436 :
437 0 : if (!pcmk__is_pacemaker_remote_node(action->node)) {
438 0 : continue;
439 : }
440 :
441 : /* We are only interested in real actions.
442 : *
443 : * @TODO This is probably wrong; pseudo-actions might be converted to
444 : * real actions and vice versa later in update_actions() at the end of
445 : * pcmk__apply_orderings().
446 : */
447 0 : if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
448 0 : continue;
449 : }
450 :
451 0 : remote = action->node->details->remote_rsc;
452 0 : if (remote == NULL) {
453 : // Orphaned
454 0 : continue;
455 : }
456 :
457 : /* Another special case: if a resource is moving to a Pacemaker Remote
458 : * node, order the stop on the original node after any start of the
459 : * remote connection. This ensures that if the connection fails to
460 : * start, we leave the resource running on the original node.
461 : */
462 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none)) {
463 0 : for (GList *item = action->rsc->actions; item != NULL;
464 0 : item = item->next) {
465 0 : pcmk_action_t *rsc_action = item->data;
466 :
467 0 : if (!pcmk__same_node(rsc_action->node, action->node)
468 0 : && pcmk__str_eq(rsc_action->task, PCMK_ACTION_STOP,
469 : pcmk__str_none)) {
470 0 : pcmk__new_ordering(remote, start_key(remote), NULL,
471 : action->rsc, NULL, rsc_action,
472 : pcmk__ar_ordered, scheduler);
473 : }
474 : }
475 : }
476 :
477 : /* The action occurs across a remote connection, so create
478 : * ordering constraints that guarantee the action occurs while the node
479 : * is active (after start, before stop ... things like that).
480 : *
481 : * This is somewhat brittle in that we need to make sure the results of
482 : * this ordering are compatible with the result of get_router_node().
483 : * It would probably be better to add PCMK__XA_ROUTER_NODE as part of
484 : * this logic rather than create_graph_action().
485 : */
486 0 : if (remote->container) {
487 0 : crm_trace("Container ordering for %s", action->uuid);
488 0 : apply_container_ordering(action);
489 :
490 : } else {
491 0 : crm_trace("Remote ordering for %s", action->uuid);
492 0 : apply_remote_ordering(action);
493 : }
494 : }
495 : }
496 :
497 : /*!
498 : * \internal
499 : * \brief Check whether a node is a failed remote node
500 : *
501 : * \param[in] node Node to check
502 : *
503 : * \return true if \p node is a failed remote node, false otherwise
504 : */
505 : bool
506 0 : pcmk__is_failed_remote_node(const pcmk_node_t *node)
507 : {
508 0 : return pcmk__is_remote_node(node) && (node->details->remote_rsc != NULL)
509 0 : && (get_remote_node_state(node) == remote_state_failed);
510 : }
511 :
512 : /*!
513 : * \internal
514 : * \brief Check whether a given resource corresponds to a given node as guest
515 : *
516 : * \param[in] rsc Resource to check
517 : * \param[in] node Node to check
518 : *
519 : * \return true if \p node is a guest node and \p rsc is its containing
520 : * resource, otherwise false
521 : */
522 : bool
523 0 : pcmk__rsc_corresponds_to_guest(const pcmk_resource_t *rsc,
524 : const pcmk_node_t *node)
525 : {
526 0 : return (rsc != NULL) && (rsc->fillers != NULL) && (node != NULL)
527 0 : && (node->details->remote_rsc != NULL)
528 0 : && (node->details->remote_rsc->container == rsc);
529 : }
530 :
531 : /*!
532 : * \internal
533 : * \brief Get proper connection host that a remote action must be routed through
534 : *
535 : * A remote connection resource might be starting, stopping, or migrating in the
536 : * same transition that an action needs to be executed on its Pacemaker Remote
537 : * node. Determine the proper node that the remote action should be routed
538 : * through.
539 : *
540 : * \param[in] action (Potentially remote) action to route
541 : *
542 : * \return Connection host that action should be routed through if remote,
543 : * otherwise NULL
544 : */
545 : pcmk_node_t *
546 0 : pcmk__connection_host_for_action(const pcmk_action_t *action)
547 : {
548 0 : pcmk_node_t *began_on = NULL;
549 0 : pcmk_node_t *ended_on = NULL;
550 0 : bool partial_migration = false;
551 0 : const char *task = action->task;
552 :
553 0 : if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_none)
554 0 : || !pcmk__is_pacemaker_remote_node(action->node)) {
555 0 : return NULL;
556 : }
557 :
558 0 : CRM_ASSERT(action->node->details->remote_rsc != NULL);
559 :
560 0 : began_on = pcmk__current_node(action->node->details->remote_rsc);
561 0 : ended_on = action->node->details->remote_rsc->allocated_to;
562 0 : if (action->node->details->remote_rsc
563 0 : && (action->node->details->remote_rsc->container == NULL)
564 0 : && action->node->details->remote_rsc->partial_migration_target) {
565 0 : partial_migration = true;
566 : }
567 :
568 0 : if (began_on == NULL) {
569 0 : crm_trace("Routing %s for %s through remote connection's "
570 : "next node %s (starting)%s",
571 : action->task, (action->rsc? action->rsc->id : "no resource"),
572 : (ended_on? ended_on->details->uname : "none"),
573 : partial_migration? " (partial migration)" : "");
574 0 : return ended_on;
575 : }
576 :
577 0 : if (ended_on == NULL) {
578 0 : crm_trace("Routing %s for %s through remote connection's "
579 : "current node %s (stopping)%s",
580 : action->task, (action->rsc? action->rsc->id : "no resource"),
581 : (began_on? began_on->details->uname : "none"),
582 : partial_migration? " (partial migration)" : "");
583 0 : return began_on;
584 : }
585 :
586 0 : if (pcmk__same_node(began_on, ended_on)) {
587 0 : crm_trace("Routing %s for %s through remote connection's "
588 : "current node %s (not moving)%s",
589 : action->task, (action->rsc? action->rsc->id : "no resource"),
590 : (began_on? began_on->details->uname : "none"),
591 : partial_migration? " (partial migration)" : "");
592 0 : return began_on;
593 : }
594 :
595 : /* If we get here, the remote connection is moving during this transition.
596 : * This means some actions for resources behind the connection will get
597 : * routed through the cluster node the connection resource is currently on,
598 : * and others are routed through the cluster node the connection will end up
599 : * on.
600 : */
601 :
602 0 : if (pcmk__str_eq(task, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
603 0 : task = g_hash_table_lookup(action->meta, "notify_operation");
604 : }
605 :
606 : /*
607 : * Stop, demote, and migration actions must occur before the connection can
608 : * move (these actions are required before the remote resource can stop). In
609 : * this case, we know these actions have to be routed through the initial
610 : * cluster node the connection resource lived on before the move takes
611 : * place.
612 : *
613 : * The exception is a partial migration of a (non-guest) remote connection
614 : * resource; in that case, all actions (even these) will be ordered after
615 : * the connection's pseudo-start on the migration target, so the target is
616 : * the router node.
617 : */
618 0 : if (pcmk__strcase_any_of(task, PCMK_ACTION_CANCEL, PCMK_ACTION_STOP,
619 : PCMK_ACTION_DEMOTE, PCMK_ACTION_MIGRATE_FROM,
620 : PCMK_ACTION_MIGRATE_TO, NULL)
621 0 : && !partial_migration) {
622 0 : crm_trace("Routing %s for %s through remote connection's "
623 : "current node %s (moving)%s",
624 : action->task, (action->rsc? action->rsc->id : "no resource"),
625 : (began_on? began_on->details->uname : "none"),
626 : partial_migration? " (partial migration)" : "");
627 0 : return began_on;
628 : }
629 :
630 : /* Everything else (start, promote, monitor, probe, refresh,
631 : * clear failcount, delete, ...) must occur after the connection starts on
632 : * the node it is moving to.
633 : */
634 0 : crm_trace("Routing %s for %s through remote connection's "
635 : "next node %s (moving)%s",
636 : action->task, (action->rsc? action->rsc->id : "no resource"),
637 : (ended_on? ended_on->details->uname : "none"),
638 : partial_migration? " (partial migration)" : "");
639 0 : return ended_on;
640 : }
641 :
642 : /*!
643 : * \internal
644 : * \brief Replace remote connection's addr="#uname" with actual address
645 : *
646 : * REMOTE_CONTAINER_HACK: If a given resource is a remote connection resource
647 : * with its "addr" parameter set to "#uname", pull the actual value from the
648 : * parameters evaluated without a node (which was put there earlier in
649 : * pcmk__create_graph() when the bundle's expand() method was called).
650 : *
651 : * \param[in,out] rsc Resource to check
652 : * \param[in,out] params Resource parameters evaluated per node
653 : */
654 : void
655 0 : pcmk__substitute_remote_addr(pcmk_resource_t *rsc, GHashTable *params)
656 : {
657 0 : const char *remote_addr = g_hash_table_lookup(params, PCMK_REMOTE_RA_ADDR);
658 :
659 0 : if (pcmk__str_eq(remote_addr, "#uname", pcmk__str_none)) {
660 0 : GHashTable *base = pe_rsc_params(rsc, NULL, rsc->cluster);
661 :
662 0 : remote_addr = g_hash_table_lookup(base, PCMK_REMOTE_RA_ADDR);
663 0 : if (remote_addr != NULL) {
664 0 : pcmk__insert_dup(params, PCMK_REMOTE_RA_ADDR, remote_addr);
665 : }
666 : }
667 0 : }
668 :
669 : /*!
670 : * \brief Add special guest node meta-attributes to XML
671 : *
672 : * If a given action will be executed on a guest node, add the following as XML
673 : * attributes (using meta-attribute naming):
674 : * * The resource's \c PCMK_META_CONTAINER_ATTRIBUTE_TARGET meta-attribute
675 : * (usually set only for bundles), as \c PCMK_META_CONTAINER_ATTRIBUTE_TARGET
676 : * * The guest's physical host (current host for "down" actions, next host for
677 : * "up" actions), as \c PCMK__META_PHYSICAL_HOST
678 : *
679 : * If the guest node has no physical host, then don't add either attribute.
680 : *
681 : * \param[in,out] args_xml XML to add attributes to
682 : * \param[in] action Action to check
683 : */
684 : void
685 0 : pcmk__add_guest_meta_to_xml(xmlNode *args_xml, const pcmk_action_t *action)
686 : {
687 0 : const pcmk_node_t *guest = action->node;
688 0 : const pcmk_node_t *host = NULL;
689 : enum action_tasks task;
690 :
691 0 : if (!pcmk__is_guest_or_bundle_node(guest)) {
692 0 : return;
693 : }
694 :
695 0 : task = pcmk_parse_action(action->task);
696 0 : if ((task == pcmk_action_notify) || (task == pcmk_action_notified)) {
697 0 : task = pcmk_parse_action(g_hash_table_lookup(action->meta,
698 : "notify_operation"));
699 : }
700 :
701 0 : switch (task) {
702 0 : case pcmk_action_stop:
703 : case pcmk_action_stopped:
704 : case pcmk_action_demote:
705 : case pcmk_action_demoted:
706 : // "Down" actions take place on guest's current host
707 0 : host = pcmk__current_node(guest->details->remote_rsc->container);
708 0 : break;
709 :
710 0 : case pcmk_action_start:
711 : case pcmk_action_started:
712 : case pcmk_action_monitor:
713 : case pcmk_action_promote:
714 : case pcmk_action_promoted:
715 : // "Up" actions take place on guest's next host
716 0 : host = guest->details->remote_rsc->container->allocated_to;
717 0 : break;
718 :
719 0 : default:
720 0 : break;
721 : }
722 :
723 0 : if (host != NULL) {
724 : gpointer target =
725 0 : g_hash_table_lookup(action->rsc->meta,
726 : PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
727 :
728 0 : hash2metafield((gpointer) PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
729 : target,
730 : (gpointer) args_xml);
731 0 : hash2metafield((gpointer) PCMK__META_PHYSICAL_HOST,
732 0 : (gpointer) host->details->uname,
733 : (gpointer) args_xml);
734 : }
735 : }
|