/*
 * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <crm_internal.h>

#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>

#include <glib.h>

#include <allocate.h>
#include <utils.h>

void update_colo_start_chain(action_t * action);
gboolean rsc_update_action(action_t * first, action_t * then, enum pe_ordering type);

static enum pe_action_flags
get_action_flags(action_t * action, node_t * node)
{
    enum pe_action_flags flags = action->flags;

    if (action->rsc) {
        flags = action->rsc->cmds->action_flags(action, NULL);

        if (action->rsc->variant >= pe_clone && node) {

            /* We only care about activity on $node */
            enum pe_action_flags clone_flags = action->rsc->cmds->action_flags(action, node);

            /* Go to great lengths to ensure the correct value for pe_action_runnable...
             *
             * If we are a clone, then for _ordering_ constraints, it's only relevant
             * if we are runnable _anywhere_.
             *
             * This only applies to _runnable_ though, and only for ordering constraints.
             * If this function is ever used during colocation, then we'll need additional logic
             *
             * Not very satisfying, but it's logical and appears to work well.
             */
            if (is_not_set(clone_flags, pe_action_runnable)
                && is_set(flags, pe_action_runnable)) {
                pe_rsc_trace(action->rsc, "Fixing up runnable flag for %s", action->uuid);
                set_bit(clone_flags, pe_action_runnable);
            }
            flags = clone_flags;
        }
    }
    return flags;
}

static char *
convert_non_atomic_uuid(char *old_uuid, resource_t * rsc, gboolean allow_notify,
                        gboolean free_original)
{
    int interval = 0;
    char *uuid = NULL;
    char *rid = NULL;
    char *raw_task = NULL;
    int task = no_action;

    CRM_ASSERT(rsc);
    pe_rsc_trace(rsc, "Processing %s", old_uuid);
    if (old_uuid == NULL) {
        return NULL;

    } else if (strstr(old_uuid, "notify") != NULL) {
        goto done;              /* no conversion */

    } else if (rsc->variant < pe_group) {
        goto done;              /* no conversion */
    }

    CRM_ASSERT(parse_op_key(old_uuid, &rid, &raw_task, &interval));
    if (interval > 0) {
        goto done;              /* no conversion */
    }

    task = text2task(raw_task);
    switch (task) {
        case stop_rsc:
        case start_rsc:
        case action_notify:
        case action_promote:
        case action_demote:
            break;
        case stopped_rsc:
        case started_rsc:
        case action_notified:
        case action_promoted:
        case action_demoted:
            task--;
            break;
        case monitor_rsc:
        case shutdown_crm:
        case stonith_node:
            task = no_action;
            break;
        default:
            crm_err("Unknown action: %s", raw_task);
            task = no_action;
            break;
    }

    if (task != no_action) {
        if (is_set(rsc->flags, pe_rsc_notify) && allow_notify) {
            uuid = generate_notify_key(rid, "confirmed-post", task2text(task + 1));

        } else {
            uuid = generate_op_key(rid, task2text(task + 1), 0);
        }
        pe_rsc_trace(rsc, "Converted %s -> %s", old_uuid, uuid);
    }

  done:
    if (uuid == NULL) {
        uuid = strdup(old_uuid);
    }

    if (free_original) {
        free(old_uuid);
    }

    free(raw_task);
    free(rid);
    return uuid;
}

static action_t *
rsc_expand_action(action_t * action)
{
    action_t *result = action;

    if (action->rsc && action->rsc->variant >= pe_group) {
        /* Expand 'start' -> 'started' */
        char *uuid = NULL;
        gboolean notify = FALSE;

        if (action->rsc->parent == NULL) {
            /* Only outter-most resources have notification actions */
            notify = is_set(action->rsc->flags, pe_rsc_notify);
        }

        uuid = convert_non_atomic_uuid(action->uuid, action->rsc, notify, FALSE);
        if (uuid) {
            pe_rsc_trace(action->rsc, "Converting %s to %s %d", action->uuid, uuid,
                         is_set(action->rsc->flags, pe_rsc_notify));
            result = find_first_action(action->rsc->actions, uuid, NULL, NULL);
            if (result == NULL) {
                crm_err("Couldn't expand %s", action->uuid);
                result = action;
            }
            free(uuid);
        }
    }
    return result;
}

static enum pe_graph_flags
graph_update_action(action_t * first, action_t * then, node_t * node, enum pe_action_flags flags,
                    enum pe_ordering type)
{
    enum pe_graph_flags changed = pe_graph_none;
    gboolean processed = FALSE;

    /* TODO: Do as many of these in parallel as possible */

