/**
* 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
*/
private 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());
}
}