org.opendaylight.openflowplugin.applications.frsync.util.ReconcileUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.openflowplugin.applications.frsync.util.ReconcileUtil.java

Source

/**
 * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.openflowplugin.applications.frsync.util;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.JdkFutureAdapters;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCase;
import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.Bucket;
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Util methods for group reconcil task (future chaining, transforms).
 */
public final class ReconcileUtil {

    private static final Logger LOG = LoggerFactory.getLogger(ReconcileUtil.class);

    private ReconcileUtil() {
        throw new IllegalStateException("This class should not be instantiated.");
    }

    /**
     * @param previousItemAction description for case when the triggering future contains failure
     * @param <D>                type of rpc output (gathered in list)
     * @return single rpc result of type Void honoring all partial rpc results
     */
    public static <D> Function<List<RpcResult<D>>, RpcResult<Void>> createRpcResultCondenser(
            final String previousItemAction) {
        return input -> {
            final RpcResultBuilder<Void> resultSink;
            if (input != null) {
                List<RpcError> errors = new ArrayList<>();
                for (RpcResult<D> rpcResult : input) {
                    if (!rpcResult.isSuccessful()) {
                        errors.addAll(rpcResult.getErrors());
                    }
                }
                if (errors.isEmpty()) {
                    resultSink = RpcResultBuilder.success();
                } else {
                    resultSink = RpcResultBuilder.<Void>failed().withRpcErrors(errors);
                }
            } else {
                resultSink = RpcResultBuilder.<Void>failed().withError(RpcError.ErrorType.APPLICATION,
                        "previous " + previousItemAction + " failed");
            }
            return resultSink.build();
        };
    }

    /**
     * @param actionDescription description for case when the triggering future contains failure
     * @param <D>               type of rpc output (gathered in list)
     * @return single rpc result of type Void honoring all partial rpc results
     */
    public static <D> Function<RpcResult<D>, RpcResult<Void>> createRpcResultToVoidFunction(
            final String actionDescription) {
        return input -> {
            final RpcResultBuilder<Void> resultSink;
            if (input != null) {
                List<RpcError> errors = new ArrayList<>();
                if (!input.isSuccessful()) {
                    errors.addAll(input.getErrors());
                    resultSink = RpcResultBuilder.<Void>failed().withRpcErrors(errors);
                } else {
                    resultSink = RpcResultBuilder.success();
                }
            } else {
                resultSink = RpcResultBuilder.<Void>failed().withError(RpcError.ErrorType.APPLICATION,
                        "action of " + actionDescription + " failed");
            }
            return resultSink.build();
        };
    }

    /**
     * @param nodeIdent                     flow capable node path - target device for routed rpc
     * @param flowCapableTransactionService barrier rpc service
     * @return async barrier result
     */
    public static AsyncFunction<RpcResult<Void>, RpcResult<Void>> chainBarrierFlush(
            final InstanceIdentifier<Node> nodeIdent,
            final FlowCapableTransactionService flowCapableTransactionService) {
        return input -> {
            final SendBarrierInput barrierInput = new SendBarrierInputBuilder().setNode(new NodeRef(nodeIdent))
                    .build();
            return JdkFutureAdapters.listenInPoolThread(flowCapableTransactionService.sendBarrier(barrierInput));
        };
    }

    /**
     * @param nodeId             target node
     * @param installedGroupsArg groups resent on device
     * @param pendingGroups      groups configured for device
     * @return list of safe synchronization steps with updates
     */
    public static List<ItemSyncBox<Group>> resolveAndDivideGroupDiffs(final NodeId nodeId,
            final Map<Long, Group> installedGroupsArg, final Collection<Group> pendingGroups) {
        return resolveAndDivideGroupDiffs(nodeId, installedGroupsArg, pendingGroups, true);
    }

