Line data Source code
1 : /*
2 : * Copyright 2004-2024 the Pacemaker project contributors
3 : *
4 : * The version control history for this file may have further details.
5 : *
6 : * This source code is licensed under the GNU Lesser General Public License
7 : * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <sys/param.h>
13 : #include <sys/stat.h>
14 :
15 : #include <crm/crm.h>
16 : #include <crm/common/xml.h>
17 : #include <crm/common/xml_internal.h>
18 : #include <crm/lrmd_internal.h>
19 : #include <pacemaker-internal.h>
20 :
21 :
22 : /*
23 : * Functions for freeing transition graph objects
24 : */
25 :
26 : /*!
27 : * \internal
28 : * \brief Free a transition graph action object
29 : *
30 : * \param[in,out] user_data Action to free
31 : */
32 : static void
33 0 : free_graph_action(gpointer user_data)
34 : {
35 0 : pcmk__graph_action_t *action = user_data;
36 :
37 0 : if (action->timer != 0) {
38 0 : crm_warn("Cancelling timer for graph action %d", action->id);
39 0 : g_source_remove(action->timer);
40 : }
41 0 : if (action->params != NULL) {
42 0 : g_hash_table_destroy(action->params);
43 : }
44 0 : free_xml(action->xml);
45 0 : free(action);
46 0 : }
47 :
48 : /*!
49 : * \internal
50 : * \brief Free a transition graph synapse object
51 : *
52 : * \param[in,out] user_data Synapse to free
53 : */
54 : static void
55 0 : free_graph_synapse(gpointer user_data)
56 : {
57 0 : pcmk__graph_synapse_t *synapse = user_data;
58 :
59 0 : g_list_free_full(synapse->actions, free_graph_action);
60 0 : g_list_free_full(synapse->inputs, free_graph_action);
61 0 : free(synapse);
62 0 : }
63 :
64 : /*!
65 : * \internal
66 : * \brief Free a transition graph object
67 : *
68 : * \param[in,out] graph Transition graph to free
69 : */
70 : void
71 0 : pcmk__free_graph(pcmk__graph_t *graph)
72 : {
73 0 : if (graph != NULL) {
74 0 : g_list_free_full(graph->synapses, free_graph_synapse);
75 0 : free(graph->source);
76 0 : free(graph->failed_stop_offset);
77 0 : free(graph->failed_start_offset);
78 0 : free(graph);
79 : }
80 0 : }
81 :
82 :
83 : /*
84 : * Functions for updating graph
85 : */
86 :
87 : /*!
88 : * \internal
89 : * \brief Update synapse after completed prerequisite
90 : *
91 : * A synapse is ready to be executed once all its prerequisite actions (inputs)
92 : * complete. Given a completed action, check whether it is an input for a given
93 : * synapse, and if so, mark the input as confirmed, and mark the synapse as
94 : * ready if appropriate.
95 : *
96 : * \param[in,out] synapse Transition graph synapse to update
97 : * \param[in] action_id ID of an action that completed
98 : *
99 : * \note The only substantial effect here is confirming synapse inputs.
100 : * should_fire_synapse() will recalculate pcmk__synapse_ready, so the only
101 : * thing that uses the pcmk__synapse_ready from here is
102 : * synapse_state_str().
103 : */
104 : static void
105 0 : update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id)
106 : {
107 0 : if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
108 0 : return; // All inputs have already been confirmed
109 : }
110 :
111 : // Presume ready until proven otherwise
112 0 : pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
113 :
114 0 : for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
115 0 : pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
116 :
117 0 : if (prereq->id == action_id) {
118 0 : crm_trace("Confirming input %d of synapse %d",
119 : action_id, synapse->id);
120 0 : pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed);
121 :
122 0 : } else if (!pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed)) {
123 0 : pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
124 0 : crm_trace("Synapse %d still not ready after action %d",
125 : synapse->id, action_id);
126 : }
127 : }
128 0 : if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
129 0 : crm_trace("Synapse %d is now ready to execute", synapse->id);
130 : }
131 : }
132 :
133 : /*!
134 : * \internal
135 : * \brief Update action and synapse confirmation after action completion
136 : *
137 : * \param[in,out] synapse Transition graph synapse that action belongs to
138 : * \param[in] action_id ID of action that completed
139 : */
140 : static void
141 0 : update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id)
142 : {
143 0 : bool all_confirmed = true;
144 :
145 0 : for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
146 0 : pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
147 :
148 0 : if (action->id == action_id) {
149 0 : crm_trace("Confirmed action %d of synapse %d",
150 : action_id, synapse->id);
151 0 : pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
152 :
153 0 : } else if (all_confirmed &&
154 0 : !pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
155 0 : all_confirmed = false;
156 0 : crm_trace("Synapse %d still not confirmed after action %d",
157 : synapse->id, action_id);
158 : }
159 : }
160 :
161 0 : if (all_confirmed
162 0 : && !pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
163 0 : crm_trace("Confirmed synapse %d", synapse->id);
164 0 : pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
165 : }
166 0 : }
167 :
168 : /*!
169 : * \internal
170 : * \brief Update the transition graph with a completed action result
171 : *
172 : * \param[in,out] graph Transition graph to update
173 : * \param[in] action Action that completed
174 : */
175 : void
176 0 : pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action)
177 : {
178 0 : for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
179 0 : pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
180 :
181 0 : if (pcmk_any_flags_set(synapse->flags,
182 : pcmk__synapse_confirmed|pcmk__synapse_failed)) {
183 0 : continue; // This synapse already completed
184 :
185 0 : } else if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
186 0 : update_synapse_confirmed(synapse, action->id);
187 :
188 0 : } else if (!pcmk_is_set(action->flags, pcmk__graph_action_failed)
189 0 : || (synapse->priority == PCMK_SCORE_INFINITY)) {
190 0 : update_synapse_ready(synapse, action->id);
191 : }
192 : }
193 0 : }
194 :
195 :
196 : /*
197 : * Functions for executing graph
198 : */
199 :
200 : /* A transition graph consists of various types of actions. The library caller
201 : * registers execution functions for each action type, which will be stored
202 : * here.
203 : */
204 : static pcmk__graph_functions_t *graph_fns = NULL;
205 :
206 : /*!
207 : * \internal
208 : * \brief Set transition graph execution functions
209 : *
210 : * \param[in] Execution functions to use
211 : */
212 : void
213 0 : pcmk__set_graph_functions(pcmk__graph_functions_t *fns)
214 : {
215 0 : crm_debug("Setting custom functions for executing transition graphs");
216 0 : graph_fns = fns;
217 :
218 0 : CRM_ASSERT(graph_fns != NULL);
219 0 : CRM_ASSERT(graph_fns->rsc != NULL);
220 0 : CRM_ASSERT(graph_fns->cluster != NULL);
221 0 : CRM_ASSERT(graph_fns->pseudo != NULL);
222 0 : CRM_ASSERT(graph_fns->fence != NULL);
223 0 : }
224 :
225 : /*!
226 : * \internal
227 : * \brief Check whether a graph synapse is ready to be executed
228 : *
229 : * \param[in,out] graph Transition graph that synapse is part of
230 : * \param[in,out] synapse Synapse to check
231 : *
232 : * \return true if synapse is ready, false otherwise
233 : */
234 : static bool
235 0 : should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
236 : {
237 0 : GList *lpc = NULL;
238 :
239 0 : pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
240 0 : for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
241 0 : pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
242 :
243 0 : if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) {
244 0 : crm_trace("Input %d for synapse %d not yet confirmed",
245 : prereq->id, synapse->id);
246 0 : pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
247 0 : break;
248 :
249 0 : } else if (pcmk_is_set(prereq->flags, pcmk__graph_action_failed)
250 0 : && !pcmk_is_set(prereq->flags,
251 : pcmk__graph_action_can_fail)) {
252 0 : crm_trace("Input %d for synapse %d confirmed but failed",
253 : prereq->id, synapse->id);
254 0 : pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
255 0 : break;
256 : }
257 : }
258 0 : if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
259 0 : crm_trace("Synapse %d is ready to execute", synapse->id);
260 : } else {
261 0 : return false;
262 : }
263 :
264 0 : for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
265 0 : pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data;
266 :
267 0 : if (a->type == pcmk__pseudo_graph_action) {
268 : /* None of the below applies to pseudo ops */
269 :
270 0 : } else if (synapse->priority < graph->abort_priority) {
271 0 : crm_trace("Skipping synapse %d: priority %d is less than "
272 : "abort priority %d",
273 : synapse->id, synapse->priority, graph->abort_priority);
274 0 : graph->skipped++;
275 0 : return false;
276 :
277 0 : } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) {
278 0 : crm_trace("Deferring synapse %d: not allowed", synapse->id);
279 0 : return false;
280 : }
281 : }
282 :
283 0 : return true;
284 : }
285 :
286 : /*!
287 : * \internal
288 : * \brief Initiate an action from a transition graph
289 : *
290 : * \param[in,out] graph Transition graph containing action
291 : * \param[in,out] action Action to execute
292 : *
293 : * \return Standard Pacemaker return code
294 : */
295 : static int
296 0 : initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
297 : {
298 0 : const char *id = pcmk__xe_id(action->xml);
299 :
300 0 : CRM_CHECK(id != NULL, return EINVAL);
301 0 : CRM_CHECK(!pcmk_is_set(action->flags, pcmk__graph_action_executed),
302 : return pcmk_rc_already);
303 :
304 0 : pcmk__set_graph_action_flags(action, pcmk__graph_action_executed);
305 0 : switch (action->type) {
306 0 : case pcmk__pseudo_graph_action:
307 0 : crm_trace("Executing pseudo-action %d (%s)", action->id, id);
308 0 : return graph_fns->pseudo(graph, action);
309 :
310 0 : case pcmk__rsc_graph_action:
311 0 : crm_trace("Executing resource action %d (%s)", action->id, id);
312 0 : return graph_fns->rsc(graph, action);
313 :
314 0 : case pcmk__cluster_graph_action:
315 0 : if (pcmk__str_eq(crm_element_value(action->xml, PCMK_XA_OPERATION),
316 : PCMK_ACTION_STONITH, pcmk__str_none)) {
317 0 : crm_trace("Executing fencing action %d (%s)",
318 : action->id, id);
319 0 : return graph_fns->fence(graph, action);
320 : }
321 0 : crm_trace("Executing cluster action %d (%s)", action->id, id);
322 0 : return graph_fns->cluster(graph, action);
323 :
324 0 : default:
325 0 : crm_err("Unsupported graph action type <%s " PCMK_XA_ID "='%s'> "
326 : "(bug?)",
327 : action->xml->name, id);
328 0 : return EINVAL;
329 : }
330 : }
331 :
332 : /*!
333 : * \internal
334 : * \brief Execute a graph synapse
335 : *
336 : * \param[in,out] graph Transition graph with synapse to execute
337 : * \param[in,out] synapse Synapse to execute
338 : *
339 : * \return Standard Pacemaker return value
340 : */
341 : static int
342 0 : fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
343 : {
344 0 : pcmk__set_synapse_flags(synapse, pcmk__synapse_executed);
345 0 : for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
346 0 : pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
347 0 : int rc = initiate_action(graph, action);
348 :
349 0 : if (rc != pcmk_rc_ok) {
350 0 : crm_err("Failed initiating <%s " PCMK_XA_ID "=%d> in synapse %d: "
351 : "%s",
352 : action->xml->name, action->id, synapse->id,
353 : pcmk_rc_str(rc));
354 0 : pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
355 0 : pcmk__set_graph_action_flags(action,
356 : pcmk__graph_action_confirmed
357 : |pcmk__graph_action_failed);
358 0 : return pcmk_rc_error;
359 : }
360 : }
361 0 : return pcmk_rc_ok;
362 : }
363 :
364 : /*!
365 : * \internal
366 : * \brief Dummy graph method that can be used with simulations
367 : *
368 : * \param[in,out] graph Transition graph containing action
369 : * \param[in,out] action Graph action to be initiated
370 : *
371 : * \return Standard Pacemaker return code
372 : * \note If the PE_fail environment variable is set to the action ID,
373 : * then the graph action will be marked as failed.
374 : */
375 : static int
376 0 : pseudo_action_dummy(pcmk__graph_t *graph, pcmk__graph_action_t *action)
377 : {
378 : static int fail = -1;
379 :
380 0 : if (fail < 0) {
381 : long long fail_ll;
382 :
383 0 : if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok)
384 0 : && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) {
385 0 : fail = (int) fail_ll;
386 : } else {
387 0 : fail = 0;
388 : }
389 : }
390 :
391 0 : if (action->id == fail) {
392 0 : crm_err("Dummy event handler: pretending action %d failed", action->id);
393 0 : pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
394 0 : graph->abort_priority = PCMK_SCORE_INFINITY;
395 : } else {
396 0 : crm_trace("Dummy event handler: action %d initiated", action->id);
397 : }
398 0 : pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
399 0 : pcmk__update_graph(graph, action);
400 0 : return pcmk_rc_ok;
401 : }
402 :
403 : static pcmk__graph_functions_t default_fns = {
404 : pseudo_action_dummy,
405 : pseudo_action_dummy,
406 : pseudo_action_dummy,
407 : pseudo_action_dummy
408 : };
409 :
410 : /*!
411 : * \internal
412 : * \brief Execute all actions in a transition graph
413 : *
414 : * \param[in,out] graph Transition graph to execute
415 : *
416 : * \return Status of transition after execution
417 : */
418 : enum pcmk__graph_status
419 0 : pcmk__execute_graph(pcmk__graph_t *graph)
420 : {
421 0 : GList *lpc = NULL;
422 0 : int log_level = LOG_DEBUG;
423 0 : enum pcmk__graph_status pass_result = pcmk__graph_active;
424 0 : const char *status = "In progress";
425 :
426 0 : if (graph_fns == NULL) {
427 0 : graph_fns = &default_fns;
428 : }
429 0 : if (graph == NULL) {
430 0 : return pcmk__graph_complete;
431 : }
432 :
433 0 : graph->fired = 0;
434 0 : graph->pending = 0;
435 0 : graph->skipped = 0;
436 0 : graph->completed = 0;
437 0 : graph->incomplete = 0;
438 :
439 : // Count completed and in-flight synapses
440 0 : for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
441 0 : pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
442 :
443 0 : if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
444 0 : graph->completed++;
445 :
446 0 : } else if (!pcmk_is_set(synapse->flags, pcmk__synapse_failed)
447 0 : && pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
448 0 : graph->pending++;
449 : }
450 : }
451 0 : crm_trace("Executing graph %d (%d synapses already completed, %d pending)",
452 : graph->id, graph->completed, graph->pending);
453 :
454 : // Execute any synapses that are ready
455 0 : for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
456 0 : pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
457 :
458 0 : if ((graph->batch_limit > 0)
459 0 : && (graph->pending >= graph->batch_limit)) {
460 :
461 0 : crm_debug("Throttling graph execution: batch limit (%d) reached",
462 : graph->batch_limit);
463 0 : break;
464 :
465 0 : } else if (pcmk_is_set(synapse->flags, pcmk__synapse_failed)) {
466 0 : graph->skipped++;
467 0 : continue;
468 :
469 0 : } else if (pcmk_any_flags_set(synapse->flags,
470 : pcmk__synapse_confirmed
471 : |pcmk__synapse_executed)) {
472 0 : continue; // Already handled
473 :
474 0 : } else if (should_fire_synapse(graph, synapse)) {
475 0 : graph->fired++;
476 0 : if (fire_synapse(graph, synapse) != pcmk_rc_ok) {
477 0 : crm_err("Synapse %d failed to fire", synapse->id);
478 0 : log_level = LOG_ERR;
479 0 : graph->abort_priority = PCMK_SCORE_INFINITY;
480 0 : graph->incomplete++;
481 0 : graph->fired--;
482 : }
483 :
484 0 : if (!(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) {
485 0 : graph->pending++;
486 : }
487 :
488 : } else {
489 0 : crm_trace("Synapse %d cannot fire", synapse->id);
490 0 : graph->incomplete++;
491 : }
492 : }
493 :
494 0 : if ((graph->pending == 0) && (graph->fired == 0)) {
495 0 : graph->complete = true;
496 :
497 0 : if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) {
498 0 : log_level = LOG_WARNING;
499 0 : pass_result = pcmk__graph_terminated;
500 0 : status = "Terminated";
501 :
502 0 : } else if (graph->skipped != 0) {
503 0 : log_level = LOG_NOTICE;
504 0 : pass_result = pcmk__graph_complete;
505 0 : status = "Stopped";
506 :
507 : } else {
508 0 : log_level = LOG_NOTICE;
509 0 : pass_result = pcmk__graph_complete;
510 0 : status = "Complete";
511 : }
512 :
513 0 : } else if (graph->fired == 0) {
514 0 : pass_result = pcmk__graph_pending;
515 : }
516 :
517 0 : do_crm_log(log_level,
518 : "Transition %d (Complete=%d, Pending=%d,"
519 : " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s",
520 : graph->id, graph->completed, graph->pending, graph->fired,
521 : graph->skipped, graph->incomplete, graph->source, status);
522 :
523 0 : return pass_result;
524 : }
525 :
526 :
527 : /*
528 : * Functions for unpacking transition graph XML into structs
529 : */
530 :
531 : /*!
532 : * \internal
533 : * \brief Unpack a transition graph action from XML
534 : *
535 : * \param[in] parent Synapse that action is part of
536 : * \param[in] xml_action Action XML to unparse
537 : *
538 : * \return Newly allocated action on success, or NULL otherwise
539 : */
540 : static pcmk__graph_action_t *
541 0 : unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action)
542 : {
543 : enum pcmk__graph_action_type action_type;
544 0 : pcmk__graph_action_t *action = NULL;
545 0 : const char *value = pcmk__xe_id(xml_action);
546 :
547 0 : if (value == NULL) {
548 0 : crm_err("Ignoring transition graph action without " PCMK_XA_ID
549 : " (bug?)");
550 0 : crm_log_xml_trace(xml_action, "invalid");
551 0 : return NULL;
552 : }
553 :
554 0 : if (pcmk__xe_is(xml_action, PCMK__XE_RSC_OP)) {
555 0 : action_type = pcmk__rsc_graph_action;
556 :
557 0 : } else if (pcmk__xe_is(xml_action, PCMK__XE_PSEUDO_EVENT)) {
558 0 : action_type = pcmk__pseudo_graph_action;
559 :
560 0 : } else if (pcmk__xe_is(xml_action, PCMK__XE_CRM_EVENT)) {
561 0 : action_type = pcmk__cluster_graph_action;
562 :
563 : } else {
564 0 : crm_err("Ignoring transition graph action of unknown type '%s' (bug?)",
565 : xml_action->name);
566 0 : crm_log_xml_trace(xml_action, "invalid");
567 0 : return NULL;
568 : }
569 :
570 0 : action = calloc(1, sizeof(pcmk__graph_action_t));
571 0 : if (action == NULL) {
572 0 : crm_perror(LOG_CRIT, "Cannot unpack transition graph action");
573 0 : crm_log_xml_trace(xml_action, "lost");
574 0 : return NULL;
575 : }
576 :
577 0 : pcmk__scan_min_int(value, &(action->id), -1);
578 0 : action->type = pcmk__rsc_graph_action;
579 0 : action->xml = pcmk__xml_copy(NULL, xml_action);
580 0 : action->synapse = parent;
581 0 : action->type = action_type;
582 0 : action->params = xml2list(action->xml);
583 :
584 0 : value = crm_meta_value(action->params, PCMK_META_TIMEOUT);
585 0 : pcmk__scan_min_int(value, &(action->timeout), 0);
586 :
587 : /* Take PCMK_META_START_DELAY into account for the timeout of the action
588 : * timer
589 : */
590 0 : value = crm_meta_value(action->params, PCMK_META_START_DELAY);
591 : {
592 : int start_delay;
593 :
594 0 : pcmk__scan_min_int(value, &start_delay, 0);
595 0 : action->timeout += start_delay;
596 : }
597 :
598 0 : if (pcmk__guint_from_hash(action->params, CRM_META "_" PCMK_META_INTERVAL,
599 : 0, &(action->interval_ms)) != pcmk_rc_ok) {
600 0 : action->interval_ms = 0;
601 : }
602 :
603 0 : value = crm_meta_value(action->params, PCMK__META_CAN_FAIL);
604 0 : if (value != NULL) {
605 0 : int can_fail = 0;
606 :
607 0 : if ((crm_str_to_boolean(value, &can_fail) > 0) && (can_fail > 0)) {
608 0 : pcmk__set_graph_action_flags(action, pcmk__graph_action_can_fail);
609 : } else {
610 0 : pcmk__clear_graph_action_flags(action, pcmk__graph_action_can_fail);
611 : }
612 :
613 0 : if (pcmk_is_set(action->flags, pcmk__graph_action_can_fail)) {
614 0 : crm_warn("Support for the " PCMK__META_CAN_FAIL " meta-attribute "
615 : "is deprecated and will be removed in a future release");
616 : }
617 : }
618 :
619 0 : crm_trace("Action %d has timer set to %dms", action->id, action->timeout);
620 :
621 0 : return action;
622 : }
623 :
624 : /*!
625 : * \internal
626 : * \brief Unpack transition graph synapse from XML
627 : *
628 : * \param[in,out] new_graph Transition graph that synapse is part of
629 : * \param[in] xml_synapse Synapse XML
630 : *
631 : * \return Newly allocated synapse on success, or NULL otherwise
632 : */
633 : static pcmk__graph_synapse_t *
634 0 : unpack_synapse(pcmk__graph_t *new_graph, const xmlNode *xml_synapse)
635 : {
636 0 : const char *value = NULL;
637 0 : xmlNode *action_set = NULL;
638 0 : pcmk__graph_synapse_t *new_synapse = NULL;
639 :
640 0 : crm_trace("Unpacking synapse %s", pcmk__xe_id(xml_synapse));
641 :
642 0 : new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t));
643 0 : if (new_synapse == NULL) {
644 0 : return NULL;
645 : }
646 :
647 0 : pcmk__scan_min_int(pcmk__xe_id(xml_synapse), &(new_synapse->id), 0);
648 :
649 0 : value = crm_element_value(xml_synapse, PCMK__XA_PRIORITY);
650 0 : pcmk__scan_min_int(value, &(new_synapse->priority), 0);
651 :
652 0 : CRM_CHECK(new_synapse->id >= 0,
653 : free_graph_synapse((gpointer) new_synapse); return NULL);
654 :
655 0 : new_graph->num_synapses++;
656 :
657 0 : crm_trace("Unpacking synapse %s action sets",
658 : crm_element_value(xml_synapse, PCMK_XA_ID));
659 :
660 0 : for (action_set = pcmk__xe_first_child(xml_synapse, "action_set", NULL,
661 : NULL);
662 0 : action_set != NULL; action_set = pcmk__xe_next_same(action_set)) {
663 :
664 0 : for (xmlNode *action = pcmk__xe_first_child(action_set, NULL, NULL,
665 : NULL);
666 0 : action != NULL; action = pcmk__xe_next(action)) {
667 :
668 0 : pcmk__graph_action_t *new_action = unpack_action(new_synapse,
669 : action);
670 :
671 0 : if (new_action == NULL) {
672 0 : continue;
673 : }
674 :
675 0 : crm_trace("Adding action %d to synapse %d",
676 : new_action->id, new_synapse->id);
677 0 : new_graph->num_actions++;
678 0 : new_synapse->actions = g_list_append(new_synapse->actions,
679 : new_action);
680 : }
681 : }
682 :
683 0 : crm_trace("Unpacking synapse %s inputs", pcmk__xe_id(xml_synapse));
684 :
685 0 : for (xmlNode *inputs = pcmk__xe_first_child(xml_synapse, "inputs", NULL,
686 : NULL);
687 0 : inputs != NULL; inputs = pcmk__xe_next_same(inputs)) {
688 :
689 0 : for (xmlNode *trigger = pcmk__xe_first_child(inputs, "trigger", NULL,
690 : NULL);
691 0 : trigger != NULL; trigger = pcmk__xe_next_same(trigger)) {
692 :
693 0 : for (xmlNode *input = pcmk__xe_first_child(trigger, NULL, NULL,
694 : NULL);
695 0 : input != NULL; input = pcmk__xe_next(input)) {
696 :
697 0 : pcmk__graph_action_t *new_input = unpack_action(new_synapse,
698 : input);
699 :
700 0 : if (new_input == NULL) {
701 0 : continue;
702 : }
703 :
704 0 : crm_trace("Adding input %d to synapse %d",
705 : new_input->id, new_synapse->id);
706 :
707 0 : new_synapse->inputs = g_list_append(new_synapse->inputs,
708 : new_input);
709 : }
710 : }
711 : }
712 :
713 0 : return new_synapse;
714 : }
715 :
716 : /*!
717 : * \internal
718 : * \brief Unpack transition graph XML
719 : *
720 : * \param[in] xml_graph Transition graph XML to unpack
721 : * \param[in] reference Where the XML came from (for logging)
722 : *
723 : * \return Newly allocated transition graph on success, NULL otherwise
724 : * \note The caller is responsible for freeing the return value using
725 : * pcmk__free_graph().
726 : * \note The XML is expected to be structured like:
727 : <transition_graph ...>
728 : <synapse id="0">
729 : <action_set>
730 : <rsc_op id="2" ...>
731 : ...
732 : </action_set>
733 : <inputs>
734 : <rsc_op id="1" ...
735 : ...
736 : </inputs>
737 : </synapse>
738 : ...
739 : </transition_graph>
740 : */
741 : pcmk__graph_t *
742 0 : pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference)
743 : {
744 0 : pcmk__graph_t *new_graph = NULL;
745 :
746 0 : new_graph = calloc(1, sizeof(pcmk__graph_t));
747 0 : if (new_graph == NULL) {
748 0 : return NULL;
749 : }
750 :
751 0 : new_graph->source = strdup(pcmk__s(reference, "unknown"));
752 0 : if (new_graph->source == NULL) {
753 0 : pcmk__free_graph(new_graph);
754 0 : return NULL;
755 : }
756 :
757 0 : new_graph->id = -1;
758 0 : new_graph->abort_priority = 0;
759 0 : new_graph->network_delay = 0;
760 0 : new_graph->stonith_timeout = 0;
761 0 : new_graph->completion_action = pcmk__graph_done;
762 :
763 : // Parse top-level attributes from PCMK__XE_TRANSITION_GRAPH
764 0 : if (xml_graph != NULL) {
765 0 : const char *buf = crm_element_value(xml_graph, "transition_id");
766 :
767 0 : CRM_CHECK(buf != NULL,
768 : pcmk__free_graph(new_graph); return NULL);
769 0 : pcmk__scan_min_int(buf, &(new_graph->id), -1);
770 :
771 0 : buf = crm_element_value(xml_graph, PCMK_OPT_CLUSTER_DELAY);
772 0 : CRM_CHECK(buf != NULL,
773 : pcmk__free_graph(new_graph); return NULL);
774 0 : pcmk_parse_interval_spec(buf, &(new_graph->network_delay));
775 :
776 0 : buf = crm_element_value(xml_graph, PCMK_OPT_STONITH_TIMEOUT);
777 0 : if (buf == NULL) {
778 0 : new_graph->stonith_timeout = new_graph->network_delay;
779 : } else {
780 0 : pcmk_parse_interval_spec(buf, &(new_graph->stonith_timeout));
781 : }
782 :
783 : // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum
784 0 : buf = crm_element_value(xml_graph, PCMK_OPT_BATCH_LIMIT);
785 0 : if ((buf == NULL)
786 0 : || (pcmk__scan_min_int(buf, &(new_graph->batch_limit),
787 : -1) != pcmk_rc_ok)) {
788 0 : new_graph->batch_limit = 0;
789 : }
790 :
791 0 : buf = crm_element_value(xml_graph, PCMK_OPT_MIGRATION_LIMIT);
792 0 : pcmk__scan_min_int(buf, &(new_graph->migration_limit), -1);
793 :
794 0 : new_graph->failed_stop_offset =
795 0 : crm_element_value_copy(xml_graph, "failed-stop-offset");
796 0 : new_graph->failed_start_offset =
797 0 : crm_element_value_copy(xml_graph, "failed-start-offset");
798 :
799 0 : if (crm_element_value_epoch(xml_graph, "recheck-by",
800 : &(new_graph->recheck_by)) != pcmk_ok) {
801 0 : new_graph->recheck_by = 0;
802 : }
803 : }
804 :
805 : // Unpack each child <synapse> element
806 0 : for (const xmlNode *synapse_xml = pcmk__xe_first_child(xml_graph,
807 : "synapse", NULL,
808 : NULL);
809 0 : synapse_xml != NULL; synapse_xml = pcmk__xe_next_same(synapse_xml)) {
810 :
811 0 : pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph,
812 : synapse_xml);
813 :
814 0 : if (new_synapse != NULL) {
815 0 : new_graph->synapses = g_list_append(new_graph->synapses,
816 : new_synapse);
817 : }
818 : }
819 :
820 0 : crm_debug("Unpacked transition %d from %s: %d actions in %d synapses",
821 : new_graph->id, new_graph->source, new_graph->num_actions,
822 : new_graph->num_synapses);
823 :
824 0 : return new_graph;
825 : }
826 :
827 :
828 : /*
829 : * Other transition graph utilities
830 : */
831 :
832 : /*!
833 : * \internal
834 : * \brief Synthesize an executor event from a graph action
835 : *
836 : * \param[in] resource If not NULL, use greater call ID than in this XML
837 : * \param[in] action Graph action
838 : * \param[in] status What to use as event execution status
839 : * \param[in] rc What to use as event exit status
840 : * \param[in] exit_reason What to use as event exit reason
841 : *
842 : * \return Newly allocated executor event on success, or NULL otherwise
843 : */
844 : lrmd_event_data_t *
845 0 : pcmk__event_from_graph_action(const xmlNode *resource,
846 : const pcmk__graph_action_t *action,
847 : int status, int rc, const char *exit_reason)
848 : {
849 0 : lrmd_event_data_t *op = NULL;
850 : GHashTableIter iter;
851 0 : const char *name = NULL;
852 0 : const char *value = NULL;
853 0 : xmlNode *action_resource = NULL;
854 :
855 0 : CRM_CHECK(action != NULL, return NULL);
856 0 : CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL);
857 :
858 0 : action_resource = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
859 : NULL);
860 0 : CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid");
861 : return NULL);
862 :
863 0 : op = lrmd_new_event(pcmk__xe_id(action_resource),
864 0 : crm_element_value(action->xml, PCMK_XA_OPERATION),
865 0 : action->interval_ms);
866 0 : lrmd__set_result(op, rc, status, exit_reason);
867 0 : op->t_run = time(NULL);
868 0 : op->t_rcchange = op->t_run;
869 0 : op->params = pcmk__strkey_table(free, free);
870 :
871 0 : g_hash_table_iter_init(&iter, action->params);
872 0 : while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
873 0 : pcmk__insert_dup(op->params, name, value);
874 : }
875 :
876 0 : for (xmlNode *xop = pcmk__xe_first_child(resource, NULL, NULL, NULL);
877 0 : xop != NULL; xop = pcmk__xe_next(xop)) {
878 :
879 0 : int tmp = 0;
880 :
881 0 : crm_element_value_int(xop, PCMK__XA_CALL_ID, &tmp);
882 0 : crm_debug("Got call_id=%d for %s", tmp, pcmk__xe_id(resource));
883 0 : if (tmp > op->call_id) {
884 0 : op->call_id = tmp;
885 : }
886 : }
887 :
888 0 : op->call_id++;
889 0 : return op;
890 : }
|