    if (type & pe_order_implies_then) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags & pe_action_optional,
                                                pe_action_optional, pe_order_implies_then);

        } else if (is_set(flags, pe_action_optional) == FALSE) {
            if (update_action_flags(then, pe_action_optional | pe_action_clear)) {
                changed |= pe_graph_updated_then;
            }
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "implies right: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("implies right: %s then %s %p", first->uuid, then->uuid, then->rsc);
        }
    }

    if ((type & pe_order_restart) && then->rsc) {
        enum pe_action_flags restart = (pe_action_optional | pe_action_runnable);

        processed = TRUE;
        changed |=
            then->rsc->cmds->update_actions(first, then, node, flags, restart, pe_order_restart);
        if (changed) {
            pe_rsc_trace(then->rsc, "restart: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("restart: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_implies_first) {
        processed = TRUE;
        if (first->rsc) {
            changed |=
                first->rsc->cmds->update_actions(first, then, node, flags,
                                                 pe_action_optional, pe_order_implies_first);

        } else if (is_set(flags, pe_action_optional) == FALSE) {
            if (update_action_flags(first, pe_action_runnable | pe_action_clear)) {
                changed |= pe_graph_updated_first;
            }
        }

        if (changed) {
            pe_rsc_trace(then->rsc, "implies left: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("implies left: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_implies_first_master) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags & pe_action_optional,
                                                pe_action_optional, pe_order_implies_first_master);
        }

        if (changed) {
            pe_rsc_trace(then->rsc,
                         "implies left when right rsc is Master role: %s then %s: changed",
                         first->uuid, then->uuid);
        } else {
            crm_trace("implies left when right rsc is Master role: %s then %s", first->uuid,
                      then->uuid);
        }
    }

    if (type & pe_order_one_or_more) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_runnable, pe_order_one_or_more);

        } else if (is_set(flags, pe_action_runnable)) {
            /* alright. a "first" action is considered runnable, incremente
             * the 'runnable_before' counter */
            then->runnable_before++;

            /* if the runnable before count for then exceeds the required number
             * of "before" runnable actions... mark then as runnable */
            if (then->runnable_before >= then->required_runnable_before) {
                if (update_action_flags(then, pe_action_runnable)) {
                    changed |= pe_graph_updated_then;
                }
            }
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "runnable_one_or_more: %s then %s: changed", first->uuid,
                         then->uuid);
        } else {
            crm_trace("runnable_one_or_more: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_runnable_left) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_runnable, pe_order_runnable_left);

        } else if (is_set(flags, pe_action_runnable) == FALSE) {
            if (update_action_flags(then, pe_action_runnable | pe_action_clear)) {
                changed |= pe_graph_updated_then;
            }
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "runnable: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("runnable: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_implies_first_migratable) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_optional, pe_order_implies_first_migratable);
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("optional: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_pseudo_left) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_optional, pe_order_pseudo_left);
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("optional: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_optional) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_runnable, pe_order_optional);
        }
        if (changed) {
            pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("optional: %s then %s", first->uuid, then->uuid);
        }
    }

    if (type & pe_order_asymmetrical) {
        processed = TRUE;
        if (then->rsc) {
            changed |=
                then->rsc->cmds->update_actions(first, then, node, flags,
                                                pe_action_runnable, pe_order_asymmetrical);
        }

        if (changed) {
            pe_rsc_trace(then->rsc, "asymmetrical: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("asymmetrical: %s then %s", first->uuid, then->uuid);
        }

    }

    if ((first->flags & pe_action_runnable) && (type & pe_order_implies_then_printed)
        && (flags & pe_action_optional) == 0) {
        processed = TRUE;
        crm_trace("%s implies %s printed", first->uuid, then->uuid);
        update_action_flags(then, pe_action_print_always);      /* dont care about changed */
    }

    if ((type & pe_order_implies_first_printed) && (flags & pe_action_optional) == 0) {
        processed = TRUE;
        crm_trace("%s implies %s printed", then->uuid, first->uuid);
        update_action_flags(first, pe_action_print_always);     /* dont care about changed */
    }

    if ((type & pe_order_implies_then
         || type & pe_order_implies_first
         || type & pe_order_restart)
        && first->rsc
        && safe_str_eq(first->task, RSC_STOP)
        && is_not_set(first->rsc->flags, pe_rsc_managed)
        && is_set(first->rsc->flags, pe_rsc_block)
        && is_not_set(first->flags, pe_action_runnable)) {

        if (update_action_flags(then, pe_action_runnable | pe_action_clear)) {
            changed |= pe_graph_updated_then;
        }

        if (changed) {
            pe_rsc_trace(then->rsc, "unmanaged left: %s then %s: changed", first->uuid, then->uuid);
        } else {
            crm_trace("unmanaged left: %s then %s", first->uuid, then->uuid);
        }
    }

    if (processed == FALSE) {
        crm_trace("Constraint 0x%.6x not applicable", type);
    }

    return changed;
}

static void
mark_start_blocked(resource_t *rsc)
{
    GListPtr gIter = rsc->actions;

    for (; gIter != NULL; gIter = gIter->next) {
        action_t *action = (action_t *) gIter->data;

        if (safe_str_neq(action->task, RSC_START)) {
            continue;
        }
        if (is_set(action->flags, pe_action_runnable)) {
            clear_bit(action->flags, pe_action_runnable);
            update_colo_start_chain(action);
            update_action(action);
        }
    }
}

void
update_colo_start_chain(action_t *action)
{
    GListPtr gIter = NULL;
    resource_t *rsc = NULL;

    if (is_not_set(action->flags, pe_action_runnable) && safe_str_eq(action->task, RSC_START)) {
        rsc = uber_parent(action->rsc);
    }

    if (rsc == NULL || rsc->rsc_cons_lhs == NULL) {
        return;
    }

    /* if rsc has children, all the children need to have start set to
     * unrunnable before we follow the colo chain for the parent. */
    for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
        resource_t *child = (resource_t *)gIter->data;
        action_t *start = find_first_action(child->actions, NULL, RSC_START, NULL);
        if (start == NULL || is_set(start->flags, pe_action_runnable)) {
            return;
        }
    }

    for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
        rsc_colocation_t *colocate_with = (rsc_colocation_t *)gIter->data;
        if (colocate_with->score == INFINITY) {
            mark_start_blocked(colocate_with->rsc_lh);
        }
    }
}

