diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c index 4fe563a025..de90635a8d 100644 --- a/daemons/controld/controld_te_utils.c +++ b/daemons/controld/controld_te_utils.c @@ -1,291 +1,337 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include gboolean stop_te_timer(crm_action_timer_t * timer) { if (timer == NULL) { return FALSE; } if (timer->source_id != 0) { crm_trace("Stopping action timer"); g_source_remove(timer->source_id); timer->source_id = 0; } else { crm_trace("Action timer was already stopped"); return FALSE; } return TRUE; } gboolean te_graph_trigger(gpointer user_data) { if (transition_graph == NULL) { crm_debug("Nothing to do"); return TRUE; } crm_trace("Invoking graph %d in state %s", transition_graph->id, fsa_state2string(fsa_state)); switch (fsa_state) { case S_STARTING: case S_PENDING: case S_NOT_DC: case S_HALT: case S_ILLEGAL: case S_STOPPING: case S_TERMINATE: return TRUE; default: break; } if (transition_graph->complete == FALSE) { enum transition_status graph_rc; int limit = transition_graph->batch_limit; transition_graph->batch_limit = throttle_get_total_job_limit(limit); graph_rc = pcmk__execute_graph(transition_graph); transition_graph->batch_limit = limit; /* Restore the configured value */ /* significant overhead... */ /* print_graph(LOG_TRACE, transition_graph); */ if (graph_rc == transition_active) { crm_trace("Transition not yet complete"); return TRUE; } else if (graph_rc == transition_pending) { crm_trace("Transition not yet complete - no actions fired"); return TRUE; } if (graph_rc != transition_complete) { crm_warn("Transition failed: %s", transition_status(graph_rc)); print_graph(LOG_NOTICE, transition_graph); } } crm_debug("Transition %d is now complete", transition_graph->id); transition_graph->complete = TRUE; notify_crmd(transition_graph); return TRUE; } void trigger_graph_processing(const char *fn, int line) { crm_trace("%s:%d - Triggered graph processing", fn, line); mainloop_set_trigger(transition_trigger); } static struct abort_timer_s { bool aborted; guint id; int priority; enum transition_action action; const char *text; } abort_timer = { 0, }; static gboolean abort_timer_popped(gpointer data) { if (AM_I_DC && (abort_timer.aborted == FALSE)) { abort_transition(abort_timer.priority, abort_timer.action, abort_timer.text, NULL); } abort_timer.id = 0; return FALSE; // do not immediately reschedule timer } /*! * \internal * \brief Abort transition after delay, if not already aborted in that time * * \param[in] abort_text Must be literal string */ void abort_after_delay(int abort_priority, enum transition_action abort_action, const char *abort_text, guint delay_ms) { if (abort_timer.id) { // Timer already in progress, stop and reschedule g_source_remove(abort_timer.id); } abort_timer.aborted = FALSE; abort_timer.priority = abort_priority; abort_timer.action = abort_action; abort_timer.text = abort_text; abort_timer.id = g_timeout_add(delay_ms, abort_timer_popped, NULL); } +static const char * +abort2text(enum transition_action abort_action) +{ + switch (abort_action) { + case tg_done: + return "done"; + case tg_stop: + return "stop"; + case tg_restart: + return "restart"; + case tg_shutdown: + return "shutdown"; + } + return "unknown"; +} + +static bool +update_abort_priority(crm_graph_t *graph, int priority, + enum transition_action action, const char *abort_reason) +{ + bool change = FALSE; + + if (graph == NULL) { + return change; + } + + if (graph->abort_priority < priority) { + crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority); + graph->abort_priority = priority; + if (graph->abort_reason != NULL) { + crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason); + } + graph->abort_reason = abort_reason; + change = TRUE; + } + + if (graph->completion_action < action) { + crm_debug("Abort action %s superseded by %s: %s", + abort2text(graph->completion_action), abort2text(action), abort_reason); + graph->completion_action = action; + change = TRUE; + } + + return change; +} + void abort_transition_graph(int abort_priority, enum transition_action abort_action, const char *abort_text, xmlNode * reason, const char *fn, int line) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; int level = LOG_INFO; xmlNode *diff = NULL; xmlNode *change = NULL; CRM_CHECK(transition_graph != NULL, return); switch (fsa_state) { case S_STARTING: case S_PENDING: case S_NOT_DC: case S_HALT: case S_ILLEGAL: case S_STOPPING: case S_TERMINATE: crm_info("Abort %s suppressed: state=%s (complete=%d)", abort_text, fsa_state2string(fsa_state), transition_graph->complete); return; default: break; } abort_timer.aborted = TRUE; controld_expect_sched_reply(NULL); if (transition_graph->complete == FALSE) { if(update_abort_priority(transition_graph, abort_priority, abort_action, abort_text)) { level = LOG_NOTICE; } } if(reason) { xmlNode *search = NULL; for(search = reason; search; search = search->parent) { if (pcmk__str_eq(XML_TAG_DIFF, TYPE(search), pcmk__str_casei)) { diff = search; break; } } if(diff) { xml_patch_versions(diff, add, del); for(search = reason; search; search = search->parent) { if (pcmk__str_eq(XML_DIFF_CHANGE, TYPE(search), pcmk__str_casei)) { change = search; break; } } } } if(reason == NULL) { do_crm_log(level, "Transition %d aborted: %s "CRM_XS" source=%s:%d complete=%s", transition_graph->id, abort_text, fn, line, pcmk__btoa(transition_graph->complete)); } else if(change == NULL) { char *local_path = xml_get_path(reason); do_crm_log(level, "Transition %d aborted by %s.%s: %s " CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s", transition_graph->id, TYPE(reason), ID(reason), abort_text, add[0], add[1], add[2], fn, line, local_path, pcmk__btoa(transition_graph->complete)); free(local_path); } else { const char *kind = NULL; const char *op = crm_element_value(change, XML_DIFF_OP); const char *path = crm_element_value(change, XML_DIFF_PATH); if(change == reason) { if(strcmp(op, "create") == 0) { reason = reason->children; } else if(strcmp(op, "modify") == 0) { reason = first_named_child(reason, XML_DIFF_RESULT); if(reason) { reason = reason->children; } } } kind = TYPE(reason); if(strcmp(op, "delete") == 0) { const char *shortpath = strrchr(path, '/'); do_crm_log(level, "Transition %d aborted by deletion of %s: %s " CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s", transition_graph->id, (shortpath? (shortpath + 1) : path), abort_text, add[0], add[1], add[2], fn, line, path, pcmk__btoa(transition_graph->complete)); } else if (pcmk__str_eq(XML_CIB_TAG_NVPAIR, kind, pcmk__str_casei)) { do_crm_log(level, "Transition %d aborted by %s doing %s %s=%s: %s " CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s", transition_graph->id, crm_element_value(reason, XML_ATTR_ID), op, crm_element_value(reason, XML_NVPAIR_ATTR_NAME), crm_element_value(reason, XML_NVPAIR_ATTR_VALUE), abort_text, add[0], add[1], add[2], fn, line, path, pcmk__btoa(transition_graph->complete)); } else if (pcmk__str_eq(XML_LRM_TAG_RSC_OP, kind, pcmk__str_casei)) { const char *magic = crm_element_value(reason, XML_ATTR_TRANSITION_MAGIC); do_crm_log(level, "Transition %d aborted by operation %s '%s' on %s: %s " CRM_XS " magic=%s cib=%d.%d.%d source=%s:%d complete=%s", transition_graph->id, crm_element_value(reason, XML_LRM_ATTR_TASK_KEY), op, crm_element_value(reason, XML_LRM_ATTR_TARGET), abort_text, magic, add[0], add[1], add[2], fn, line, pcmk__btoa(transition_graph->complete)); } else if (pcmk__strcase_any_of(kind, XML_CIB_TAG_STATE, XML_CIB_TAG_NODE, NULL)) { const char *uname = crm_peer_uname(ID(reason)); do_crm_log(level, "Transition %d aborted by %s '%s' on %s: %s " CRM_XS " cib=%d.%d.%d source=%s:%d complete=%s", transition_graph->id, kind, op, (uname? uname : ID(reason)), abort_text, add[0], add[1], add[2], fn, line, pcmk__btoa(transition_graph->complete)); } else { const char *id = ID(reason); do_crm_log(level, "Transition %d aborted by %s.%s '%s': %s " CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s", transition_graph->id, TYPE(reason), (id? id : ""), (op? op : "change"), abort_text, add[0], add[1], add[2], fn, line, path, pcmk__btoa(transition_graph->complete)); } } if (transition_graph->complete) { if (transition_timer->period_ms > 0) { controld_stop_timer(transition_timer); controld_start_timer(transition_timer); } else { register_fsa_input(C_FSA_INTERNAL, I_PE_CALC, NULL); } return; } mainloop_set_trigger(transition_trigger); } diff --git a/include/pcmki/pcmki_transition.h b/include/pcmki/pcmki_transition.h index d715aa0904..304f0dfa2b 100644 --- a/include/pcmki/pcmki_transition.h +++ b/include/pcmki/pcmki_transition.h @@ -1,142 +1,140 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_TRANSITION__H # define CRM_TRANSITION__H #ifdef __cplusplus extern "C" { #endif #include #include #include #include typedef enum { action_type_pseudo, action_type_rsc, action_type_crm } action_type_e; typedef struct te_timer_s crm_action_timer_t; typedef struct crm_graph_s crm_graph_t; typedef struct synapse_s { int id; int priority; gboolean ready; gboolean failed; gboolean executed; gboolean confirmed; GList *actions; /* crm_action_t* */ GList *inputs; /* crm_action_t* */ } synapse_t; typedef struct crm_action_s { int id; int timeout; guint interval_ms; GHashTable *params; action_type_e type; crm_action_timer_t *timer; synapse_t *synapse; gboolean sent_update; /* sent to the CIB */ gboolean executed; /* sent to the CRM */ gboolean confirmed; gboolean failed; gboolean can_fail; //! \deprecated Will be removed in a future release xmlNode *xml; } crm_action_t; struct te_timer_s { int source_id; int timeout; crm_action_t *action; }; /* order matters here */ enum transition_action { tg_done, tg_stop, tg_restart, tg_shutdown, }; struct crm_graph_s { int id; char *source; int abort_priority; gboolean complete; const char *abort_reason; enum transition_action completion_action; int num_actions; int num_synapses; int batch_limit; guint network_delay; guint stonith_timeout; int fired; int pending; int skipped; int completed; int incomplete; GList *synapses; /* synapse_t* */ int migration_limit; }; typedef struct crm_graph_functions_s { gboolean(*pseudo) (crm_graph_t * graph, crm_action_t * action); gboolean(*rsc) (crm_graph_t * graph, crm_action_t * action); gboolean(*crmd) (crm_graph_t * graph, crm_action_t * action); gboolean(*stonith) (crm_graph_t * graph, crm_action_t * action); gboolean(*allowed) (crm_graph_t * graph, crm_action_t * action); } crm_graph_functions_t; enum transition_status { transition_active, transition_pending, /* active but no actions performed this time */ transition_complete, transition_stopped, transition_terminated, transition_action_failed, transition_failed, }; void pcmk__set_graph_functions(crm_graph_functions_t *fns); crm_graph_t *pcmk__unpack_graph(xmlNode *xml_graph, const char *reference); enum transition_status pcmk__execute_graph(crm_graph_t *graph); void pcmk__update_graph(crm_graph_t *graph, crm_action_t *action); void pcmk__free_graph(crm_graph_t *graph); const char *transition_status(enum transition_status state); void print_graph(unsigned int log_level, crm_graph_t * graph); void print_action(int log_level, const char *prefix, crm_action_t * action); -bool update_abort_priority(crm_graph_t * graph, int priority, - enum transition_action action, const char *abort_reason); lrmd_event_data_t *pcmk__event_from_graph_action(xmlNode *resource, crm_action_t *action, int status, int rc); #ifdef __cplusplus } #endif #endif diff --git a/lib/pacemaker/pcmk_graph_consumer.c b/lib/pacemaker/pcmk_graph_consumer.c index b2031cc378..08198a850f 100644 --- a/lib/pacemaker/pcmk_graph_consumer.c +++ b/lib/pacemaker/pcmk_graph_consumer.c @@ -1,891 +1,845 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include /* * Functions for updating graph */ /*! * \internal * \brief Update synapse after completed prerequisite * * A synapse is ready to be executed once all its prerequisite actions (inputs) * complete. Given a completed action, check whether it is an input for a given * synapse, and if so, mark the input as confirmed, and mark the synapse as * ready if appropriate. * * \param[in] synapse Transition graph synapse to update * \param[in] action_id ID of an action that completed * * \note The only substantial effect here is confirming synapse inputs. * should_fire_synapse() will recalculate synapse->ready, so the only * thing that uses the synapse->ready value from here is * synapse_state_str(). */ static void update_synapse_ready(synapse_t *synapse, int action_id) { if (synapse->ready) { return; // All inputs have already been confirmed } synapse->ready = TRUE; // Presume ready until proven otherwise for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) { crm_action_t *prereq = (crm_action_t *) lpc->data; if (prereq->id == action_id) { crm_trace("Confirming input %d of synapse %d", action_id, synapse->id); prereq->confirmed = TRUE; } else if (!(prereq->confirmed)) { synapse->ready = FALSE; crm_trace("Synapse %d still not ready after action %d", synapse->id, action_id); } } if (synapse->ready) { crm_trace("Synapse %d is now ready to execute", synapse->id); } } /*! * \internal * \brief Update action and synapse confirmation after action completion * * \param[in] synapse Transition graph synapse that action belongs to * \param[in] action_id ID of action that completed */ static void update_synapse_confirmed(synapse_t *synapse, int action_id) { bool all_confirmed = true; for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { crm_action_t *action = (crm_action_t *) lpc->data; if (action->id == action_id) { crm_trace("Confirmed action %d of synapse %d", action_id, synapse->id); action->confirmed = TRUE; } else if (all_confirmed && !(action->confirmed)) { all_confirmed = false; crm_trace("Synapse %d still not confirmed after action %d", synapse->id, action_id); } } if (all_confirmed && !(synapse->confirmed)) { crm_trace("Confirmed synapse %d", synapse->id); synapse->confirmed = TRUE; } } /*! * \internal * \brief Update the transition graph with a completed action result * * \param[in,out] graph Transition graph to update * \param[in] action Action that completed */ void pcmk__update_graph(crm_graph_t *graph, crm_action_t *action) { for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { synapse_t *synapse = (synapse_t *) lpc->data; if (synapse->confirmed || synapse->failed) { continue; // This synapse already completed } else if (synapse->executed) { update_synapse_confirmed(synapse, action->id); } else if (!(action->failed) || (synapse->priority == INFINITY)) { update_synapse_ready(synapse, action->id); } } } /* * Functions for executing graph */ /* A transition graph consists of various types of actions. The library caller * registers execution functions for each action type, which will be stored * here. */ static crm_graph_functions_t *graph_fns = NULL; /*! * \internal * \brief Set transition graph execution functions * * \param[in] Execution functions to use */ void pcmk__set_graph_functions(crm_graph_functions_t *fns) { crm_debug("Setting custom functions for executing transition graphs"); graph_fns = fns; CRM_ASSERT(graph_fns != NULL); CRM_ASSERT(graph_fns->rsc != NULL); CRM_ASSERT(graph_fns->crmd != NULL); CRM_ASSERT(graph_fns->pseudo != NULL); CRM_ASSERT(graph_fns->stonith != NULL); } /*! * \internal * \brief Check whether a graph synapse is ready to be executed * * \param[in] graph Transition graph that synapse is part of * \param[in] synapse Synapse to check * * \return true if synapse is ready, false otherwise */ static bool should_fire_synapse(crm_graph_t *graph, synapse_t *synapse) { GList *lpc = NULL; synapse->ready = TRUE; for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) { crm_action_t *prereq = (crm_action_t *) lpc->data; if (!(prereq->confirmed)) { crm_trace("Input %d for synapse %d not yet confirmed", prereq->id, synapse->id); synapse->ready = FALSE; break; } else if (prereq->failed && !(prereq->can_fail)) { crm_trace("Input %d for synapse %d confirmed but failed", prereq->id, synapse->id); synapse->ready = FALSE; break; } } if (synapse->ready) { crm_trace("Synapse %d is ready to execute", synapse->id); } else { return false; } for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { crm_action_t *a = (crm_action_t *) lpc->data; if (a->type == action_type_pseudo) { /* None of the below applies to pseudo ops */ } else if (synapse->priority < graph->abort_priority) { crm_trace("Skipping synapse %d: priority %d is less than " "abort priority %d", synapse->id, synapse->priority, graph->abort_priority); graph->skipped++; return false; } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) { crm_trace("Deferring synapse %d: not allowed", synapse->id); return false; } } return true; } /*! * \internal * \brief Initiate an action from a transition graph * * \param[in] graph Transition graph containing action * \parma[in] action Action to execute * * \return TRUE if action was initiated, FALSE otherwise */ static gboolean initiate_action(crm_graph_t *graph, crm_action_t *action) { const char *id = ID(action->xml); CRM_CHECK(!(action->executed), return FALSE); CRM_CHECK(id != NULL, return FALSE); action->executed = TRUE; switch (action->type) { case action_type_pseudo: crm_trace("Executing pseudo-action %d (%s)", action->id, id); return graph_fns->pseudo(graph, action); case action_type_rsc: crm_trace("Executing resource action %d (%s)", action->id, id); return graph_fns->rsc(graph, action); case action_type_crm: if (pcmk__str_eq(crm_element_value(action->xml, XML_LRM_ATTR_TASK), CRM_OP_FENCE, pcmk__str_casei)) { crm_trace("Executing fencing action %d (%s)", action->id, id); return graph_fns->stonith(graph, action); } crm_trace("Executing control action %d (%s)", action->id, id); return graph_fns->crmd(graph, action); default: crm_err("Unsupported graph action type <%s id='%s'> (bug?)", crm_element_name(action->xml), id); return FALSE; } } /*! * \internal * \brief Execute a graph synapse * * \param[in] graph Transition graph with synapse to execute * \param[in] synapse Synapse to execute * * \return Standard Pacemaker return value */ static int fire_synapse(crm_graph_t *graph, synapse_t *synapse) { synapse->executed = TRUE; for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { crm_action_t *action = (crm_action_t *) lpc->data; if (!initiate_action(graph, action)) { crm_err("Failed initiating <%s id=%d> in synapse %d", crm_element_name(action->xml), action->id, synapse->id); synapse->confirmed = TRUE; action->confirmed = TRUE; action->failed = TRUE; return pcmk_rc_error; } } return pcmk_rc_ok; } /*! * \internal * \brief Dummy graph method that can be used with simulations * * \param[in] graph Transition graph containing action * \param[in] action Action to be initiated * * \retval TRUE Action initiation was (simulated to be) successful * \retval FALSE Action initiation was (simulated to be) failed (due to the * PE_fail environment variable being set to the action ID) */ static gboolean pseudo_action_dummy(crm_graph_t * graph, crm_action_t * action) { static int fail = -1; if (fail < 0) { long long fail_ll; if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok) && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) { fail = (int) fail_ll; } else { fail = 0; } } if (action->id == fail) { crm_err("Dummy event handler: pretending action %d failed", action->id); action->failed = TRUE; graph->abort_priority = INFINITY; } else { crm_trace("Dummy event handler: action %d initiated", action->id); } action->confirmed = TRUE; pcmk__update_graph(graph, action); return TRUE; } static crm_graph_functions_t default_fns = { pseudo_action_dummy, pseudo_action_dummy, pseudo_action_dummy, pseudo_action_dummy }; /*! * \internal * \brief Execute all actions in a transition graph * * \param[in] graph Transition graph to execute * * \return Status of transition after execution */ enum transition_status pcmk__execute_graph(crm_graph_t *graph) { GList *lpc = NULL; int log_level = LOG_DEBUG; enum transition_status pass_result = transition_active; const char *status = "In progress"; if (graph_fns == NULL) { graph_fns = &default_fns; } if (graph == NULL) { return transition_complete; } graph->fired = 0; graph->pending = 0; graph->skipped = 0; graph->completed = 0; graph->incomplete = 0; // Count completed and in-flight synapses for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { synapse_t *synapse = (synapse_t *) lpc->data; if (synapse->confirmed) { graph->completed++; } else if (!(synapse->failed) && synapse->executed) { graph->pending++; } } crm_trace("Executing graph %d (%d synapses already completed, %d pending)", graph->id, graph->completed, graph->pending); // Execute any synapses that are ready for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { synapse_t *synapse = (synapse_t *) lpc->data; if ((graph->batch_limit > 0) && (graph->pending >= graph->batch_limit)) { crm_debug("Throttling graph execution: batch limit (%d) reached", graph->batch_limit); break; } else if (synapse->failed) { graph->skipped++; continue; } else if (synapse->confirmed || synapse->executed) { continue; // Already handled } else if (should_fire_synapse(graph, synapse)) { graph->fired++; if (fire_synapse(graph, synapse) != pcmk_rc_ok) { crm_err("Synapse %d failed to fire", synapse->id); log_level = LOG_ERR; graph->abort_priority = INFINITY; graph->incomplete++; graph->fired--; } if (!(synapse->confirmed)) { graph->pending++; } } else { crm_trace("Synapse %d cannot fire", synapse->id); graph->incomplete++; } } if ((graph->pending == 0) && (graph->fired == 0)) { graph->complete = TRUE; if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) { log_level = LOG_WARNING; pass_result = transition_terminated; status = "Terminated"; } else if (graph->skipped != 0) { log_level = LOG_NOTICE; pass_result = transition_complete; status = "Stopped"; } else { log_level = LOG_NOTICE; pass_result = transition_complete; status = "Complete"; } } else if (graph->fired == 0) { pass_result = transition_pending; } do_crm_log(log_level, "Transition %d (Complete=%d, Pending=%d," " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s", graph->id, graph->completed, graph->pending, graph->fired, graph->skipped, graph->incomplete, graph->source, status); return pass_result; } /* * Functions for unpacking transition graph XML into structs */ /*! * \internal * \brief Unpack a transition graph action from XML * * \param[in] parent Synapse that action is part of * \param[in] xml_action Action XML to unparse * * \return Newly allocated action on success, or NULL otherwise */ static crm_action_t * unpack_action(synapse_t *parent, xmlNode *xml_action) { action_type_e action_type; crm_action_t *action = NULL; const char *element = TYPE(xml_action); const char *value = ID(xml_action); if (value == NULL) { crm_err("Ignoring transition graph action without id (bug?)"); crm_log_xml_trace(xml_action, "invalid"); return NULL; } if (pcmk__str_eq(element, XML_GRAPH_TAG_RSC_OP, pcmk__str_casei)) { action_type = action_type_rsc; } else if (pcmk__str_eq(element, XML_GRAPH_TAG_PSEUDO_EVENT, pcmk__str_casei)) { action_type = action_type_pseudo; } else if (pcmk__str_eq(element, XML_GRAPH_TAG_CRM_EVENT, pcmk__str_casei)) { action_type = action_type_crm; } else { crm_err("Ignoring transition graph action of unknown type '%s' (bug?)", element); crm_log_xml_trace(xml_action, "invalid"); return NULL; } action = calloc(1, sizeof(crm_action_t)); if (action == NULL) { crm_perror(LOG_CRIT, "Cannot unpack transition graph action"); crm_log_xml_trace(xml_action, "lost"); return NULL; } pcmk__scan_min_int(value, &(action->id), -1); action->type = action_type_rsc; action->xml = copy_xml(xml_action); action->synapse = parent; action->type = action_type; action->params = xml2list(action->xml); value = g_hash_table_lookup(action->params, "CRM_meta_timeout"); pcmk__scan_min_int(value, &(action->timeout), 0); /* Take start-delay into account for the timeout of the action timer */ value = g_hash_table_lookup(action->params, "CRM_meta_start_delay"); { int start_delay; pcmk__scan_min_int(value, &start_delay, 0); action->timeout += start_delay; } if (pcmk__guint_from_hash(action->params, CRM_META "_" XML_LRM_ATTR_INTERVAL, 0, &(action->interval_ms)) != pcmk_rc_ok) { action->interval_ms = 0; } value = g_hash_table_lookup(action->params, "CRM_meta_can_fail"); if (value != NULL) { crm_str_to_boolean(value, &(action->can_fail)); #ifndef PCMK__COMPAT_2_0 if (action->can_fail) { crm_warn("Support for the can_fail meta-attribute is deprecated" " and will be removed in a future release"); } #endif } crm_trace("Action %d has timer set to %dms", action->id, action->timeout); return action; } /*! * \internal * \brief Unpack transition graph synapse from XML * * \param[in] new_graph Transition graph that synapse is part of * \param[in] xml_synapse Synapse XML * * \return Newly allocated synapse on success, or NULL otherwise */ static synapse_t * unpack_synapse(crm_graph_t *new_graph, xmlNode *xml_synapse) { const char *value = NULL; xmlNode *action_set = NULL; synapse_t *new_synapse = NULL; crm_trace("Unpacking synapse %s", ID(xml_synapse)); new_synapse = calloc(1, sizeof(synapse_t)); if (new_synapse == NULL) { return NULL; } pcmk__scan_min_int(ID(xml_synapse), &(new_synapse->id), 0); value = crm_element_value(xml_synapse, XML_CIB_ATTR_PRIORITY); pcmk__scan_min_int(value, &(new_synapse->priority), 0); CRM_CHECK(new_synapse->id >= 0, free(new_synapse); return NULL); new_graph->num_synapses++; crm_trace("Unpacking synapse %s action sets", crm_element_value(xml_synapse, XML_ATTR_ID)); for (action_set = first_named_child(xml_synapse, "action_set"); action_set != NULL; action_set = crm_next_same_xml(action_set)) { for (xmlNode *action = pcmk__xml_first_child(action_set); action != NULL; action = pcmk__xml_next(action)) { crm_action_t *new_action = unpack_action(new_synapse, action); if (new_action == NULL) { continue; } crm_trace("Adding action %d to synapse %d", new_action->id, new_synapse->id); new_graph->num_actions++; new_synapse->actions = g_list_append(new_synapse->actions, new_action); } } crm_trace("Unpacking synapse %s inputs", ID(xml_synapse)); for (xmlNode *inputs = first_named_child(xml_synapse, "inputs"); inputs != NULL; inputs = crm_next_same_xml(inputs)) { for (xmlNode *trigger = first_named_child(inputs, "trigger"); trigger != NULL; trigger = crm_next_same_xml(trigger)) { for (xmlNode *input = pcmk__xml_first_child(trigger); input != NULL; input = pcmk__xml_next(input)) { crm_action_t *new_input = unpack_action(new_synapse, input); if (new_input == NULL) { continue; } crm_trace("Adding input %d to synapse %d", new_input->id, new_synapse->id); new_synapse->inputs = g_list_append(new_synapse->inputs, new_input); } } } return new_synapse; } /*! * \internal * \brief Unpack transition graph XML * * \param[in] xml_graph Transition graph XML to unpack * \param[in] reference Where the XML came from (for logging) * * \return Newly allocated transition graph on success, NULL otherwise * \note The caller is responsible for freeing the return value using * pcmk__free_graph(). * \note The XML is expected to be structured like: ... ... */ crm_graph_t * pcmk__unpack_graph(xmlNode *xml_graph, const char *reference) { crm_graph_t *new_graph = NULL; const char *t_id = NULL; const char *time = NULL; new_graph = calloc(1, sizeof(crm_graph_t)); if (new_graph == NULL) { return NULL; } new_graph->source = strdup((reference == NULL)? "unknown" : reference); if (new_graph->source == NULL) { free(new_graph); return NULL; } new_graph->id = -1; new_graph->abort_priority = 0; new_graph->network_delay = 0; new_graph->stonith_timeout = 0; new_graph->completion_action = tg_done; // Parse top-level attributes from if (xml_graph != NULL) { t_id = crm_element_value(xml_graph, "transition_id"); CRM_CHECK(t_id != NULL, free(new_graph); return NULL); pcmk__scan_min_int(t_id, &(new_graph->id), -1); time = crm_element_value(xml_graph, "cluster-delay"); CRM_CHECK(time != NULL, free(new_graph); return NULL); new_graph->network_delay = crm_parse_interval_spec(time); time = crm_element_value(xml_graph, "stonith-timeout"); if (time == NULL) { new_graph->stonith_timeout = new_graph->network_delay; } else { new_graph->stonith_timeout = crm_parse_interval_spec(time); } // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum t_id = crm_element_value(xml_graph, "batch-limit"); if ((t_id == NULL) || (pcmk__scan_min_int(t_id, &(new_graph->batch_limit), -1) != pcmk_rc_ok)) { new_graph->batch_limit = 0; } t_id = crm_element_value(xml_graph, "migration-limit"); pcmk__scan_min_int(t_id, &(new_graph->migration_limit), -1); } // Unpack each child element for (xmlNode *synapse_xml = first_named_child(xml_graph, "synapse"); synapse_xml != NULL; synapse_xml = crm_next_same_xml(synapse_xml)) { synapse_t *new_synapse = unpack_synapse(new_graph, synapse_xml); if (new_synapse != NULL) { new_graph->synapses = g_list_append(new_graph->synapses, new_synapse); } } crm_debug("Unpacked transition %d from %s: %d actions in %d synapses", new_graph->id, new_graph->source, new_graph->num_actions, new_graph->num_synapses); return new_graph; } /* * Functions for freeing transition graph objects */ /*! * \internal * \brief Free a transition graph action object * * \param[in] user_data Action to free */ static void free_graph_action(gpointer user_data) { crm_action_t *action = user_data; if ((action->timer != NULL) && (action->timer->source_id != 0)) { crm_warn("Cancelling timer for graph action %d", action->id); g_source_remove(action->timer->source_id); } if (action->params != NULL) { g_hash_table_destroy(action->params); } free_xml(action->xml); free(action->timer); free(action); } /*! * \internal * \brief Free a transition graph synapse object * * \param[in] user_data Synapse to free */ static void free_graph_synapse(gpointer user_data) { synapse_t *synapse = user_data; g_list_free_full(synapse->actions, free_graph_action); g_list_free_full(synapse->inputs, free_graph_action); free(synapse); } /*! * \internal * \brief Free a transition graph object * * \param[in] graph Transition graph to free */ void pcmk__free_graph(crm_graph_t *graph) { if (graph != NULL) { g_list_free_full(graph->synapses, free_graph_synapse); free(graph->source); free(graph); } } /* * Other transition graph utilities */ /*! * \internal * \brief Synthesize an executor event from a graph action * * \param[in] resource If not NULL, use greater call ID than in this XML * \param[in] action Graph action * \param[in] status What to use as event execution status * \param[in] rc What to use as event exit status * * \return Newly allocated executor event on success, or NULL otherwise */ lrmd_event_data_t * pcmk__event_from_graph_action(xmlNode *resource, crm_action_t *action, int status, int rc) { lrmd_event_data_t *op = NULL; GHashTableIter iter; const char *name = NULL; const char *value = NULL; xmlNode *action_resource = NULL; CRM_CHECK(action != NULL, return NULL); CRM_CHECK(action->type == action_type_rsc, return NULL); action_resource = first_named_child(action->xml, XML_CIB_TAG_RESOURCE); CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid"); return NULL); op = lrmd_new_event(ID(action_resource), crm_element_value(action->xml, XML_LRM_ATTR_TASK), action->interval_ms); op->rc = rc; op->op_status = status; op->t_run = time(NULL); op->t_rcchange = op->t_run; op->params = pcmk__strkey_table(free, free); g_hash_table_iter_init(&iter, action->params); while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) { g_hash_table_insert(op->params, strdup(name), strdup(value)); } for (xmlNode *xop = pcmk__xml_first_child(resource); xop != NULL; xop = pcmk__xml_next(xop)) { int tmp = 0; crm_element_value_int(xop, XML_LRM_ATTR_CALLID, &tmp); crm_debug("Got call_id=%d for %s", tmp, ID(resource)); if (tmp > op->call_id) { op->call_id = tmp; } } op->call_id++; return op; } - -static const char * -abort2text(enum transition_action abort_action) -{ - switch (abort_action) { - case tg_done: - return "done"; - case tg_stop: - return "stop"; - case tg_restart: - return "restart"; - case tg_shutdown: - return "shutdown"; - } - return "unknown"; -} - -bool -update_abort_priority(crm_graph_t * graph, int priority, - enum transition_action action, const char *abort_reason) -{ - bool change = FALSE; - - if (graph == NULL) { - return change; - } - - if (graph->abort_priority < priority) { - crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority); - graph->abort_priority = priority; - if (graph->abort_reason != NULL) { - crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason); - } - graph->abort_reason = abort_reason; - change = TRUE; - } - - if (graph->completion_action < action) { - crm_debug("Abort action %s superseded by %s: %s", - abort2text(graph->completion_action), abort2text(action), abort_reason); - graph->completion_action = action; - change = TRUE; - } - - return change; -}