    /**
     * @param nodeId             target node
     * @param installedGroupsArg groups resent on device
     * @param pendingGroups      groups configured for device
     * @param gatherUpdates      check content of pending item if present on device (and create update task eventually)
     * @return list of safe synchronization steps
     */
    public static List<ItemSyncBox<Group>> resolveAndDivideGroupDiffs(final NodeId nodeId,
            final Map<Long, Group> installedGroupsArg, final Collection<Group> pendingGroups,
            final boolean gatherUpdates) {
        final Map<Long, Group> installedGroups = new HashMap<>(installedGroupsArg);
        final List<ItemSyncBox<Group>> plan = new ArrayList<>();

        while (!Iterables.isEmpty(pendingGroups)) {
            final ItemSyncBox<Group> stepPlan = new ItemSyncBox<>();
            final Iterator<Group> iterator = pendingGroups.iterator();
            final Map<Long, Group> installIncrement = new HashMap<>();

            while (iterator.hasNext()) {
                final Group group = iterator.next();

                final Group existingGroup = installedGroups.get(group.getGroupId().getValue());
                if (existingGroup != null) {
                    if (!gatherUpdates) {
                        iterator.remove();
                    } else {
                        // check buckets and eventually update
                        if (group.equals(existingGroup)) {
                            iterator.remove();
                        } else {
                            if (checkGroupPrecondition(installedGroups.keySet(), group)) {
                                iterator.remove();
                                LOG.trace("Group {} on device {} differs - planned for update", group.getGroupId(),
                                        nodeId);
                                stepPlan.getItemsToUpdate()
                                        .add(new ItemSyncBox.ItemUpdateTuple<>(existingGroup, group));
                            }
                        }
                    }
                } else if (checkGroupPrecondition(installedGroups.keySet(), group)) {
                    iterator.remove();
                    installIncrement.put(group.getGroupId().getValue(), group);
                    stepPlan.getItemsToPush().add(group);
                }
            }

            if (!stepPlan.isEmpty()) {
                // atomic update of installed flows in order to keep plan portions clean of local group dependencies
                installedGroups.putAll(installIncrement);
                plan.add(stepPlan);
            } else if (!pendingGroups.isEmpty()) {
                LOG.warn("Failed to resolve and divide groups into preconditions-match based ordered plan: {}, "
                        + "resolving stuck at level {}", nodeId.getValue(), plan.size());
                throw new IllegalStateException("Failed to resolve and divide groups when matching preconditions");
            }
        }

        return plan;
    }

    public static boolean checkGroupPrecondition(final Set<Long> installedGroupIds, final Group pendingGroup) {
        boolean okToInstall = true;
        // check each bucket in the pending group
        for (Bucket bucket : pendingGroup.getBuckets().getBucket()) {
            for (Action action : bucket.getAction()) {
                // if the output action is a group
                if (GroupActionCase.class.equals(action.getAction().getImplementedInterface())) {
                    Long groupId = ((GroupActionCase) (action.getAction())).getGroupAction().getGroupId();
                    // see if that output group is installed
                    if (!installedGroupIds.contains(groupId)) {
                        // if not installed, we have missing dependencies and cannot install this pending group
                        okToInstall = false;
                        break;
                    }
                }
            }
            if (!okToInstall) {
                break;
            }
        }
        return okToInstall;
    }

    public static <E> int countTotalPushed(final Iterable<ItemSyncBox<E>> groupsAddPlan) {
        int count = 0;
        for (ItemSyncBox<E> groupItemSyncBox : groupsAddPlan) {
            count += groupItemSyncBox.getItemsToPush().size();
        }
        return count;
    }

    public static <E> int countTotalUpdated(final Iterable<ItemSyncBox<E>> groupsAddPlan) {
        int count = 0;
        for (ItemSyncBox<E> groupItemSyncBox : groupsAddPlan) {
            count += groupItemSyncBox.getItemsToUpdate().size();
        }
        return count;
    }