gboolean
update_action(action_t * then)
{
    GListPtr lpc = NULL;
    enum pe_graph_flags changed = pe_graph_none;
    int last_flags = then->flags;

    crm_trace("Processing %s (%s %s %s)",
              then->uuid,
              is_set(then->flags, pe_action_optional) ? "optional" : "required",
              is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable",
              is_set(then->flags,
                     pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->uname : "");

    if (is_set(then->flags, pe_action_requires_any)) {
        /* initialize current known runnable before actions to 0
         * from here as graph_update_action is called for each of
         * then's before actions, this number will increment as
         * runnable 'first' actions are encountered */
        then->runnable_before = 0;

        /* for backwards compatibility with previous options that use
         * the 'requires_any' flag, initialize required to 1 if it is
         * not set. */ 
        if (then->required_runnable_before == 0) {
            then->required_runnable_before = 1;
        }
        clear_bit(then->flags, pe_action_runnable);
        /* We are relying on the pe_order_one_or_more clause of
         * graph_update_action(), called as part of the:
         *
         *    'if (first == other->action)'
         *
         * block below, to set this back if appropriate
         */
    }

    for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) {
        action_wrapper_t *other = (action_wrapper_t *) lpc->data;
        action_t *first = other->action;

        node_t *then_node = then->node;
        node_t *first_node = first->node;

        enum pe_action_flags then_flags = 0;
        enum pe_action_flags first_flags = 0;

        if (first->rsc && first->rsc->variant == pe_group && safe_str_eq(first->task, RSC_START)) {
            first_node = first->rsc->fns->location(first->rsc, NULL, FALSE);
            if (first_node) {
                crm_trace("First: Found node %s for %s", first_node->details->uname, first->uuid);
            }
        }

        if (then->rsc && then->rsc->variant == pe_group && safe_str_eq(then->task, RSC_START)) {
            then_node = then->rsc->fns->location(then->rsc, NULL, FALSE);
            if (then_node) {
                crm_trace("Then: Found node %s for %s", then_node->details->uname, then->uuid);
            }
        }

        clear_bit(changed, pe_graph_updated_first);

        if (first->rsc != then->rsc
            && first->rsc != NULL && then->rsc != NULL && first->rsc != then->rsc->parent) {
            first = rsc_expand_action(first);
        }
        if (first != other->action) {
            crm_trace("Ordering %s after %s instead of %s", then->uuid, first->uuid,
                      other->action->uuid);
        }

        first_flags = get_action_flags(first, then_node);
        then_flags = get_action_flags(then, first_node);

        crm_trace("Checking %s (%s %s %s) against %s (%s %s %s) filter=0x%.6x type=0x%.6x",
                  then->uuid,
                  is_set(then_flags, pe_action_optional) ? "optional" : "required",
                  is_set(then_flags, pe_action_runnable) ? "runnable" : "unrunnable",
                  is_set(then_flags,
                         pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->
                  uname : "", first->uuid, is_set(first_flags,
                                                  pe_action_optional) ? "optional" : "required",
                  is_set(first_flags, pe_action_runnable) ? "runnable" : "unrunnable",
                  is_set(first_flags,
                         pe_action_pseudo) ? "pseudo" : first->node ? first->node->details->
                  uname : "", first_flags, other->type);

        if (first == other->action) {
            /*
             * 'first' was not expanded (ie. from 'start' to 'running'), which could mean it:
             * - has no associated resource,
             * - was a primitive,
             * - was pre-expanded (ie. 'running' instead of 'start')
             *
             * The third argument here to graph_update_action() is a node which is used under two conditions:
             * - Interleaving, in which case first->node and
             *   then->node are equal (and NULL)
             * - If 'then' is a clone, to limit the scope of the
             *   constraint to instances on the supplied node
             *
             */
            int otype = other->type;
            node_t *node = then->node;

            if(is_set(otype, pe_order_implies_then_on_node)) {
                /* Normally we want the _whole_ 'then' clone to
                 * restart if 'first' is restarted, so then->node is
                 * needed.
                 *
                 * However for unfencing, we want to limit this to
                 * instances on the same node as 'first' (the
                 * unfencing operation), so first->node is supplied.
                 *
                 * Swap the node, from then on we can can treat it
                 * like any other 'pe_order_implies_then'
                 */

                clear_bit(otype, pe_order_implies_then_on_node);
                set_bit(otype, pe_order_implies_then);
                node = first->node;
            }
            clear_bit(first_flags, pe_action_pseudo);
            changed |= graph_update_action(first, then, node, first_flags, otype);

            /* 'first' was for a complex resource (clone, group, etc),
             * create a new dependency if necessary
             */
        } else if (order_actions(first, then, other->type)) {
            /* This was the first time 'first' and 'then' were associated,
             * start again to get the new actions_before list
             */
            changed |= (pe_graph_updated_then | pe_graph_disable);
        }

        if (changed & pe_graph_disable) {
            crm_trace("Disabled constraint %s -> %s", other->action->uuid, then->uuid);
            clear_bit(changed, pe_graph_disable);
            other->type = pe_order_none;
        }

        if (changed & pe_graph_updated_first) {
            GListPtr lpc2 = NULL;

            crm_trace("Updated %s (first %s %s %s), processing dependents ",
                      first->uuid,
                      is_set(first->flags, pe_action_optional) ? "optional" : "required",
                      is_set(first->flags, pe_action_runnable) ? "runnable" : "unrunnable",
                      is_set(first->flags,
                             pe_action_pseudo) ? "pseudo" : first->node ? first->node->details->
                      uname : "");
            for (lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) {
                action_wrapper_t *other = (action_wrapper_t *) lpc2->data;

                update_action(other->action);
            }
            update_action(first);
        }
    }

    if (is_set(then->flags, pe_action_requires_any)) {
        if (last_flags != then->flags) {
            changed |= pe_graph_updated_then;
        } else {
            clear_bit(changed, pe_graph_updated_then);
        }
    }

    if (changed & pe_graph_updated_then) {
        crm_trace("Updated %s (then %s %s %s), processing dependents ",
                  then->uuid,
                  is_set(then->flags, pe_action_optional) ? "optional" : "required",
                  is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable",
                  is_set(then->flags,
                         pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->
                  uname : "");

        if (is_set(last_flags, pe_action_runnable) && is_not_set(then->flags, pe_action_runnable)) {
            update_colo_start_chain(then);
        }
        update_action(then);
        for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) {
            action_wrapper_t *other = (action_wrapper_t *) lpc->data;

            update_action(other->action);
        }
    }

    return FALSE;
}

gboolean
shutdown_constraints(node_t * node, action_t * shutdown_op, pe_working_set_t * data_set)
{
    /* add the stop to the before lists so it counts as a pre-req
     * for the shutdown
     */
    GListPtr lpc = NULL;

    for (lpc = data_set->actions; lpc != NULL; lpc = lpc->next) {
        action_t *action = (action_t *) lpc->data;

        if (action->rsc == NULL || action->node == NULL) {
            continue;
        } else if (action->node->details != node->details) {
            continue;
        } else if (is_set(action->rsc->flags, pe_rsc_maintenance)) {
            pe_rsc_trace(action->rsc, "Skipping %s: maintenance mode", action->uuid);
            continue;
        } else if (node->details->maintenance) {
            pe_rsc_trace(action->rsc, "Skipping %s: node %s is in maintenance mode",
                         action->uuid, node->details->uname);
            continue;
        } else if (safe_str_neq(action->task, RSC_STOP)) {
            continue;
        } else if (is_not_set(action->rsc->flags, pe_rsc_managed)
                   && is_not_set(action->rsc->flags, pe_rsc_block)) {
            /*
             * If another action depends on this one, we may still end up blocking
             */
            pe_rsc_trace(action->rsc, "Skipping %s: unmanaged", action->uuid);
            continue;
        }

        pe_rsc_trace(action->rsc, "Ordering %s before shutdown on %s", action->uuid,
                     node->details->uname);
        pe_clear_action_bit(action, pe_action_optional);
        custom_action_order(action->rsc, NULL, action,
                            NULL, strdup(CRM_OP_SHUTDOWN), shutdown_op,
                            pe_order_optional | pe_order_runnable_left, data_set);
    }

    return TRUE;
}

/*!
 * \internal
 * \brief Order all actions appropriately relative to a fencing operation
 *
 * Ensure start operations of affected resources are ordered after fencing,
 * imply stop and demote operations of affected resources by marking them as
 * pseudo-actions, etc.
 *
 * \param[in]     node        Node to be fenced
 * \param[in]     stonith_op  Fencing operation
 * \param[in/out] data_set    Working set of cluster
 */
gboolean
stonith_constraints(node_t * node, action_t * stonith_op, pe_working_set_t * data_set)
{
    GListPtr r = NULL;

    CRM_CHECK(stonith_op != NULL, return FALSE);
    for (r = data_set->resources; r != NULL; r = r->next) {
        resource_t *rsc = (resource_t *) r->data;

        if ((stonith_op->rsc == NULL)
            || ((stonith_op->rsc != rsc) && (stonith_op->rsc != rsc->container))) {

            rsc_stonith_ordering(rsc, stonith_op, data_set);
        }
    }
    return TRUE;
}

static node_t *
get_router_node(action_t *action)
{
    node_t *began_on = NULL;
    node_t *ended_on = NULL;
    node_t *router_node = NULL;

    if (safe_str_eq(action->task, CRM_OP_FENCE) || is_remote_node(action->node) == FALSE) {
        return NULL;
    }

    CRM_ASSERT(action->node->details->remote_rsc != NULL);

    if (action->node->details->remote_rsc->running_on) {
        began_on = action->node->details->remote_rsc->running_on->data;
    }
    ended_on = action->node->details->remote_rsc->allocated_to;

    /* if there is only one location to choose from,
     * this is easy. Check for those conditions first */
    if (!began_on || !ended_on) {
        /* remote rsc is either shutting down or starting up */
        return began_on ? began_on : ended_on;
    } else if (began_on->details == ended_on->details) {
        /* remote rsc didn't move nodes. */
        return began_on;
    }

    /* If we have get here, we know the remote resource
     * began on one node and is moving to another node.
     *
     * This means some actions will get routed through the cluster
     * node the connection rsc began on, and others are routed through
     * the cluster node the connection rsc ends up on.
     *
     * 1. stop, demote, migrate actions of resources living in the remote
     *    node _MUST_ occur _BEFORE_ the connection can move (these actions
     *    are all required before the remote rsc stop action can occur.) In
     *    this case, we know these actions have to be routed through the initial
     *    cluster node the connection resource lived on before the move takes place.
     *
     * 2. Everything else (start, promote, monitor, probe, refresh, clear failcount
     *    delete ....) must occur after the resource starts on the node it is
     *    moving to.
     */

    /* 1. before connection rsc moves. */
    if (safe_str_eq(action->task, "stop") ||
        safe_str_eq(action->task, "demote") ||
        safe_str_eq(action->task, "migrate_from") ||
        safe_str_eq(action->task, "migrate_to")) {

        router_node = began_on;

    /* 2. after connection rsc moves. */
    } else {
        router_node = ended_on;
    }
    return router_node;
}

/*!
 * \internal
 * \brief Add an XML node tag for a specified ID
 *
 * \param[in]     id      Node UUID to add
 * \param[in,out] xml     Parent XML tag to add to
 */
static void
add_node_to_xml_by_id(const char *id, xmlNode *xml)
{
    xmlNode *node_xml;

    node_xml = create_xml_node(xml, XML_CIB_TAG_NODE);
    crm_xml_add(node_xml, XML_ATTR_UUID, id);
}

/*!
 * \internal
 * \brief Add an XML node tag for a specified node
 *
 * \param[in]     node  Node to add
 * \param[in/out] xml   XML to add node to
 */
static void
add_node_to_xml(const node_t *node, void *xml)
{
    add_node_to_xml_by_id(node->details->id, (xmlNode *) xml);
}

/*!
 * \internal
 * \brief Add XML with nodes that an action is expected to bring down
 *
 * If a specified action is expected to bring any nodes down, add an XML block
 * with their UUIDs. When a node is lost, this allows the crmd to determine
 * whether it was expected.
 *
 * \param[in,out] xml       Parent XML tag to add to
 * \param[in]     action    Action to check for downed nodes
 * \param[in]     data_set  Working set for cluster
 */
static void
add_downed_nodes(xmlNode *xml, const action_t *action,
                 const pe_working_set_t *data_set)
{
    CRM_CHECK(xml && action && action->node && data_set, return);

    if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {

        /* Shutdown makes the action's node down */
        xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
        add_node_to_xml_by_id(action->node->details->id, downed);

    } else if (safe_str_eq(action->task, CRM_OP_FENCE)) {

        /* Fencing makes the action's node and any hosted guest nodes down */
        const char *fence = g_hash_table_lookup(action->meta, "stonith_action");

        if (safe_str_eq(fence, "off") || safe_str_eq(fence, "reboot")) {
            xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
            add_node_to_xml_by_id(action->node->details->id, downed);
            pe_foreach_guest_node(data_set, action->node, add_node_to_xml, downed);
        }

    } else if (action->rsc && action->rsc->is_remote_node
               && safe_str_eq(action->task, CRMD_ACTION_STOP)) {

        /* Stopping a remote connection resource makes connected node down,
         * unless it's part of a migration
         */
        GListPtr iter;
        action_t *input;
        gboolean migrating = FALSE;

        for (iter = action->actions_before; iter != NULL; iter = iter->next) {
            input = ((action_wrapper_t *) iter->data)->action;
            if (input->rsc && safe_str_eq(action->rsc->id, input->rsc->id)
               && safe_str_eq(input->task, CRMD_ACTION_MIGRATED)) {
                migrating = TRUE;
                break;
            }
        }
        if (!migrating) {
            xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
            add_node_to_xml_by_id(action->rsc->id, downed);
        }
    }
}

static xmlNode *
action2xml(action_t * action, gboolean as_input, pe_working_set_t *data_set)
{
    gboolean needs_node_info = TRUE;
    xmlNode *action_xml = NULL;
    xmlNode *args_xml = NULL;

    if (action == NULL) {
        return NULL;
    }

    if (safe_str_eq(action->task, CRM_OP_FENCE)) {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

    } else if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

    } else if (safe_str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT)) {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

    } else if (safe_str_eq(action->task, CRM_OP_LRM_REFRESH)) {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

/* 	} else if(safe_str_eq(action->task, RSC_PROBED)) { */
/* 		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); */

    } else if (is_set(action->flags, pe_action_pseudo)) {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_PSEUDO_EVENT);
        needs_node_info = FALSE;

    } else {
        action_xml = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
    }

    crm_xml_add_int(action_xml, XML_ATTR_ID, action->id);

    crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task);
    if (action->rsc != NULL && action->rsc->clone_name != NULL) {
        char *clone_key = NULL;
        const char *interval_s = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
        int interval = crm_parse_int(interval_s, "0");

        if (safe_str_eq(action->task, RSC_NOTIFY)) {
            const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
            const char *n_task = g_hash_table_lookup(action->meta, "notify_operation");

            CRM_CHECK(n_type != NULL, crm_err("No notify type value found for %s", action->uuid));
            CRM_CHECK(n_task != NULL,
                      crm_err("No notify operation value found for %s", action->uuid));
            clone_key = generate_notify_key(action->rsc->clone_name, n_type, n_task);

        } else if(action->cancel_task) {
            clone_key = generate_op_key(action->rsc->clone_name, action->cancel_task, interval);
        } else {
            clone_key = generate_op_key(action->rsc->clone_name, action->task, interval);
        }

        CRM_CHECK(clone_key != NULL, crm_err("Could not generate a key for %s", action->uuid));
        crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key);
        crm_xml_add(action_xml, "internal_" XML_LRM_ATTR_TASK_KEY, action->uuid);
        free(clone_key);

    } else {
        crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid);
    }

    if (needs_node_info && action->node != NULL) {
        node_t *router_node = get_router_node(action);

        crm_xml_add(action_xml, XML_LRM_ATTR_TARGET, action->node->details->uname);
        crm_xml_add(action_xml, XML_LRM_ATTR_TARGET_UUID, action->node->details->id);
        if (router_node) {
            crm_xml_add(action_xml, XML_LRM_ATTR_ROUTER_NODE, router_node->details->uname);
        }
    }

    /* No details if this action is only being listed in the inputs section */
    if (as_input) {
        return action_xml;
    }

    /* List affected resource */
    if (action->rsc) {
        if (is_set(action->flags, pe_action_pseudo) == FALSE) {
            int lpc = 0;

            xmlNode *rsc_xml = create_xml_node(action_xml, crm_element_name(action->rsc->xml));

            const char *attr_list[] = {
                XML_AGENT_ATTR_CLASS,
                XML_AGENT_ATTR_PROVIDER,
                XML_ATTR_TYPE
            };

            if (is_set(action->rsc->flags, pe_rsc_orphan) && action->rsc->clone_name) {
                /* Do not use the 'instance free' name here as that
                 * might interfere with the instance we plan to keep.
                 * Ie. if there are more than two named /anonymous/
                 * instances on a given node, we need to make sure the
                 * command goes to the right one.
                 *
                 * Keep this block, even when everyone is using
                 * 'instance free' anonymous clone names - it means
                 * we'll do the right thing if anyone toggles the
                 * unique flag to 'off'
                 */
                crm_debug("Using orphan clone name %s instead of %s", action->rsc->id,
                          action->rsc->clone_name);
                crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name);
                crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);

            } else if (is_not_set(action->rsc->flags, pe_rsc_unique)) {
                const char *xml_id = ID(action->rsc->xml);

                crm_debug("Using anonymous clone name %s for %s (aka. %s)", xml_id, action->rsc->id,
                          action->rsc->clone_name);

                /* ID is what we'd like client to use
                 * ID_LONG is what they might know it as instead
                 *
                 * ID_LONG is only strictly needed /here/ during the
                 * transition period until all nodes in the cluster
                 * are running the new software /and/ have rebooted
                 * once (meaning that they've only ever spoken to a DC
                 * supporting this feature).
                 *
                 * If anyone toggles the unique flag to 'on', the
                 * 'instance free' name will correspond to an orphan
                 * and fall into the claus above instead
                 */
                crm_xml_add(rsc_xml, XML_ATTR_ID, xml_id);
                if (action->rsc->clone_name && safe_str_neq(xml_id, action->rsc->clone_name)) {
                    crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->clone_name);
                } else {
                    crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
                }

            } else {
                CRM_ASSERT(action->rsc->clone_name == NULL);
                crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id);
            }

            for (lpc = 0; lpc < DIMOF(attr_list); lpc++) {
                crm_xml_add(rsc_xml, attr_list[lpc],
                            g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
            }
        }
    }

    /* List any attributes in effect */
    args_xml = create_xml_node(NULL, XML_TAG_ATTRS);
    crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);

    g_hash_table_foreach(action->extra, hash2field, args_xml);
    if (action->rsc != NULL && action->node) {
        GHashTable *p = g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str);

        get_rsc_attributes(p, action->rsc, action->node, data_set);
        g_hash_table_foreach(p, hash2smartfield, args_xml);

        g_hash_table_destroy(p);
    } else if(action->rsc && action->rsc->variant <= pe_native) {
        g_hash_table_foreach(action->rsc->parameters, hash2smartfield, args_xml);
    }

    g_hash_table_foreach(action->meta, hash2metafield, args_xml);
    if (action->rsc != NULL) {
        int isolated = 0;
        resource_t *parent = action->rsc;

        while (parent != NULL) {
            isolated |= parent->isolation_wrapper ? 1 : 0;
            parent->cmds->append_meta(parent, args_xml);
            parent = parent->parent;
        }

        if (isolated && action->node) {
            char *nodeattr = crm_meta_name(XML_RSC_ATTR_ISOLATION_HOST);
            crm_xml_add(args_xml, nodeattr, action->node->details->uname);
            free(nodeattr);
        }

    } else if (safe_str_eq(action->task, CRM_OP_FENCE) && action->node) {
        g_hash_table_foreach(action->node->details->attrs, hash2metafield, args_xml);
    }

    sorted_xml(args_xml, action_xml, FALSE);
    free_xml(args_xml);

    /* List any nodes this action is expected to make down */
    if (needs_node_info && (action->node != NULL)) {
        add_downed_nodes(action_xml, action, data_set);
    }

    crm_log_xml_trace(action_xml, "dumped action");
    return action_xml;
}

