Line data Source code
1 : /*
2 : * Copyright 2004-2024 the Pacemaker project contributors
3 : *
4 : * The version control history for this file may have further details.
5 : *
6 : * This source code is licensed under the GNU Lesser General Public License
7 : * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <glib.h>
13 : #include <stdbool.h>
14 :
15 : #include <crm/crm.h>
16 : #include <crm/common/xml.h>
17 : #include <crm/common/scheduler_internal.h>
18 : #include <crm/pengine/internal.h>
19 : #include <crm/common/xml_internal.h>
20 : #include "pe_status_private.h"
21 :
22 : static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
23 : guint interval_ms);
24 :
25 : static void
26 0 : add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action)
27 : {
28 0 : if (scheduler->singletons == NULL) {
29 0 : scheduler->singletons = pcmk__strkey_table(NULL, NULL);
30 : }
31 0 : g_hash_table_insert(scheduler->singletons, action->uuid, action);
32 0 : }
33 :
34 : static pcmk_action_t *
35 0 : lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid)
36 : {
37 0 : if (scheduler->singletons == NULL) {
38 0 : return NULL;
39 : }
40 0 : return g_hash_table_lookup(scheduler->singletons, action_uuid);
41 : }
42 :
43 : /*!
44 : * \internal
45 : * \brief Find an existing action that matches arguments
46 : *
47 : * \param[in] key Action key to match
48 : * \param[in] rsc Resource to match (if any)
49 : * \param[in] node Node to match (if any)
50 : * \param[in] scheduler Scheduler data
51 : *
52 : * \return Existing action that matches arguments (or NULL if none)
53 : */
54 : static pcmk_action_t *
55 0 : find_existing_action(const char *key, const pcmk_resource_t *rsc,
56 : const pcmk_node_t *node, const pcmk_scheduler_t *scheduler)
57 : {
58 0 : GList *matches = NULL;
59 0 : pcmk_action_t *action = NULL;
60 :
61 : /* When rsc is NULL, it would be quicker to check scheduler->singletons,
62 : * but checking all scheduler->actions takes the node into account.
63 : */
64 0 : matches = find_actions(((rsc == NULL)? scheduler->actions : rsc->actions),
65 : key, node);
66 0 : if (matches == NULL) {
67 0 : return NULL;
68 : }
69 0 : CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches));
70 :
71 0 : action = matches->data;
72 0 : g_list_free(matches);
73 0 : return action;
74 : }
75 :
76 : /*!
77 : * \internal
78 : * \brief Find the XML configuration corresponding to a specific action key
79 : *
80 : * \param[in] rsc Resource to find action configuration for
81 : * \param[in] key "RSC_ACTION_INTERVAL" of action to find
82 : * \param[in] include_disabled If false, do not return disabled actions
83 : *
84 : * \return XML configuration of desired action if any, otherwise NULL
85 : */
86 : static xmlNode *
87 0 : find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name,
88 : guint interval_ms, bool include_disabled)
89 : {
90 0 : for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
91 : NULL, NULL);
92 0 : operation != NULL; operation = pcmk__xe_next_same(operation)) {
93 :
94 0 : bool enabled = false;
95 0 : const char *config_name = NULL;
96 0 : const char *interval_spec = NULL;
97 0 : guint tmp_ms = 0U;
98 :
99 : // @TODO This does not consider meta-attributes, rules, defaults, etc.
100 0 : if (!include_disabled
101 0 : && (pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
102 0 : &enabled) == pcmk_rc_ok) && !enabled) {
103 0 : continue;
104 : }
105 :
106 0 : interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
107 0 : pcmk_parse_interval_spec(interval_spec, &tmp_ms);
108 0 : if (tmp_ms != interval_ms) {
109 0 : continue;
110 : }
111 :
112 0 : config_name = crm_element_value(operation, PCMK_XA_NAME);
113 0 : if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) {
114 0 : return operation;
115 : }
116 : }
117 0 : return NULL;
118 : }
119 :
120 : /*!
121 : * \internal
122 : * \brief Find the XML configuration of a resource action
123 : *
124 : * \param[in] rsc Resource to find action configuration for
125 : * \param[in] action_name Action name to search for
126 : * \param[in] interval_ms Action interval (in milliseconds) to search for
127 : * \param[in] include_disabled If false, do not return disabled actions
128 : *
129 : * \return XML configuration of desired action if any, otherwise NULL
130 : */
131 : xmlNode *
132 0 : pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name,
133 : guint interval_ms, bool include_disabled)
134 : {
135 0 : xmlNode *action_config = NULL;
136 :
137 : // Try requested action first
138 0 : action_config = find_exact_action_config(rsc, action_name, interval_ms,
139 : include_disabled);
140 :
141 : // For migrate_to and migrate_from actions, retry with "migrate"
142 : // @TODO This should be either documented or deprecated
143 0 : if ((action_config == NULL)
144 0 : && pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO,
145 : PCMK_ACTION_MIGRATE_FROM, NULL)) {
146 0 : action_config = find_exact_action_config(rsc, "migrate", 0,
147 : include_disabled);
148 : }
149 :
150 0 : return action_config;
151 : }
152 :
153 : /*!
154 : * \internal
155 : * \brief Create a new action object
156 : *
157 : * \param[in] key Action key
158 : * \param[in] task Action name
159 : * \param[in,out] rsc Resource that action is for (if any)
160 : * \param[in] node Node that action is on (if any)
161 : * \param[in] optional Whether action should be considered optional
162 : * \param[in,out] scheduler Scheduler data
163 : *
164 : * \return Newly allocated action
165 : * \note This function takes ownership of \p key. It is the caller's
166 : * responsibility to free the return value with pe_free_action().
167 : */
168 : static pcmk_action_t *
169 0 : new_action(char *key, const char *task, pcmk_resource_t *rsc,
170 : const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler)
171 : {
172 0 : pcmk_action_t *action = pcmk__assert_alloc(1, sizeof(pcmk_action_t));
173 :
174 0 : action->rsc = rsc;
175 0 : action->task = pcmk__str_copy(task);
176 0 : action->uuid = key;
177 :
178 0 : if (node) {
179 0 : action->node = pe__copy_node(node);
180 : }
181 :
182 0 : if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) {
183 : // Resource history deletion for a node can be done on the DC
184 0 : pcmk__set_action_flags(action, pcmk_action_on_dc);
185 : }
186 :
187 0 : pcmk__set_action_flags(action, pcmk_action_runnable);
188 0 : if (optional) {
189 0 : pcmk__set_action_flags(action, pcmk_action_optional);
190 : } else {
191 0 : pcmk__clear_action_flags(action, pcmk_action_optional);
192 : }
193 :
194 0 : if (rsc == NULL) {
195 0 : action->meta = pcmk__strkey_table(free, free);
196 : } else {
197 0 : guint interval_ms = 0;
198 :
199 0 : parse_op_key(key, NULL, NULL, &interval_ms);
200 0 : action->op_entry = pcmk__find_action_config(rsc, task, interval_ms,
201 : true);
202 :
203 : /* If the given key is for one of the many notification pseudo-actions
204 : * (pre_notify_promote, etc.), the actual action name is "notify"
205 : */
206 0 : if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) {
207 0 : action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY,
208 : 0, true);
209 : }
210 :
211 0 : unpack_operation(action, action->op_entry, interval_ms);
212 : }
213 :
214 0 : pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s",
215 : (optional? "optional" : "required"),
216 : scheduler->action_id, key, task,
217 : ((rsc == NULL)? "no resource" : rsc->id),
218 : pcmk__node_name(node));
219 0 : action->id = scheduler->action_id++;
220 :
221 0 : scheduler->actions = g_list_prepend(scheduler->actions, action);
222 0 : if (rsc == NULL) {
223 0 : add_singleton(scheduler, action);
224 : } else {
225 0 : rsc->actions = g_list_prepend(rsc->actions, action);
226 : }
227 0 : return action;
228 : }
229 :
230 : /*!
231 : * \internal
232 : * \brief Unpack a resource's action-specific instance parameters
233 : *
234 : * \param[in] action_xml XML of action's configuration in CIB (if any)
235 : * \param[in,out] node_attrs Table of node attributes (for rule evaluation)
236 : * \param[in,out] scheduler Cluster working set (for rule evaluation)
237 : *
238 : * \return Newly allocated hash table of action-specific instance parameters
239 : */
240 : GHashTable *
241 0 : pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
242 : GHashTable *node_attrs,
243 : pcmk_scheduler_t *scheduler)
244 : {
245 0 : GHashTable *params = pcmk__strkey_table(free, free);
246 :
247 0 : pe_rule_eval_data_t rule_data = {
248 : .node_hash = node_attrs,
249 0 : .now = scheduler->now,
250 : .match_data = NULL,
251 : .rsc_data = NULL,
252 : .op_data = NULL
253 : };
254 :
255 0 : pe__unpack_dataset_nvpairs(action_xml, PCMK_XE_INSTANCE_ATTRIBUTES,
256 : &rule_data, params, NULL,
257 : FALSE, scheduler);
258 0 : return params;
259 : }
260 :
261 : /*!
262 : * \internal
263 : * \brief Update an action's optional flag
264 : *
265 : * \param[in,out] action Action to update
266 : * \param[in] optional Requested optional status
267 : */
268 : static void
269 0 : update_action_optional(pcmk_action_t *action, gboolean optional)
270 : {
271 : // Force a non-recurring action to be optional if its resource is unmanaged
272 0 : if ((action->rsc != NULL) && (action->node != NULL)
273 0 : && !pcmk_is_set(action->flags, pcmk_action_pseudo)
274 0 : && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
275 0 : && (g_hash_table_lookup(action->meta, PCMK_META_INTERVAL) == NULL)) {
276 0 : pcmk__rsc_debug(action->rsc,
277 : "%s on %s is optional (%s is unmanaged)",
278 : action->uuid, pcmk__node_name(action->node),
279 : action->rsc->id);
280 0 : pcmk__set_action_flags(action, pcmk_action_optional);
281 : // We shouldn't clear runnable here because ... something
282 :
283 : // Otherwise require the action if requested
284 0 : } else if (!optional) {
285 0 : pcmk__clear_action_flags(action, pcmk_action_optional);
286 : }
287 0 : }
288 :
289 : static enum pe_quorum_policy
290 0 : effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
291 : {
292 0 : enum pe_quorum_policy policy = scheduler->no_quorum_policy;
293 :
294 0 : if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
295 0 : policy = pcmk_no_quorum_ignore;
296 :
297 0 : } else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) {
298 0 : switch (rsc->role) {
299 0 : case pcmk_role_promoted:
300 : case pcmk_role_unpromoted:
301 0 : if (rsc->next_role > pcmk_role_unpromoted) {
302 0 : pe__set_next_role(rsc, pcmk_role_unpromoted,
303 : PCMK_OPT_NO_QUORUM_POLICY "=demote");
304 : }
305 0 : policy = pcmk_no_quorum_ignore;
306 0 : break;
307 0 : default:
308 0 : policy = pcmk_no_quorum_stop;
309 0 : break;
310 : }
311 : }
312 0 : return policy;
313 : }
314 :
315 : /*!
316 : * \internal
317 : * \brief Update a resource action's runnable flag
318 : *
319 : * \param[in,out] action Action to update
320 : * \param[in,out] scheduler Scheduler data
321 : *
322 : * \note This may also schedule fencing if a stop is unrunnable.
323 : */
324 : static void
325 0 : update_resource_action_runnable(pcmk_action_t *action,
326 : pcmk_scheduler_t *scheduler)
327 : {
328 0 : if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
329 0 : return;
330 : }
331 :
332 0 : if (action->node == NULL) {
333 0 : pcmk__rsc_trace(action->rsc, "%s is unrunnable (unallocated)",
334 : action->uuid);
335 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
336 :
337 0 : } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
338 0 : && !(action->node->details->online)
339 0 : && (!pcmk__is_guest_or_bundle_node(action->node)
340 0 : || action->node->details->remote_requires_reset)) {
341 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
342 0 : do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)",
343 : action->uuid, pcmk__node_name(action->node));
344 0 : if (pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
345 0 : && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)
346 0 : && !(action->node->details->unclean)) {
347 0 : pe_fence_node(scheduler, action->node, "stop is unrunnable", false);
348 : }
349 :
350 0 : } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
351 0 : && action->node->details->pending) {
352 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
353 0 : do_crm_log(LOG_WARNING,
354 : "Action %s on %s is unrunnable (node is pending)",
355 : action->uuid, pcmk__node_name(action->node));
356 :
357 0 : } else if (action->needs == pcmk_requires_nothing) {
358 0 : pe_action_set_reason(action, NULL, TRUE);
359 0 : if (pcmk__is_guest_or_bundle_node(action->node)
360 0 : && !pe_can_fence(scheduler, action->node)) {
361 : /* An action that requires nothing usually does not require any
362 : * fencing in order to be runnable. However, there is an exception:
363 : * such an action cannot be completed if it is on a guest node whose
364 : * host is unclean and cannot be fenced.
365 : */
366 0 : pcmk__rsc_debug(action->rsc,
367 : "%s on %s is unrunnable "
368 : "(node's host cannot be fenced)",
369 : action->uuid, pcmk__node_name(action->node));
370 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
371 : } else {
372 0 : pcmk__rsc_trace(action->rsc,
373 : "%s on %s does not require fencing or quorum",
374 : action->uuid, pcmk__node_name(action->node));
375 0 : pcmk__set_action_flags(action, pcmk_action_runnable);
376 : }
377 :
378 : } else {
379 0 : switch (effective_quorum_policy(action->rsc, scheduler)) {
380 0 : case pcmk_no_quorum_stop:
381 0 : pcmk__rsc_debug(action->rsc,
382 : "%s on %s is unrunnable (no quorum)",
383 : action->uuid, pcmk__node_name(action->node));
384 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
385 0 : pe_action_set_reason(action, "no quorum", true);
386 0 : break;
387 :
388 0 : case pcmk_no_quorum_freeze:
389 0 : if (!action->rsc->fns->active(action->rsc, TRUE)
390 0 : || (action->rsc->next_role > action->rsc->role)) {
391 0 : pcmk__rsc_debug(action->rsc,
392 : "%s on %s is unrunnable (no quorum)",
393 : action->uuid,
394 : pcmk__node_name(action->node));
395 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
396 0 : pe_action_set_reason(action, "quorum freeze", true);
397 : }
398 0 : break;
399 :
400 0 : default:
401 : //pe_action_set_reason(action, NULL, TRUE);
402 0 : pcmk__set_action_flags(action, pcmk_action_runnable);
403 0 : break;
404 : }
405 : }
406 : }
407 :
408 : /*!
409 : * \internal
410 : * \brief Update a resource object's flags for a new action on it
411 : *
412 : * \param[in,out] rsc Resource that action is for (if any)
413 : * \param[in] action New action
414 : */
415 : static void
416 0 : update_resource_flags_for_action(pcmk_resource_t *rsc,
417 : const pcmk_action_t *action)
418 : {
419 : /* @COMPAT pcmk_rsc_starting and pcmk_rsc_stopping are deprecated and unused
420 : * within Pacemaker, and will eventually be removed
421 : */
422 0 : if (pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)) {
423 0 : pcmk__set_rsc_flags(rsc, pcmk_rsc_stopping);
424 :
425 0 : } else if (pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_casei)) {
426 0 : if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
427 0 : pcmk__set_rsc_flags(rsc, pcmk_rsc_starting);
428 : } else {
429 0 : pcmk__clear_rsc_flags(rsc, pcmk_rsc_starting);
430 : }
431 : }
432 0 : }
433 :
434 : static bool
435 0 : valid_stop_on_fail(const char *value)
436 : {
437 0 : return !pcmk__strcase_any_of(value,
438 : PCMK_VALUE_STANDBY, PCMK_VALUE_DEMOTE,
439 0 : PCMK_VALUE_STOP, NULL);
440 : }
441 :
442 : /*!
443 : * \internal
444 : * \brief Validate (and possibly reset) resource action's on_fail meta-attribute
445 : *
446 : * \param[in] rsc Resource that action is for
447 : * \param[in] action_name Action name
448 : * \param[in] action_config Action configuration XML from CIB (if any)
449 : * \param[in,out] meta Table of action meta-attributes
450 : */
451 : static void
452 0 : validate_on_fail(const pcmk_resource_t *rsc, const char *action_name,
453 : const xmlNode *action_config, GHashTable *meta)
454 : {
455 0 : const char *name = NULL;
456 0 : const char *role = NULL;
457 0 : const char *interval_spec = NULL;
458 0 : const char *value = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
459 0 : guint interval_ms = 0U;
460 :
461 : // Stop actions can only use certain on-fail values
462 0 : if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)
463 0 : && !valid_stop_on_fail(value)) {
464 :
465 0 : pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s stop "
466 : "action to default value because '%s' is not "
467 : "allowed for stop", rsc->id, value);
468 0 : g_hash_table_remove(meta, PCMK_META_ON_FAIL);
469 0 : return;
470 : }
471 :
472 : /* Demote actions default on-fail to the on-fail value for the first
473 : * recurring monitor for the promoted role (if any).
474 : */
475 0 : if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)
476 0 : && (value == NULL)) {
477 :
478 : /* @TODO This does not consider promote options set in a meta-attribute
479 : * block (which may have rules that need to be evaluated) rather than
480 : * XML properties.
481 : */
482 0 : for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
483 : NULL, NULL);
484 0 : operation != NULL; operation = pcmk__xe_next_same(operation)) {
485 :
486 0 : bool enabled = false;
487 0 : const char *promote_on_fail = NULL;
488 :
489 : /* We only care about explicit on-fail (if promote uses default, so
490 : * can demote)
491 : */
492 0 : promote_on_fail = crm_element_value(operation, PCMK_META_ON_FAIL);
493 0 : if (promote_on_fail == NULL) {
494 0 : continue;
495 : }
496 :
497 : // We only care about recurring monitors for the promoted role
498 0 : name = crm_element_value(operation, PCMK_XA_NAME);
499 0 : role = crm_element_value(operation, PCMK_XA_ROLE);
500 0 : if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
501 0 : || !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
502 : PCMK__ROLE_PROMOTED_LEGACY, NULL)) {
503 0 : continue;
504 : }
505 0 : interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
506 0 : pcmk_parse_interval_spec(interval_spec, &interval_ms);
507 0 : if (interval_ms == 0U) {
508 0 : continue;
509 : }
510 :
511 : // We only care about enabled monitors
512 0 : if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
513 0 : &enabled) == pcmk_rc_ok) && !enabled) {
514 0 : continue;
515 : }
516 :
517 : /* Demote actions can't default to
518 : * PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE
519 : */
520 0 : if (pcmk__str_eq(promote_on_fail, PCMK_VALUE_DEMOTE,
521 : pcmk__str_casei)) {
522 0 : continue;
523 : }
524 :
525 : // Use value from first applicable promote action found
526 0 : pcmk__insert_dup(meta, PCMK_META_ON_FAIL, promote_on_fail);
527 : }
528 0 : return;
529 : }
530 :
531 0 : if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none)
532 0 : && !pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) {
533 :
534 0 : pcmk__insert_dup(meta, PCMK_META_ON_FAIL, PCMK_VALUE_IGNORE);
535 0 : return;
536 : }
537 :
538 : // PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE is allowed only for certain actions
539 0 : if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
540 0 : name = crm_element_value(action_config, PCMK_XA_NAME);
541 0 : role = crm_element_value(action_config, PCMK_XA_ROLE);
542 0 : interval_spec = crm_element_value(action_config, PCMK_META_INTERVAL);
543 0 : pcmk_parse_interval_spec(interval_spec, &interval_ms);
544 :
545 0 : if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none)
546 0 : && ((interval_ms == 0U)
547 0 : || !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
548 0 : || !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
549 : PCMK__ROLE_PROMOTED_LEGACY, NULL))) {
550 :
551 0 : pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s %s "
552 : "action to default value because 'demote' is not "
553 : "allowed for it", rsc->id, name);
554 0 : g_hash_table_remove(meta, PCMK_META_ON_FAIL);
555 0 : return;
556 : }
557 : }
558 : }
559 :
560 : static int
561 0 : unpack_timeout(const char *value)
562 : {
563 0 : long long timeout_ms = crm_get_msec(value);
564 :
565 0 : if (timeout_ms <= 0) {
566 0 : timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
567 : }
568 0 : return (int) QB_MIN(timeout_ms, INT_MAX);
569 : }
570 :
571 : // true if value contains valid, non-NULL interval origin for recurring op
572 : static bool
573 0 : unpack_interval_origin(const char *value, const xmlNode *xml_obj,
574 : guint interval_ms, const crm_time_t *now,
575 : long long *start_delay)
576 : {
577 0 : long long result = 0;
578 0 : guint interval_sec = interval_ms / 1000;
579 0 : crm_time_t *origin = NULL;
580 :
581 : // Ignore unspecified values and non-recurring operations
582 0 : if ((value == NULL) || (interval_ms == 0) || (now == NULL)) {
583 0 : return false;
584 : }
585 :
586 : // Parse interval origin from text
587 0 : origin = crm_time_new(value);
588 0 : if (origin == NULL) {
589 0 : pcmk__config_err("Ignoring '" PCMK_META_INTERVAL_ORIGIN "' for "
590 : "operation '%s' because '%s' is not valid",
591 : pcmk__s(pcmk__xe_id(xml_obj), "(missing ID)"), value);
592 0 : return false;
593 : }
594 :
595 : // Get seconds since origin (negative if origin is in the future)
596 0 : result = crm_time_get_seconds(now) - crm_time_get_seconds(origin);
597 0 : crm_time_free(origin);
598 :
599 : // Calculate seconds from closest interval to now
600 0 : result = result % interval_sec;
601 :
602 : // Calculate seconds remaining until next interval
603 0 : result = ((result <= 0)? 0 : interval_sec) - result;
604 0 : crm_info("Calculated a start delay of %llds for operation '%s'",
605 : result, pcmk__s(pcmk__xe_id(xml_obj), "(unspecified)"));
606 :
607 0 : if (start_delay != NULL) {
608 0 : *start_delay = result * 1000; // milliseconds
609 : }
610 0 : return true;
611 : }
612 :
613 : static int
614 0 : unpack_start_delay(const char *value, GHashTable *meta)
615 : {
616 0 : long long start_delay_ms = 0;
617 :
618 0 : if (value == NULL) {
619 0 : return 0;
620 : }
621 :
622 0 : start_delay_ms = crm_get_msec(value);
623 0 : start_delay_ms = QB_MIN(start_delay_ms, INT_MAX);
624 0 : if (start_delay_ms < 0) {
625 0 : start_delay_ms = 0;
626 : }
627 :
628 0 : if (meta != NULL) {
629 0 : g_hash_table_replace(meta, strdup(PCMK_META_START_DELAY),
630 0 : pcmk__itoa(start_delay_ms));
631 : }
632 :
633 0 : return (int) start_delay_ms;
634 : }
635 :
636 : /*!
637 : * \internal
638 : * \brief Find a resource's most frequent recurring monitor
639 : *
640 : * \param[in] rsc Resource to check
641 : *
642 : * \return Operation XML configured for most frequent recurring monitor for
643 : * \p rsc (if any)
644 : */
645 : static xmlNode *
646 0 : most_frequent_monitor(const pcmk_resource_t *rsc)
647 : {
648 0 : guint min_interval_ms = G_MAXUINT;
649 0 : xmlNode *op = NULL;
650 :
651 0 : for (xmlNode *operation = pcmk__xe_first_child(rsc->ops_xml, PCMK_XE_OP,
652 : NULL, NULL);
653 0 : operation != NULL; operation = pcmk__xe_next_same(operation)) {
654 :
655 0 : bool enabled = false;
656 0 : guint interval_ms = 0U;
657 0 : const char *interval_spec = crm_element_value(operation,
658 : PCMK_META_INTERVAL);
659 :
660 : // We only care about enabled recurring monitors
661 0 : if (!pcmk__str_eq(crm_element_value(operation, PCMK_XA_NAME),
662 : PCMK_ACTION_MONITOR, pcmk__str_none)) {
663 0 : continue;
664 : }
665 :
666 0 : pcmk_parse_interval_spec(interval_spec, &interval_ms);
667 0 : if (interval_ms == 0U) {
668 0 : continue;
669 : }
670 :
671 : // @TODO This does not consider meta-attributes, rules, defaults, etc.
672 0 : if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
673 0 : &enabled) == pcmk_rc_ok) && !enabled) {
674 0 : continue;
675 : }
676 :
677 0 : if (interval_ms < min_interval_ms) {
678 0 : min_interval_ms = interval_ms;
679 0 : op = operation;
680 : }
681 : }
682 0 : return op;
683 : }
684 :
685 : /*!
686 : * \internal
687 : * \brief Unpack action meta-attributes
688 : *
689 : * \param[in,out] rsc Resource that action is for
690 : * \param[in] node Node that action is on
691 : * \param[in] action_name Action name
692 : * \param[in] interval_ms Action interval (in milliseconds)
693 : * \param[in] action_config Action XML configuration from CIB (if any)
694 : *
695 : * Unpack a resource action's meta-attributes (normalizing the interval,
696 : * timeout, and start delay values as integer milliseconds) from its CIB XML
697 : * configuration (including defaults).
698 : *
699 : * \return Newly allocated hash table with normalized action meta-attributes
700 : */
701 : GHashTable *
702 0 : pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node,
703 : const char *action_name, guint interval_ms,
704 : const xmlNode *action_config)
705 : {
706 0 : GHashTable *meta = NULL;
707 0 : const char *timeout_spec = NULL;
708 0 : const char *str = NULL;
709 :
710 0 : pe_rsc_eval_data_t rsc_rule_data = {
711 0 : .standard = crm_element_value(rsc->xml, PCMK_XA_CLASS),
712 0 : .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
713 0 : .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE),
714 : };
715 :
716 0 : pe_op_eval_data_t op_rule_data = {
717 : .op_name = action_name,
718 : .interval = interval_ms,
719 : };
720 :
721 0 : pe_rule_eval_data_t rule_data = {
722 : /* @COMPAT Support for node attribute expressions in operation
723 : * meta-attributes (whether in the operation configuration or operation
724 : * defaults) is deprecated. When we can break behavioral backward
725 : * compatibility, drop this line.
726 : */
727 0 : .node_hash = (node == NULL)? NULL : node->details->attrs,
728 :
729 0 : .now = rsc->cluster->now,
730 : .match_data = NULL,
731 : .rsc_data = &rsc_rule_data,
732 : .op_data = &op_rule_data,
733 : };
734 :
735 0 : meta = pcmk__strkey_table(free, free);
736 :
737 : // Cluster-wide <op_defaults> <meta_attributes>
738 0 : pe__unpack_dataset_nvpairs(rsc->cluster->op_defaults,
739 : PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL,
740 : FALSE, rsc->cluster);
741 :
742 : // Derive default timeout for probes from recurring monitor timeouts
743 0 : if (pcmk_is_probe(action_name, interval_ms)) {
744 0 : xmlNode *min_interval_mon = most_frequent_monitor(rsc);
745 :
746 0 : if (min_interval_mon != NULL) {
747 : /* @TODO This does not consider timeouts set in
748 : * PCMK_XE_META_ATTRIBUTES blocks (which may also have rules that
749 : * need to be evaluated).
750 : */
751 0 : timeout_spec = crm_element_value(min_interval_mon,
752 : PCMK_META_TIMEOUT);
753 0 : if (timeout_spec != NULL) {
754 0 : pcmk__rsc_trace(rsc,
755 : "Setting default timeout for %s probe to "
756 : "most frequent monitor's timeout '%s'",
757 : rsc->id, timeout_spec);
758 0 : pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
759 : }
760 : }
761 : }
762 :
763 0 : if (action_config != NULL) {
764 : // <op> <meta_attributes> take precedence over defaults
765 0 : pe__unpack_dataset_nvpairs(action_config, PCMK_XE_META_ATTRIBUTES,
766 : &rule_data, meta, NULL, TRUE, rsc->cluster);
767 :
768 : /* Anything set as an <op> XML property has highest precedence.
769 : * This ensures we use the name and interval from the <op> tag.
770 : * (See below for the only exception, fence device start/probe timeout.)
771 : */
772 0 : for (xmlAttrPtr attr = action_config->properties;
773 0 : attr != NULL; attr = attr->next) {
774 0 : pcmk__insert_dup(meta, (const char *) attr->name,
775 : pcmk__xml_attr_value(attr));
776 : }
777 : }
778 :
779 0 : g_hash_table_remove(meta, PCMK_XA_ID);
780 :
781 : // Normalize interval to milliseconds
782 0 : if (interval_ms > 0) {
783 0 : g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_INTERVAL),
784 0 : crm_strdup_printf("%u", interval_ms));
785 : } else {
786 0 : g_hash_table_remove(meta, PCMK_META_INTERVAL);
787 : }
788 :
789 : /* Timeout order of precedence (highest to lowest):
790 : * 1. pcmk_monitor_timeout resource parameter (only for starts and probes
791 : * when rsc has pcmk_ra_cap_fence_params; this gets used for recurring
792 : * monitors via the executor instead)
793 : * 2. timeout configured in <op> (with <op timeout> taking precedence over
794 : * <op> <meta_attributes>)
795 : * 3. timeout configured in <op_defaults> <meta_attributes>
796 : * 4. PCMK_DEFAULT_ACTION_TIMEOUT_MS
797 : */
798 :
799 : // Check for pcmk_monitor_timeout
800 0 : if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard),
801 : pcmk_ra_cap_fence_params)
802 0 : && (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)
803 0 : || pcmk_is_probe(action_name, interval_ms))) {
804 :
805 0 : GHashTable *params = pe_rsc_params(rsc, node, rsc->cluster);
806 :
807 0 : timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout");
808 0 : if (timeout_spec != NULL) {
809 0 : pcmk__rsc_trace(rsc,
810 : "Setting timeout for %s %s to "
811 : "pcmk_monitor_timeout (%s)",
812 : rsc->id, action_name, timeout_spec);
813 0 : pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
814 : }
815 : }
816 :
817 : // Normalize timeout to positive milliseconds
818 0 : timeout_spec = g_hash_table_lookup(meta, PCMK_META_TIMEOUT);
819 0 : g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_TIMEOUT),
820 0 : pcmk__itoa(unpack_timeout(timeout_spec)));
821 :
822 : // Ensure on-fail has a valid value
823 0 : validate_on_fail(rsc, action_name, action_config, meta);
824 :
825 : // Normalize PCMK_META_START_DELAY
826 0 : str = g_hash_table_lookup(meta, PCMK_META_START_DELAY);
827 0 : if (str != NULL) {
828 0 : unpack_start_delay(str, meta);
829 : } else {
830 0 : long long start_delay = 0;
831 :
832 0 : str = g_hash_table_lookup(meta, PCMK_META_INTERVAL_ORIGIN);
833 0 : if (unpack_interval_origin(str, action_config, interval_ms,
834 0 : rsc->cluster->now, &start_delay)) {
835 0 : g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_START_DELAY),
836 0 : crm_strdup_printf("%lld", start_delay));
837 : }
838 : }
839 0 : return meta;
840 : }
841 :
842 : /*!
843 : * \internal
844 : * \brief Determine an action's quorum and fencing dependency
845 : *
846 : * \param[in] rsc Resource that action is for
847 : * \param[in] action_name Name of action being unpacked
848 : *
849 : * \return Quorum and fencing dependency appropriate to action
850 : */
851 : enum rsc_start_requirement
852 0 : pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name)
853 : {
854 0 : const char *value = NULL;
855 0 : enum rsc_start_requirement requires = pcmk_requires_nothing;
856 :
857 0 : CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires);
858 :
859 0 : if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START,
860 : PCMK_ACTION_PROMOTE, NULL)) {
861 0 : value = "nothing (not start or promote)";
862 :
863 0 : } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
864 0 : requires = pcmk_requires_fencing;
865 0 : value = "fencing";
866 :
867 0 : } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_quorum)) {
868 0 : requires = pcmk_requires_quorum;
869 0 : value = "quorum";
870 :
871 : } else {
872 0 : value = "nothing";
873 : }
874 0 : pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value);
875 0 : return requires;
876 : }
877 :
878 : /*!
879 : * \internal
880 : * \brief Parse action failure response from a user-provided string
881 : *
882 : * \param[in] rsc Resource that action is for
883 : * \param[in] action_name Name of action
884 : * \param[in] interval_ms Action interval (in milliseconds)
885 : * \param[in] value User-provided configuration value for on-fail
886 : *
887 : * \return Action failure response parsed from \p text
888 : */
889 : enum action_fail_response
890 0 : pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name,
891 : guint interval_ms, const char *value)
892 : {
893 0 : const char *desc = NULL;
894 0 : bool needs_remote_reset = false;
895 0 : enum action_fail_response on_fail = pcmk_on_fail_ignore;
896 :
897 : // There's no enum value for unknown or invalid, so assert
898 0 : CRM_ASSERT((rsc != NULL) && (action_name != NULL));
899 :
900 0 : if (value == NULL) {
901 : // Use default
902 :
903 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
904 0 : on_fail = pcmk_on_fail_block;
905 0 : desc = "block";
906 :
907 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_FENCE, pcmk__str_casei)) {
908 0 : if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
909 0 : on_fail = pcmk_on_fail_fence_node;
910 0 : desc = "node fencing";
911 : } else {
912 0 : pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for "
913 : "%s of %s to 'stop' because 'fence' is not "
914 : "valid when fencing is disabled",
915 : action_name, rsc->id);
916 0 : on_fail = pcmk_on_fail_stop;
917 0 : desc = "stop resource";
918 : }
919 :
920 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_STANDBY, pcmk__str_casei)) {
921 0 : on_fail = pcmk_on_fail_standby_node;
922 0 : desc = "node standby";
923 :
924 0 : } else if (pcmk__strcase_any_of(value,
925 : PCMK_VALUE_IGNORE, PCMK_VALUE_NOTHING,
926 : NULL)) {
927 0 : desc = "ignore";
928 :
929 0 : } else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) {
930 0 : on_fail = pcmk_on_fail_ban;
931 0 : desc = "force migration";
932 :
933 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_STOP, pcmk__str_casei)) {
934 0 : on_fail = pcmk_on_fail_stop;
935 0 : desc = "stop resource";
936 :
937 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
938 0 : on_fail = pcmk_on_fail_restart;
939 0 : desc = "restart (and possibly migrate)";
940 :
941 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART_CONTAINER,
942 : pcmk__str_casei)) {
943 0 : if (rsc->container == NULL) {
944 0 : pcmk__rsc_debug(rsc,
945 : "Using default " PCMK_META_ON_FAIL " for %s "
946 : "of %s because it does not have a container",
947 : action_name, rsc->id);
948 : } else {
949 0 : on_fail = pcmk_on_fail_restart_container;
950 0 : desc = "restart container (and possibly migrate)";
951 : }
952 :
953 0 : } else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
954 0 : on_fail = pcmk_on_fail_demote;
955 0 : desc = "demote instance";
956 :
957 : } else {
958 0 : pcmk__config_err("Using default '" PCMK_META_ON_FAIL "' for "
959 : "%s of %s because '%s' is not valid",
960 : action_name, rsc->id, value);
961 : }
962 :
963 : /* Remote node connections are handled specially. Failures that result
964 : * in dropping an active connection must result in fencing. The only
965 : * failures that don't are probes and starts. The user can explicitly set
966 : * PCMK_META_ON_FAIL=PCMK_VALUE_FENCE to fence after start failures.
967 : */
968 0 : if (rsc->is_remote_node
969 0 : && pcmk__is_remote_node(pcmk_find_node(rsc->cluster, rsc->id))
970 0 : && !pcmk_is_probe(action_name, interval_ms)
971 0 : && !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
972 0 : needs_remote_reset = true;
973 0 : if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
974 0 : desc = NULL; // Force default for unmanaged connections
975 : }
976 : }
977 :
978 0 : if (desc != NULL) {
979 : // Explicit value used, default not needed
980 :
981 0 : } else if (rsc->container != NULL) {
982 0 : on_fail = pcmk_on_fail_restart_container;
983 0 : desc = "restart container (and possibly migrate) (default)";
984 :
985 0 : } else if (needs_remote_reset) {
986 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
987 0 : if (pcmk_is_set(rsc->cluster->flags,
988 : pcmk_sched_fencing_enabled)) {
989 0 : desc = "fence remote node (default)";
990 : } else {
991 0 : desc = "recover remote node connection (default)";
992 : }
993 0 : on_fail = pcmk_on_fail_reset_remote;
994 : } else {
995 0 : on_fail = pcmk_on_fail_stop;
996 0 : desc = "stop unmanaged remote node (enforcing default)";
997 : }
998 :
999 0 : } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
1000 0 : if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
1001 0 : on_fail = pcmk_on_fail_fence_node;
1002 0 : desc = "resource fence (default)";
1003 : } else {
1004 0 : on_fail = pcmk_on_fail_block;
1005 0 : desc = "resource block (default)";
1006 : }
1007 :
1008 : } else {
1009 0 : on_fail = pcmk_on_fail_restart;
1010 0 : desc = "restart (and possibly migrate) (default)";
1011 : }
1012 :
1013 0 : pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s",
1014 : pcmk__readable_interval(interval_ms), action_name,
1015 : rsc->id, desc);
1016 0 : return on_fail;
1017 : }
1018 :
1019 : /*!
1020 : * \internal
1021 : * \brief Determine a resource's role after failure of an action
1022 : *
1023 : * \param[in] rsc Resource that action is for
1024 : * \param[in] action_name Action name
1025 : * \param[in] on_fail Failure handling for action
1026 : * \param[in] meta Unpacked action meta-attributes
1027 : *
1028 : * \return Resource role that results from failure of action
1029 : */
1030 : enum rsc_role_e
1031 0 : pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name,
1032 : enum action_fail_response on_fail, GHashTable *meta)
1033 : {
1034 0 : const char *value = NULL;
1035 0 : enum rsc_role_e role = pcmk_role_unknown;
1036 :
1037 : // Set default for role after failure specially in certain circumstances
1038 0 : switch (on_fail) {
1039 0 : case pcmk_on_fail_stop:
1040 0 : role = pcmk_role_stopped;
1041 0 : break;
1042 :
1043 0 : case pcmk_on_fail_reset_remote:
1044 0 : if (rsc->remote_reconnect_ms != 0) {
1045 0 : role = pcmk_role_stopped;
1046 : }
1047 0 : break;
1048 :
1049 0 : default:
1050 0 : break;
1051 : }
1052 :
1053 : // @COMPAT Check for explicitly configured role (deprecated)
1054 0 : value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE);
1055 0 : if (value != NULL) {
1056 0 : pcmk__warn_once(pcmk__wo_role_after,
1057 : "Support for " PCMK__META_ROLE_AFTER_FAILURE " is "
1058 : "deprecated and will be removed in a future release");
1059 0 : if (role == pcmk_role_unknown) {
1060 0 : role = pcmk_parse_role(value);
1061 0 : if (role == pcmk_role_unknown) {
1062 0 : pcmk__config_err("Ignoring invalid value %s for "
1063 : PCMK__META_ROLE_AFTER_FAILURE,
1064 : value);
1065 : }
1066 : }
1067 : }
1068 :
1069 0 : if (role == pcmk_role_unknown) {
1070 : // Use default
1071 0 : if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
1072 0 : role = pcmk_role_unpromoted;
1073 : } else {
1074 0 : role = pcmk_role_started;
1075 : }
1076 : }
1077 0 : pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s",
1078 : rsc->id, action_name, pcmk_role_text(role));
1079 0 : return role;
1080 : }
1081 :
1082 : /*!
1083 : * \internal
1084 : * \brief Unpack action configuration
1085 : *
1086 : * Unpack a resource action's meta-attributes (normalizing the interval,
1087 : * timeout, and start delay values as integer milliseconds), requirements, and
1088 : * failure policy from its CIB XML configuration (including defaults).
1089 : *
1090 : * \param[in,out] action Resource action to unpack into
1091 : * \param[in] xml_obj Action configuration XML (NULL for defaults only)
1092 : * \param[in] interval_ms How frequently to perform the operation
1093 : */
1094 : static void
1095 0 : unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
1096 : guint interval_ms)
1097 : {
1098 0 : const char *value = NULL;
1099 :
1100 0 : action->meta = pcmk__unpack_action_meta(action->rsc, action->node,
1101 0 : action->task, interval_ms, xml_obj);
1102 0 : action->needs = pcmk__action_requires(action->rsc, action->task);
1103 :
1104 0 : value = g_hash_table_lookup(action->meta, PCMK_META_ON_FAIL);
1105 0 : action->on_fail = pcmk__parse_on_fail(action->rsc, action->task,
1106 : interval_ms, value);
1107 :
1108 0 : action->fail_role = pcmk__role_after_failure(action->rsc, action->task,
1109 : action->on_fail, action->meta);
1110 0 : }
1111 :
1112 : /*!
1113 : * \brief Create or update an action object
1114 : *
1115 : * \param[in,out] rsc Resource that action is for (if any)
1116 : * \param[in,out] key Action key (must be non-NULL)
1117 : * \param[in] task Action name (must be non-NULL)
1118 : * \param[in] on_node Node that action is on (if any)
1119 : * \param[in] optional Whether action should be considered optional
1120 : * \param[in,out] scheduler Scheduler data
1121 : *
1122 : * \return Action object corresponding to arguments (guaranteed not to be
1123 : * \c NULL)
1124 : * \note This function takes ownership of (and might free) \p key, and
1125 : * \p scheduler takes ownership of the returned action (the caller should
1126 : * not free it).
1127 : */
1128 : pcmk_action_t *
1129 0 : custom_action(pcmk_resource_t *rsc, char *key, const char *task,
1130 : const pcmk_node_t *on_node, gboolean optional,
1131 : pcmk_scheduler_t *scheduler)
1132 : {
1133 0 : pcmk_action_t *action = NULL;
1134 :
1135 0 : CRM_ASSERT((key != NULL) && (task != NULL) && (scheduler != NULL));
1136 :
1137 0 : action = find_existing_action(key, rsc, on_node, scheduler);
1138 0 : if (action == NULL) {
1139 0 : action = new_action(key, task, rsc, on_node, optional, scheduler);
1140 : } else {
1141 0 : free(key);
1142 : }
1143 :
1144 0 : update_action_optional(action, optional);
1145 :
1146 0 : if (rsc != NULL) {
1147 : /* An action can be initially created with a NULL node, and later have
1148 : * the node added via find_existing_action() (above) -> find_actions().
1149 : * That is why the extra parameters are unpacked here rather than in
1150 : * new_action().
1151 : */
1152 0 : if ((action->node != NULL) && (action->op_entry != NULL)
1153 0 : && !pcmk_is_set(action->flags, pcmk_action_attrs_evaluated)) {
1154 :
1155 0 : GHashTable *attrs = action->node->details->attrs;
1156 :
1157 0 : if (action->extra != NULL) {
1158 0 : g_hash_table_destroy(action->extra);
1159 : }
1160 0 : action->extra = pcmk__unpack_action_rsc_params(action->op_entry,
1161 : attrs, scheduler);
1162 0 : pcmk__set_action_flags(action, pcmk_action_attrs_evaluated);
1163 : }
1164 :
1165 0 : update_resource_action_runnable(action, scheduler);
1166 0 : update_resource_flags_for_action(rsc, action);
1167 : }
1168 :
1169 0 : if (action->extra == NULL) {
1170 0 : action->extra = pcmk__strkey_table(free, free);
1171 : }
1172 :
1173 0 : return action;
1174 : }
1175 :
1176 : pcmk_action_t *
1177 0 : get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
1178 : {
1179 0 : pcmk_action_t *op = lookup_singleton(scheduler, name);
1180 :
1181 0 : if (op == NULL) {
1182 0 : op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler);
1183 0 : pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
1184 : }
1185 0 : return op;
1186 : }
1187 :
1188 : static GList *
1189 0 : find_unfencing_devices(GList *candidates, GList *matches)
1190 : {
1191 0 : for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) {
1192 0 : pcmk_resource_t *candidate = gIter->data;
1193 :
1194 0 : if (candidate->children != NULL) {
1195 0 : matches = find_unfencing_devices(candidate->children, matches);
1196 :
1197 0 : } else if (!pcmk_is_set(candidate->flags, pcmk_rsc_fence_device)) {
1198 0 : continue;
1199 :
1200 0 : } else if (pcmk_is_set(candidate->flags, pcmk_rsc_needs_unfencing)) {
1201 0 : matches = g_list_prepend(matches, candidate);
1202 :
1203 0 : } else if (pcmk__str_eq(g_hash_table_lookup(candidate->meta,
1204 : PCMK_STONITH_PROVIDES),
1205 : PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
1206 0 : matches = g_list_prepend(matches, candidate);
1207 : }
1208 : }
1209 0 : return matches;
1210 : }
1211 :
1212 : static int
1213 0 : node_priority_fencing_delay(const pcmk_node_t *node,
1214 : const pcmk_scheduler_t *scheduler)
1215 : {
1216 0 : int member_count = 0;
1217 0 : int online_count = 0;
1218 0 : int top_priority = 0;
1219 0 : int lowest_priority = 0;
1220 0 : GList *gIter = NULL;
1221 :
1222 : // PCMK_OPT_PRIORITY_FENCING_DELAY is disabled
1223 0 : if (scheduler->priority_fencing_delay <= 0) {
1224 0 : return 0;
1225 : }
1226 :
1227 : /* No need to request a delay if the fencing target is not a normal cluster
1228 : * member, for example if it's a remote node or a guest node. */
1229 0 : if (node->details->type != pcmk_node_variant_cluster) {
1230 0 : return 0;
1231 : }
1232 :
1233 : // No need to request a delay if the fencing target is in our partition
1234 0 : if (node->details->online) {
1235 0 : return 0;
1236 : }
1237 :
1238 0 : for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
1239 0 : pcmk_node_t *n = gIter->data;
1240 :
1241 0 : if (n->details->type != pcmk_node_variant_cluster) {
1242 0 : continue;
1243 : }
1244 :
1245 0 : member_count ++;
1246 :
1247 0 : if (n->details->online) {
1248 0 : online_count++;
1249 : }
1250 :
1251 0 : if (member_count == 1
1252 0 : || n->details->priority > top_priority) {
1253 0 : top_priority = n->details->priority;
1254 : }
1255 :
1256 0 : if (member_count == 1
1257 0 : || n->details->priority < lowest_priority) {
1258 0 : lowest_priority = n->details->priority;
1259 : }
1260 : }
1261 :
1262 : // No need to delay if we have more than half of the cluster members
1263 0 : if (online_count > member_count / 2) {
1264 0 : return 0;
1265 : }
1266 :
1267 : /* All the nodes have equal priority.
1268 : * Any configured corresponding `pcmk_delay_base/max` will be applied. */
1269 0 : if (lowest_priority == top_priority) {
1270 0 : return 0;
1271 : }
1272 :
1273 0 : if (node->details->priority < top_priority) {
1274 0 : return 0;
1275 : }
1276 :
1277 0 : return scheduler->priority_fencing_delay;
1278 : }
1279 :
1280 : pcmk_action_t *
1281 0 : pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
1282 : const char *reason, bool priority_delay,
1283 : pcmk_scheduler_t *scheduler)
1284 : {
1285 0 : char *op_key = NULL;
1286 0 : pcmk_action_t *stonith_op = NULL;
1287 :
1288 0 : if(op == NULL) {
1289 0 : op = scheduler->stonith_action;
1290 : }
1291 :
1292 0 : op_key = crm_strdup_printf("%s-%s-%s",
1293 0 : PCMK_ACTION_STONITH, node->details->uname, op);
1294 :
1295 0 : stonith_op = lookup_singleton(scheduler, op_key);
1296 0 : if(stonith_op == NULL) {
1297 0 : stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node,
1298 : TRUE, scheduler);
1299 :
1300 0 : pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE, node->details->uname);
1301 0 : pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE_UUID,
1302 : node->details->id);
1303 0 : pcmk__insert_meta(stonith_op, PCMK__META_STONITH_ACTION, op);
1304 :
1305 0 : if (pcmk_is_set(scheduler->flags, pcmk_sched_enable_unfencing)) {
1306 : /* Extra work to detect device changes
1307 : */
1308 0 : GString *digests_all = g_string_sized_new(1024);
1309 0 : GString *digests_secure = g_string_sized_new(1024);
1310 :
1311 0 : GList *matches = find_unfencing_devices(scheduler->resources, NULL);
1312 :
1313 0 : for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) {
1314 0 : pcmk_resource_t *match = gIter->data;
1315 0 : const char *agent = g_hash_table_lookup(match->meta,
1316 : PCMK_XA_TYPE);
1317 0 : pcmk__op_digest_t *data = NULL;
1318 :
1319 0 : data = pe__compare_fencing_digest(match, agent, node,
1320 : scheduler);
1321 0 : if (data->rc == pcmk__digest_mismatch) {
1322 0 : optional = FALSE;
1323 0 : crm_notice("Unfencing node %s because the definition of "
1324 : "%s changed", pcmk__node_name(node), match->id);
1325 0 : if (!pcmk__is_daemon && scheduler->priv != NULL) {
1326 0 : pcmk__output_t *out = scheduler->priv;
1327 :
1328 0 : out->info(out,
1329 : "notice: Unfencing node %s because the "
1330 : "definition of %s changed",
1331 : pcmk__node_name(node), match->id);
1332 : }
1333 : }
1334 :
1335 0 : pcmk__g_strcat(digests_all,
1336 : match->id, ":", agent, ":",
1337 : data->digest_all_calc, ",", NULL);
1338 0 : pcmk__g_strcat(digests_secure,
1339 : match->id, ":", agent, ":",
1340 : data->digest_secure_calc, ",", NULL);
1341 : }
1342 0 : pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_ALL,
1343 0 : digests_all->str);
1344 0 : g_string_free(digests_all, TRUE);
1345 :
1346 0 : pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_SECURE,
1347 0 : digests_secure->str);
1348 0 : g_string_free(digests_secure, TRUE);
1349 : }
1350 :
1351 : } else {
1352 0 : free(op_key);
1353 : }
1354 :
1355 0 : if (scheduler->priority_fencing_delay > 0
1356 :
1357 : /* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY
1358 : * applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as
1359 : * an indicator.
1360 : */
1361 0 : && (priority_delay
1362 :
1363 : /* The priority delay needs to be recalculated if this function has
1364 : * been called by schedule_fencing_and_shutdowns() after node
1365 : * priority has already been calculated by native_add_running().
1366 : */
1367 0 : || g_hash_table_lookup(stonith_op->meta,
1368 : PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) {
1369 :
1370 : /* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if
1371 : * it's 0 for the targeting node. So that it takes precedence over
1372 : * any possible `pcmk_delay_base/max`.
1373 : */
1374 0 : char *delay_s = pcmk__itoa(node_priority_fencing_delay(node,
1375 : scheduler));
1376 :
1377 0 : g_hash_table_insert(stonith_op->meta,
1378 0 : strdup(PCMK_OPT_PRIORITY_FENCING_DELAY),
1379 : delay_s);
1380 : }
1381 :
1382 0 : if(optional == FALSE && pe_can_fence(scheduler, node)) {
1383 0 : pcmk__clear_action_flags(stonith_op, pcmk_action_optional);
1384 0 : pe_action_set_reason(stonith_op, reason, false);
1385 :
1386 0 : } else if(reason && stonith_op->reason == NULL) {
1387 0 : stonith_op->reason = strdup(reason);
1388 : }
1389 :
1390 0 : return stonith_op;
1391 : }
1392 :
1393 : void
1394 0 : pe_free_action(pcmk_action_t *action)
1395 : {
1396 0 : if (action == NULL) {
1397 0 : return;
1398 : }
1399 0 : g_list_free_full(action->actions_before, free);
1400 0 : g_list_free_full(action->actions_after, free);
1401 0 : if (action->extra) {
1402 0 : g_hash_table_destroy(action->extra);
1403 : }
1404 0 : if (action->meta) {
1405 0 : g_hash_table_destroy(action->meta);
1406 : }
1407 0 : free(action->cancel_task);
1408 0 : free(action->reason);
1409 0 : free(action->task);
1410 0 : free(action->uuid);
1411 0 : free(action->node);
1412 0 : free(action);
1413 : }
1414 :
1415 : enum action_tasks
1416 0 : get_complex_task(const pcmk_resource_t *rsc, const char *name)
1417 : {
1418 0 : enum action_tasks task = pcmk_parse_action(name);
1419 :
1420 0 : if ((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)) {
1421 0 : switch (task) {
1422 0 : case pcmk_action_stopped:
1423 : case pcmk_action_started:
1424 : case pcmk_action_demoted:
1425 : case pcmk_action_promoted:
1426 0 : crm_trace("Folding %s back into its atomic counterpart for %s",
1427 : name, rsc->id);
1428 0 : --task;
1429 0 : break;
1430 0 : default:
1431 0 : break;
1432 : }
1433 : }
1434 0 : return task;
1435 : }
1436 :
1437 : /*!
1438 : * \internal
1439 : * \brief Find first matching action in a list
1440 : *
1441 : * \param[in] input List of actions to search
1442 : * \param[in] uuid If not NULL, action must have this UUID
1443 : * \param[in] task If not NULL, action must have this action name
1444 : * \param[in] on_node If not NULL, action must be on this node
1445 : *
1446 : * \return First action in list that matches criteria, or NULL if none
1447 : */
1448 : pcmk_action_t *
1449 0 : find_first_action(const GList *input, const char *uuid, const char *task,
1450 : const pcmk_node_t *on_node)
1451 : {
1452 0 : CRM_CHECK(uuid || task, return NULL);
1453 :
1454 0 : for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) {
1455 0 : pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1456 :
1457 0 : if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) {
1458 0 : continue;
1459 :
1460 0 : } else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) {
1461 0 : continue;
1462 :
1463 0 : } else if (on_node == NULL) {
1464 0 : return action;
1465 :
1466 0 : } else if (action->node == NULL) {
1467 0 : continue;
1468 :
1469 0 : } else if (pcmk__same_node(on_node, action->node)) {
1470 0 : return action;
1471 : }
1472 : }
1473 :
1474 0 : return NULL;
1475 : }
1476 :
1477 : GList *
1478 0 : find_actions(GList *input, const char *key, const pcmk_node_t *on_node)
1479 : {
1480 0 : GList *gIter = input;
1481 0 : GList *result = NULL;
1482 :
1483 0 : CRM_CHECK(key != NULL, return NULL);
1484 :
1485 0 : for (; gIter != NULL; gIter = gIter->next) {
1486 0 : pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1487 :
1488 0 : if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) {
1489 0 : continue;
1490 :
1491 0 : } else if (on_node == NULL) {
1492 0 : crm_trace("Action %s matches (ignoring node)", key);
1493 0 : result = g_list_prepend(result, action);
1494 :
1495 0 : } else if (action->node == NULL) {
1496 0 : crm_trace("Action %s matches (unallocated, assigning to %s)",
1497 : key, pcmk__node_name(on_node));
1498 :
1499 0 : action->node = pe__copy_node(on_node);
1500 0 : result = g_list_prepend(result, action);
1501 :
1502 0 : } else if (pcmk__same_node(on_node, action->node)) {
1503 0 : crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
1504 0 : result = g_list_prepend(result, action);
1505 : }
1506 : }
1507 :
1508 0 : return result;
1509 : }
1510 :
1511 : GList *
1512 0 : find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node)
1513 : {
1514 0 : GList *result = NULL;
1515 :
1516 0 : CRM_CHECK(key != NULL, return NULL);
1517 :
1518 0 : if (on_node == NULL) {
1519 0 : return NULL;
1520 : }
1521 :
1522 0 : for (GList *gIter = input; gIter != NULL; gIter = gIter->next) {
1523 0 : pcmk_action_t *action = (pcmk_action_t *) gIter->data;
1524 :
1525 0 : if ((action->node != NULL)
1526 0 : && pcmk__str_eq(key, action->uuid, pcmk__str_casei)
1527 0 : && pcmk__str_eq(on_node->details->id, action->node->details->id,
1528 : pcmk__str_casei)) {
1529 :
1530 0 : crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
1531 0 : result = g_list_prepend(result, action);
1532 : }
1533 : }
1534 :
1535 0 : return result;
1536 : }
1537 :
1538 : /*!
1539 : * \brief Find all actions of given type for a resource
1540 : *
1541 : * \param[in] rsc Resource to search
1542 : * \param[in] node Find only actions scheduled on this node
1543 : * \param[in] task Action name to search for
1544 : * \param[in] require_node If TRUE, NULL node or action node will not match
1545 : *
1546 : * \return List of actions found (or NULL if none)
1547 : * \note If node is not NULL and require_node is FALSE, matching actions
1548 : * without a node will be assigned to node.
1549 : */
1550 : GList *
1551 0 : pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
1552 : const char *task, bool require_node)
1553 : {
1554 0 : GList *result = NULL;
1555 0 : char *key = pcmk__op_key(rsc->id, task, 0);
1556 :
1557 0 : if (require_node) {
1558 0 : result = find_actions_exact(rsc->actions, key, node);
1559 : } else {
1560 0 : result = find_actions(rsc->actions, key, node);
1561 : }
1562 0 : free(key);
1563 0 : return result;
1564 : }
1565 :
1566 : /*!
1567 : * \internal
1568 : * \brief Create an action reason string based on the action itself
1569 : *
1570 : * \param[in] action Action to create reason string for
1571 : * \param[in] flag Action flag that was cleared
1572 : *
1573 : * \return Newly allocated string suitable for use as action reason
1574 : * \note It is the caller's responsibility to free() the result.
1575 : */
1576 : char *
1577 0 : pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag)
1578 : {
1579 0 : const char *change = NULL;
1580 :
1581 0 : switch (flag) {
1582 0 : case pcmk_action_runnable:
1583 0 : change = "unrunnable";
1584 0 : break;
1585 0 : case pcmk_action_migratable:
1586 0 : change = "unmigrateable";
1587 0 : break;
1588 0 : case pcmk_action_optional:
1589 0 : change = "required";
1590 0 : break;
1591 0 : default:
1592 : // Bug: caller passed unsupported flag
1593 0 : CRM_CHECK(change != NULL, change = "");
1594 0 : break;
1595 : }
1596 0 : return crm_strdup_printf("%s%s%s %s", change,
1597 0 : (action->rsc == NULL)? "" : " ",
1598 0 : (action->rsc == NULL)? "" : action->rsc->id,
1599 0 : action->task);
1600 : }
1601 :
1602 0 : void pe_action_set_reason(pcmk_action_t *action, const char *reason,
1603 : bool overwrite)
1604 : {
1605 0 : if (action->reason != NULL && overwrite) {
1606 0 : pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'",
1607 : action->uuid, action->reason,
1608 : pcmk__s(reason, "(none)"));
1609 0 : } else if (action->reason == NULL) {
1610 0 : pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'",
1611 : action->uuid, pcmk__s(reason, "(none)"));
1612 : } else {
1613 : // crm_assert(action->reason != NULL && !overwrite);
1614 0 : return;
1615 : }
1616 :
1617 0 : pcmk__str_update(&action->reason, reason);
1618 : }
1619 :
1620 : /*!
1621 : * \internal
1622 : * \brief Create an action to clear a resource's history from CIB
1623 : *
1624 : * \param[in,out] rsc Resource to clear
1625 : * \param[in] node Node to clear history on
1626 : */
1627 : void
1628 0 : pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node)
1629 : {
1630 0 : CRM_ASSERT((rsc != NULL) && (node != NULL));
1631 :
1632 0 : custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
1633 : PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->cluster);
1634 0 : }
1635 :
1636 : #define sort_return(an_int, why) do { \
1637 : free(a_uuid); \
1638 : free(b_uuid); \
1639 : crm_trace("%s (%d) %c %s (%d) : %s", \
1640 : a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \
1641 : b_xml_id, b_call_id, why); \
1642 : return an_int; \
1643 : } while(0)
1644 :
1645 : int
1646 0 : pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
1647 : bool same_node_default)
1648 : {
1649 0 : int a_call_id = -1;
1650 0 : int b_call_id = -1;
1651 :
1652 0 : char *a_uuid = NULL;
1653 0 : char *b_uuid = NULL;
1654 :
1655 0 : const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID);
1656 0 : const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID);
1657 :
1658 0 : const char *a_node = crm_element_value(xml_a, PCMK__META_ON_NODE);
1659 0 : const char *b_node = crm_element_value(xml_b, PCMK__META_ON_NODE);
1660 0 : bool same_node = true;
1661 :
1662 : /* @COMPAT The on_node attribute was added to last_failure as of 1.1.13 (via
1663 : * 8b3ca1c) and the other entries as of 1.1.12 (via 0b07b5c).
1664 : *
1665 : * In case that any of the PCMK__XE_LRM_RSC_OP entries doesn't have on_node
1666 : * attribute, we need to explicitly tell whether the two operations are on
1667 : * the same node.
1668 : */
1669 0 : if (a_node == NULL || b_node == NULL) {
1670 0 : same_node = same_node_default;
1671 :
1672 : } else {
1673 0 : same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei);
1674 : }
1675 :
1676 0 : if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) {
1677 : /* We have duplicate PCMK__XE_LRM_RSC_OP entries in the status
1678 : * section which is unlikely to be a good thing
1679 : * - we can handle it easily enough, but we need to get
1680 : * to the bottom of why it's happening.
1681 : */
1682 0 : pcmk__config_err("Duplicate " PCMK__XE_LRM_RSC_OP " entries named %s",
1683 : a_xml_id);
1684 0 : sort_return(0, "duplicate");
1685 : }
1686 :
1687 0 : crm_element_value_int(xml_a, PCMK__XA_CALL_ID, &a_call_id);
1688 0 : crm_element_value_int(xml_b, PCMK__XA_CALL_ID, &b_call_id);
1689 :
1690 0 : if (a_call_id == -1 && b_call_id == -1) {
1691 : /* both are pending ops so it doesn't matter since
1692 : * stops are never pending
1693 : */
1694 0 : sort_return(0, "pending");
1695 :
1696 0 : } else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) {
1697 0 : sort_return(-1, "call id");
1698 :
1699 0 : } else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) {
1700 0 : sort_return(1, "call id");
1701 :
1702 0 : } else if (a_call_id >= 0 && b_call_id >= 0
1703 0 : && (!same_node || a_call_id == b_call_id)) {
1704 : /* The op and last_failed_op are the same. Order on
1705 : * PCMK_XA_LAST_RC_CHANGE.
1706 : */
1707 0 : time_t last_a = -1;
1708 0 : time_t last_b = -1;
1709 :
1710 0 : crm_element_value_epoch(xml_a, PCMK_XA_LAST_RC_CHANGE, &last_a);
1711 0 : crm_element_value_epoch(xml_b, PCMK_XA_LAST_RC_CHANGE, &last_b);
1712 :
1713 0 : crm_trace("rc-change: %lld vs %lld",
1714 : (long long) last_a, (long long) last_b);
1715 0 : if (last_a >= 0 && last_a < last_b) {
1716 0 : sort_return(-1, "rc-change");
1717 :
1718 0 : } else if (last_b >= 0 && last_a > last_b) {
1719 0 : sort_return(1, "rc-change");
1720 : }
1721 0 : sort_return(0, "rc-change");
1722 :
1723 : } else {
1724 : /* One of the inputs is a pending operation.
1725 : * Attempt to use PCMK__XA_TRANSITION_MAGIC to determine its age relative
1726 : * to the other.
1727 : */
1728 :
1729 0 : int a_id = -1;
1730 0 : int b_id = -1;
1731 :
1732 0 : const char *a_magic = crm_element_value(xml_a,
1733 : PCMK__XA_TRANSITION_MAGIC);
1734 0 : const char *b_magic = crm_element_value(xml_b,
1735 : PCMK__XA_TRANSITION_MAGIC);
1736 :
1737 0 : CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
1738 0 : if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL,
1739 : NULL)) {
1740 0 : sort_return(0, "bad magic a");
1741 : }
1742 0 : if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL,
1743 : NULL)) {
1744 0 : sort_return(0, "bad magic b");
1745 : }
1746 : /* try to determine the relative age of the operation...
1747 : * some pending operations (e.g. a start) may have been superseded
1748 : * by a subsequent stop
1749 : *
1750 : * [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
1751 : */
1752 0 : if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) {
1753 : /*
1754 : * some of the logic in here may be redundant...
1755 : *
1756 : * if the UUID from the TE doesn't match then one better
1757 : * be a pending operation.
1758 : * pending operations don't survive between elections and joins
1759 : * because we query the LRM directly
1760 : */
1761 :
1762 0 : if (b_call_id == -1) {
1763 0 : sort_return(-1, "transition + call");
1764 :
1765 0 : } else if (a_call_id == -1) {
1766 0 : sort_return(1, "transition + call");
1767 : }
1768 :
1769 0 : } else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
1770 0 : sort_return(-1, "transition");
1771 :
1772 0 : } else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
1773 0 : sort_return(1, "transition");
1774 : }
1775 : }
1776 :
1777 : /* we should never end up here */
1778 0 : CRM_CHECK(FALSE, sort_return(0, "default"));
1779 : }
1780 :
1781 : gint
1782 0 : sort_op_by_callid(gconstpointer a, gconstpointer b)
1783 : {
1784 0 : const xmlNode *xml_a = a;
1785 0 : const xmlNode *xml_b = b;
1786 :
1787 0 : return pe__is_newer_op(xml_a, xml_b, true);
1788 : }
1789 :
1790 : /*!
1791 : * \internal
1792 : * \brief Create a new pseudo-action for a resource
1793 : *
1794 : * \param[in,out] rsc Resource to create action for
1795 : * \param[in] task Action name
1796 : * \param[in] optional Whether action should be considered optional
1797 : * \param[in] runnable Whethe action should be considered runnable
1798 : *
1799 : * \return New action object corresponding to arguments
1800 : */
1801 : pcmk_action_t *
1802 0 : pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional,
1803 : bool runnable)
1804 : {
1805 0 : pcmk_action_t *action = NULL;
1806 :
1807 0 : CRM_ASSERT((rsc != NULL) && (task != NULL));
1808 :
1809 0 : action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL,
1810 : optional, rsc->cluster);
1811 0 : pcmk__set_action_flags(action, pcmk_action_pseudo);
1812 0 : if (runnable) {
1813 0 : pcmk__set_action_flags(action, pcmk_action_runnable);
1814 : }
1815 0 : return action;
1816 : }
1817 :
1818 : /*!
1819 : * \internal
1820 : * \brief Add the expected result to an action
1821 : *
1822 : * \param[in,out] action Action to add expected result to
1823 : * \param[in] expected_result Expected result to add
1824 : *
1825 : * \note This is more efficient than calling pcmk__insert_meta().
1826 : */
1827 : void
1828 0 : pe__add_action_expected_result(pcmk_action_t *action, int expected_result)
1829 : {
1830 0 : CRM_ASSERT((action != NULL) && (action->meta != NULL));
1831 :
1832 0 : g_hash_table_insert(action->meta, pcmk__str_copy(PCMK__META_OP_TARGET_RC),
1833 0 : pcmk__itoa(expected_result));
1834 0 : }
|