    /**
     * @param nodeId              target node
     * @param meterOperationalMap meters present on device
     * @param metersConfigured    meters configured for device
     * @param gatherUpdates       check content of pending item if present on device (and create update task eventually)
     * @return synchronization box
     */
    public static ItemSyncBox<Meter> resolveMeterDiffs(final NodeId nodeId,
            final Map<MeterId, Meter> meterOperationalMap, final List<Meter> metersConfigured,
            final boolean gatherUpdates) {
        LOG.trace("resolving meters for {}", nodeId.getValue());
        final ItemSyncBox<Meter> syncBox = new ItemSyncBox<>();
        for (Meter meter : metersConfigured) {
            final Meter existingMeter = meterOperationalMap.get(meter.getMeterId());
            if (existingMeter == null) {
                syncBox.getItemsToPush().add(meter);
            } else {
                // compare content and eventually update
                if (gatherUpdates && !meter.equals(existingMeter)) {
                    syncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(existingMeter, meter));
                }
            }
        }
        return syncBox;
    }

    /**
     * @param flowsConfigured    flows resent on device
     * @param flowOperationalMap flows configured for device
     * @param gatherUpdates      check content of pending item if present on device (and create update task eventually)
     * @return list of safe synchronization steps
     */
    public static ItemSyncBox<Flow> resolveFlowDiffsInTable(final List<Flow> flowsConfigured,
            final Map<FlowDescriptor, Flow> flowOperationalMap, final boolean gatherUpdates) {
        final ItemSyncBox<Flow> flowsSyncBox = new ItemSyncBox<>();
        // loop configured flows and check if already present on device
        for (final Flow flow : flowsConfigured) {
            final Flow existingFlow = FlowCapableNodeLookups.flowMapLookupExisting(flow, flowOperationalMap);

            if (existingFlow == null) {
                flowsSyncBox.getItemsToPush().add(flow);
            } else {
                // check instructions and eventually update
                if (gatherUpdates && !Objects.equals(flow.getInstructions(), existingFlow.getInstructions())) {
                    flowsSyncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(existingFlow, flow));
                }
            }
        }
        return flowsSyncBox;
    }

    /**
     * @param nodeId              target node
     * @param tableOperationalMap flow-tables resent on device
     * @param tablesConfigured    flow-tables configured for device
     * @param gatherUpdates       check content of pending item if present on device (and create update task eventually)
     * @return map : key={@link TableKey}, value={@link ItemSyncBox} of safe synchronization steps
     */
    public static Map<TableKey, ItemSyncBox<Flow>> resolveFlowDiffsInAllTables(final NodeId nodeId,
            final Map<Short, Table> tableOperationalMap, final List<Table> tablesConfigured,
            final boolean gatherUpdates) {
        LOG.trace("resolving flows in tables for {}", nodeId.getValue());
        final Map<TableKey, ItemSyncBox<Flow>> tableFlowSyncBoxes = new HashMap<>();
        for (final Table tableConfigured : tablesConfigured) {
            final List<Flow> flowsConfigured = tableConfigured.getFlow();
            if (flowsConfigured == null || flowsConfigured.isEmpty()) {
                continue;
            }

            // lookup table (on device)
            final Table tableOperational = tableOperationalMap.get(tableConfigured.getId());
            // wrap existing (on device) flows in current table into map
            final Map<FlowDescriptor, Flow> flowOperationalMap = FlowCapableNodeLookups
                    .wrapFlowsToMap(tableOperational != null ? tableOperational.getFlow() : null);

            final ItemSyncBox<Flow> flowsSyncBox = resolveFlowDiffsInTable(flowsConfigured, flowOperationalMap,
                    gatherUpdates);
            if (!flowsSyncBox.isEmpty()) {
                tableFlowSyncBoxes.put(tableConfigured.getKey(), flowsSyncBox);
            }
        }
        return tableFlowSyncBoxes;
    }

    public static List<Group> safeGroups(FlowCapableNode node) {
        if (node == null) {
            return Collections.emptyList();
        }

        return MoreObjects.firstNonNull(node.getGroup(), ImmutableList.<Group>of());
    }

    public static List<Table> safeTables(FlowCapableNode node) {
        if (node == null) {
            return Collections.emptyList();
        }

        return MoreObjects.firstNonNull(node.getTable(), ImmutableList.<Table>of());
    }

    public static List<Meter> safeMeters(FlowCapableNode node) {
        if (node == null) {
            return Collections.emptyList();
        }

        return MoreObjects.firstNonNull(node.getMeter(), ImmutableList.<Meter>of());
    }
}