static gboolean
should_dump_action(action_t * action)
{
    CRM_CHECK(action != NULL, return FALSE);

    if (is_set(action->flags, pe_action_dumped)) {
        crm_trace("action %d (%s) was already dumped", action->id, action->uuid);
        return FALSE;

    } else if (is_set(action->flags, pe_action_pseudo) && safe_str_eq(action->task, CRM_OP_PROBED)) {
        GListPtr lpc = NULL;

        /* This is a horrible but convenient hack
         *
         * It mimimizes the number of actions with unsatisfied inputs
         * (ie. not included in the graph)
         *
         * This in turn, means we can be more concise when printing
         * aborted/incomplete graphs.
         *
         * It also makes it obvious which node is preventing
         * probe_complete from running (presumably because it is only
         * partially up)
         *
         * For these reasons we tolerate such perversions
         */

        for (lpc = action->actions_after; lpc != NULL; lpc = lpc->next) {
            action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data;

            if (is_not_set(wrapper->action->flags, pe_action_runnable)) {
                /* Only interested in runnable operations */
            } else if (safe_str_neq(wrapper->action->task, RSC_START)) {
                /* Only interested in start operations */
            } else if (is_set(wrapper->action->flags, pe_action_dumped)) {
                crm_trace("action %d (%s) dependency of %s",
                          action->id, action->uuid, wrapper->action->uuid);
                return TRUE;

            } else if (should_dump_action(wrapper->action)) {
                crm_trace("action %d (%s) dependency of %s",
                          action->id, action->uuid, wrapper->action->uuid);
                return TRUE;
            }
        }
    }

    if (is_set(action->flags, pe_action_runnable) == FALSE) {
        crm_trace("action %d (%s) was not runnable", action->id, action->uuid);
        return FALSE;

    } else if (is_set(action->flags, pe_action_optional)
               && is_set(action->flags, pe_action_print_always) == FALSE) {
        crm_trace("action %d (%s) was optional", action->id, action->uuid);
        return FALSE;

    } else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) {
        const char *interval = NULL;

        interval = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);

        /* make sure probes and recurring monitors go through */
        if (safe_str_neq(action->task, RSC_STATUS) && interval == NULL) {
            crm_trace("action %d (%s) was for an unmanaged resource (%s)",
                      action->id, action->uuid, action->rsc->id);
            return FALSE;
        }
    }

    if (is_set(action->flags, pe_action_pseudo)
        || safe_str_eq(action->task, CRM_OP_FENCE)
        || safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
        /* skip the next checks */
        return TRUE;
    }

    if (action->node == NULL) {
        pe_err("action %d (%s) was not allocated", action->id, action->uuid);
        log_action(LOG_DEBUG, "Unallocated action", action, FALSE);
        return FALSE;

    } else if (action->node->details->online == FALSE) {
        pe_err("action %d was (%s) scheduled for offline node", action->id, action->uuid);
        log_action(LOG_DEBUG, "Action for offline node", action, FALSE);
        return FALSE;
#if 0
        /* but this would also affect resources that can be safely
         *  migrated before a fencing op
         */
    } else if (action->node->details->unclean == FALSE) {
        pe_err("action %d was (%s) scheduled for unclean node", action->id, action->uuid);
        log_action(LOG_DEBUG, "Action for unclean node", action, FALSE);
        return FALSE;
#endif
    }
    return TRUE;
}

