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