/* lowest to highest */
static gint
sort_action_id(gconstpointer a, gconstpointer b)
{
    const action_wrapper_t *action_wrapper2 = (const action_wrapper_t *)a;
    const action_wrapper_t *action_wrapper1 = (const action_wrapper_t *)b;

    if (a == NULL) {
        return 1;
    }
    if (b == NULL) {
        return -1;
    }

    if (action_wrapper1->action->id > action_wrapper2->action->id) {
        return -1;
    }

    if (action_wrapper1->action->id < action_wrapper2->action->id) {
        return 1;
    }
    return 0;
}

static gboolean
check_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper)
{
    int type = wrapper->type;

    if (wrapper->state == pe_link_dumped) {
        return TRUE;

    } else if (wrapper->state == pe_link_dup) {
        return FALSE;
    }

    type &= ~pe_order_implies_first_printed;
    type &= ~pe_order_implies_then_printed;
    type &= ~pe_order_optional;

    if (wrapper->action->node
        && action->rsc && action->rsc->fillers
        && is_not_set(type, pe_order_preserve)
        && wrapper->action->node->details->remote_rsc
        && uber_parent(action->rsc) != uber_parent(wrapper->action->rsc)
        ) {
        /* This prevents user-defined ordering constraints between
         * resources in remote nodes and the resources that
         * define/represent a remote node.
         *
         * There is no known valid reason to allow this sort of thing
         * but if one arises, we'd need to change the
         * action->rsc->fillers clause to be more specific, possibly
         * to check that it contained wrapper->action->rsc
         */
        crm_warn("Invalid ordering constraint between %s and %s",
                 wrapper->action->rsc->id, action->rsc->id);
        wrapper->type = pe_order_none;
        return FALSE;
    }

    if (last_action == wrapper->action->id) {
        crm_trace("Input (%d) %s duplicated for %s",
                  wrapper->action->id, wrapper->action->uuid, action->uuid);
        wrapper->state = pe_link_dup;
        return FALSE;

    } else if (wrapper->type == pe_order_none) {
        crm_trace("Input (%d) %s suppressed for %s",
                  wrapper->action->id, wrapper->action->uuid, action->uuid);
        return FALSE;

    } else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE
               && type == pe_order_none && safe_str_neq(wrapper->action->uuid, CRM_OP_PROBED)) {
        crm_trace("Input (%d) %s optional (ordering) for %s",
                  wrapper->action->id, wrapper->action->uuid, action->uuid);
        return FALSE;

    } else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE
               && is_set(type, pe_order_one_or_more)) {
        crm_trace("Input (%d) %s optional (one-or-more) for %s",
                  wrapper->action->id, wrapper->action->uuid, action->uuid);
        return FALSE;

    } else if (is_set(action->flags, pe_action_pseudo)
               && (wrapper->type & pe_order_stonith_stop)) {
        crm_trace("Input (%d) %s suppressed for %s",
                  wrapper->action->id, wrapper->action->uuid, action->uuid);
        return FALSE;

    } else if ((wrapper->type & pe_order_implies_first_migratable) && (is_set(wrapper->action->flags, pe_action_runnable) == FALSE)) {
        return FALSE;

    } else if ((wrapper->type & pe_order_apply_first_non_migratable)
                && (is_set(wrapper->action->flags, pe_action_migrate_runnable))) {
        return FALSE;

    } else if ((wrapper->type == pe_order_optional)
               && strstr(wrapper->action->uuid, "_stop_0")
               && is_set(wrapper->action->flags, pe_action_migrate_runnable)) {

        /* for optional only ordering, ordering is not preserved for
         * a stop action that is actually involved with a migration. */
        return FALSE;

    } else if (wrapper->type == pe_order_load) {
        crm_trace("check load filter %s.%s -> %s.%s",
                  wrapper->action->uuid,
                  wrapper->action->node ? wrapper->action->node->details->uname : "", action->uuid,
                  action->node ? action->node->details->uname : "");

        if (action->rsc && safe_str_eq(action->task, RSC_MIGRATE)) {
            /* Remove the orders like the following if not relevant:
             *     "load_stopped_node2" -> "rscA_migrate_to node1"
             * which were created also from: pengine/native.c: MigrateRsc()
             *     order_actions(other, then, other_w->type);
             */

            /* For migrate_to ops, we care about where it has been
             * allocated to, not where the action will be executed
             */
            if (wrapper->action->node == NULL || action->rsc->allocated_to == NULL
                || wrapper->action->node->details != action->rsc->allocated_to->details) {
                /* Check if the actions are for the same node, ignore otherwise */
                crm_trace("load filter - migrate");
                wrapper->type = pe_order_none;
                return FALSE;
            }

        } else if (wrapper->action->node == NULL || action->node == NULL
                   || wrapper->action->node->details != action->node->details) {
            /* Check if the actions are for the same node, ignore otherwise */
            crm_trace("load filter - node");
            wrapper->type = pe_order_none;
            return FALSE;

        } else if (is_set(wrapper->action->flags, pe_action_optional)) {
            /* Check if the pre-req is optional, ignore if so */
            crm_trace("load filter - optional");
            wrapper->type = pe_order_none;
            return FALSE;
        }

    } else if (wrapper->type == pe_order_anti_colocation) {
        crm_trace("check anti-colocation filter %s.%s -> %s.%s",
                  wrapper->action->uuid,
                  wrapper->action->node ? wrapper->action->node->details->uname : "",
                  action->uuid,
                  action->node ? action->node->details->uname : "");

        if (wrapper->action->node && action->node
            && wrapper->action->node->details != action->node->details) {
            /* Check if the actions are for the same node, ignore otherwise */
            crm_trace("anti-colocation filter - node");
            wrapper->type = pe_order_none;
            return FALSE;

        } else if (is_set(wrapper->action->flags, pe_action_optional)) {
            /* Check if the pre-req is optional, ignore if so */
            crm_trace("anti-colocation filter - optional");
            wrapper->type = pe_order_none;
            return FALSE;
        }

    } else if (wrapper->action->rsc
               && wrapper->action->rsc != action->rsc
               && is_set(wrapper->action->rsc->flags, pe_rsc_failed)
               && is_not_set(wrapper->action->rsc->flags, pe_rsc_managed)
               && strstr(wrapper->action->uuid, "_stop_0")
               && action->rsc && action->rsc->variant >= pe_clone) {
        crm_warn("Ignoring requirement that %s complete before %s:"
                 " unmanaged failed resources cannot prevent clone shutdown",
                 wrapper->action->uuid, action->uuid);
        return FALSE;

    } else if (is_set(wrapper->action->flags, pe_action_dumped)
               || should_dump_action(wrapper->action)) {
        crm_trace("Input (%d) %s should be dumped for %s", wrapper->action->id,
                  wrapper->action->uuid, action->uuid);
        goto dump;

#if 0
    } else if (is_set(wrapper->action->flags, pe_action_runnable)
               && is_set(wrapper->action->flags, pe_action_pseudo)
               && wrapper->action->rsc->variant != pe_native) {
        crm_crit("Input (%d) %s should be dumped for %s",
                 wrapper->action->id, wrapper->action->uuid, action->uuid);
        goto dump;
#endif
    } else if (is_set(wrapper->action->flags, pe_action_optional) == TRUE
               && is_set(wrapper->action->flags, pe_action_print_always) == FALSE) {
        crm_trace("Input (%d) %s optional for %s", wrapper->action->id,
                  wrapper->action->uuid, action->uuid);
        crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x",
                  wrapper->action->id, wrapper->action->uuid, wrapper->action->node,
                  is_set(wrapper->action->flags, pe_action_pseudo),
                  is_set(wrapper->action->flags, pe_action_runnable),
                  is_set(wrapper->action->flags, pe_action_optional),
                  is_set(wrapper->action->flags, pe_action_print_always), wrapper->type);
        return FALSE;

    }

  dump:
    return TRUE;
}

static gboolean
graph_has_loop(action_t * init_action, action_t * action, action_wrapper_t * wrapper)
{
    GListPtr lpc = NULL;
    gboolean has_loop = FALSE;

    if (is_set(wrapper->action->flags, pe_action_tracking)) {
        crm_trace("Breaking tracking loop: %s.%s -> %s.%s (0x%.6x)",
                  wrapper->action->uuid,
                  wrapper->action->node ? wrapper->action->node->details->uname : "",
                  action->uuid,
                  action->node ? action->node->details->uname : "",
                  wrapper->type);
        return FALSE;
    }

    if (check_dump_input(-1, action, wrapper) == FALSE) {
        return FALSE;
    }

    /* If there's any order like:
     * "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
     * rscA is being migrated from node1 to node2,
     * while rscB is being migrated from node2 to node1.
     * There will be potential graph loop.
     * Break the order "load_stopped_node2" -> "rscA_migrate_to node1".
     */

    crm_trace("Checking graph loop: %s.%s -> %s.%s (0x%.6x)",
              wrapper->action->uuid,
              wrapper->action->node ? wrapper->action->node->details->uname : "",
              action->uuid,
              action->node ? action->node->details->uname : "",
              wrapper->type);

    if (wrapper->action == init_action) {
        crm_debug("Found graph loop: %s.%s ->...-> %s.%s",
                  action->uuid,
                  action->node ? action->node->details->uname : "",
                  init_action->uuid,
                  init_action->node ? init_action->node->details->uname : "");

        return TRUE;
    }

    set_bit(wrapper->action->flags, pe_action_tracking);

    for (lpc = wrapper->action->actions_before; lpc != NULL; lpc = lpc->next) {
        action_wrapper_t *wrapper_before = (action_wrapper_t *) lpc->data;

        if (graph_has_loop(init_action, wrapper->action, wrapper_before)) {
            has_loop = TRUE;
            goto done;
        }
    }

done:
    clear_bit(wrapper->action->flags, pe_action_tracking);

    return has_loop;
}

static gboolean
should_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper)
{
    wrapper->state = pe_link_not_dumped;

    if (check_dump_input(last_action, action, wrapper) == FALSE) {
        return FALSE;
    }

    if (wrapper->type == pe_order_load
        && action->rsc
        && safe_str_eq(action->task, RSC_MIGRATE)) {
        crm_trace("Checking graph loop - load migrate: %s.%s -> %s.%s",
                  wrapper->action->uuid,
                  wrapper->action->node ? wrapper->action->node->details->uname : "",
                  action->uuid,
                  action->node ? action->node->details->uname : "");

        if (graph_has_loop(action, action, wrapper)) {
            /* Remove the orders like the following if they are introducing any graph loops:
             *     "load_stopped_node2" -> "rscA_migrate_to node1"
             * which were created also from: pengine/native.c: MigrateRsc()
             *     order_actions(other, then, other_w->type);
             */
            crm_debug("Breaking graph loop - load migrate: %s.%s -> %s.%s",
                      wrapper->action->uuid,
                      wrapper->action->node ? wrapper->action->node->details->uname : "",
                      action->uuid,
                      action->node ? action->node->details->uname : "");

            wrapper->type = pe_order_none;
            return FALSE;
        }
    }

    crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x dumped for %s",
              wrapper->action->id,
              wrapper->action->uuid,
              wrapper->action->node,
              is_set(wrapper->action->flags, pe_action_pseudo),
              is_set(wrapper->action->flags, pe_action_runnable),
              is_set(wrapper->action->flags, pe_action_optional),
              is_set(wrapper->action->flags, pe_action_print_always), wrapper->type, action->uuid);
    return TRUE;
}

void
graph_element_from_action(action_t * action, pe_working_set_t * data_set)
{
    GListPtr lpc = NULL;
    int last_action = -1;
    int synapse_priority = 0;
    xmlNode *syn = NULL;
    xmlNode *set = NULL;
    xmlNode *in = NULL;
    xmlNode *input = NULL;
    xmlNode *xml_action = NULL;

    if (should_dump_action(action) == FALSE) {
        return;
    }

    set_bit(action->flags, pe_action_dumped);

    syn = create_xml_node(data_set->graph, "synapse");
    set = create_xml_node(syn, "action_set");
    in = create_xml_node(syn, "inputs");

    crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse);
    data_set->num_synapse++;

    if (action->rsc != NULL) {
        synapse_priority = action->rsc->priority;
    }
    if (action->priority > synapse_priority) {
        synapse_priority = action->priority;
    }
    if (synapse_priority > 0) {
        crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority);
    }

    xml_action = action2xml(action, FALSE, data_set);
    add_node_nocopy(set, crm_element_name(xml_action), xml_action);

    action->actions_before = g_list_sort(action->actions_before, sort_action_id);

    for (lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
        action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data;

        if (should_dump_input(last_action, action, wrapper) == FALSE) {
            continue;
        }

        wrapper->state = pe_link_dumped;
        CRM_CHECK(last_action < wrapper->action->id,;
            );
        last_action = wrapper->action->id;
        input = create_xml_node(in, "trigger");

        xml_action = action2xml(wrapper->action, TRUE, data_set);
        add_node_nocopy(input, crm_element_name(xml_action), xml_action);
    }
}
