Carmelo Cascone
Committed by Gerrit Code Review

Added BMv2 demo apps

Change-Id: Id93f6b4f407ddd224fde8e31d79c9426633b4d75
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-app-bmv2-demo</artifactId>
<groupId>org.onosproject</groupId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-bmv2-demo-common</artifactId>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-bmv2-protocol-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.common;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.app.ApplicationAdminService;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyEvent;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.net.topology.TopologyListener;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.net.topology.TopologyVertex;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.Stream.concat;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.device.DeviceEvent.Type.*;
import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Abstract implementation of an app providing fabric connectivity for a 2-stage Clos topology of BMv2 devices.
*/
@Component(immediate = true)
public abstract class AbstractUpgradableFabricApp {
private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
private static final int NUM_LEAFS = 3;
private static final int NUM_SPINES = 3;
private static final int FLOW_PRIORITY = 100;
private static final int CLEANUP_SLEEP = 1000;
protected final Logger log = getLogger(getClass());
private final TopologyListener topologyListener = new InternalTopologyListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private final HostListener hostListener = new InternalHostListener();
private final ExecutorService executorService = Executors
.newFixedThreadPool(8, groupedThreads("onos/bmv2-demo-app", "bmv2-app-task", log));
private final String appName;
private final String configurationName;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ApplicationAdminService appService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private Bmv2DeviceContextService bmv2ContextService;
private boolean appActive = false;
private boolean appFreezed = false;
private boolean otherAppFound = false;
private AbstractUpgradableFabricApp otherApp;
private boolean flowRuleGenerated = false;
private ApplicationId appId;
private Bmv2DeviceContext bmv2Context;
private Set<DeviceId> leafSwitches;
private Set<DeviceId> spineSwitches;
private Map<DeviceId, List<FlowRule>> deviceFlowRules;
private Map<DeviceId, Boolean> rulesInstalled;
/**
* Creates a new Bmv2 Fabric Component.
*
* @param appName app name
* @param configurationName a common name for the P4 program / BMv2 configuration used by this app
* @param context a BMv2 device context to be used on devices
*/
protected AbstractUpgradableFabricApp(String appName, String configurationName, Bmv2DeviceContext context) {
this.appName = checkNotNull(appName);
this.configurationName = checkNotNull(configurationName);
this.bmv2Context = checkNotNull(context);
}
@Activate
public void activate() {
log.info("Starting...");
appActive = true;
appFreezed = false;
if (APP_HANDLES.size() > 0) {
if (APP_HANDLES.size() > 1) {
throw new IllegalStateException("Found more than 1 active app handles");
}
otherAppFound = true;
otherApp = APP_HANDLES.values().iterator().next();
log.info("Found other fabric app active, signaling to freeze to {}...", otherApp.appName);
otherApp.setAppFreezed(true);
}
APP_HANDLES.put(appName, this);
appId = coreService.registerApplication(appName);
topologyService.addListener(topologyListener);
deviceService.addListener(deviceListener);
hostService.addListener(hostListener);
bmv2ContextService.registerInterpreterClassLoader(bmv2Context.interpreter().getClass(),
this.getClass().getClassLoader());
init();
log.info("STARTED", appId.id());
}
@Deactivate
public void deactivate() {
log.info("Stopping...");
try {
executorService.shutdown();
executorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
List<Runnable> runningTasks = executorService.shutdownNow();
log.warn("Unable to stop the following tasks: {}", runningTasks);
}
deviceService.removeListener(deviceListener);
topologyService.removeListener(topologyListener);
hostService.removeListener(hostListener);
flowRuleService.removeFlowRulesById(appId);
appActive = false;
APP_HANDLES.remove(appName);
log.info("STOPPED");
}
private void init() {
// Reset any previous state
synchronized (this) {
flowRuleGenerated = Boolean.FALSE;
leafSwitches = Sets.newHashSet();
spineSwitches = Sets.newHashSet();
deviceFlowRules = Maps.newConcurrentMap();
rulesInstalled = Maps.newConcurrentMap();
}
// Start flow rules generator...
spawnTask(() -> generateFlowRules(topologyService.currentTopology(), Sets.newHashSet(hostService.getHosts())));
}
private void setAppFreezed(boolean appFreezed) {
this.appFreezed = appFreezed;
if (appFreezed) {
log.info("Freezing...");
} else {
log.info("Unfreezing...!");
}
}
/**
* Perform device initialization. Returns true if the operation was successful, false otherwise.
*
* @param deviceId a device id
* @return a boolean value
*/
public abstract boolean initDevice(DeviceId deviceId);
/**
* Generates a list of flow rules for the given leaf switch, source host, destination hosts, spine switches and
* topology.
*
* @param leaf a leaf device id
* @param srcHost a source host
* @param dstHosts a collection of destination hosts
* @param spines a collection of spine device IDs
* @param topology a topology
* @return a list of flow rules
* @throws FlowRuleGeneratorException if flow rules cannot be generated
*/
public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
Collection<DeviceId> spines, Topology topology)
throws FlowRuleGeneratorException;
/**
* Generates a list of flow rules for the given spine switch, destination hosts and topology.
*
* @param deviceId a spine device id
* @param dstHosts a collection of destination hosts
* @param topology a topology
* @return a list of flow rules
* @throws FlowRuleGeneratorException if flow rules cannot be generated
*/
public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
throws FlowRuleGeneratorException;
private void deployRoutine() {
if (otherAppFound && otherApp.appActive) {
log.info("Starting update routine...");
updateRoutine();
appService.deactivate(otherApp.appId);
} else {
Stream.concat(leafSwitches.stream(), spineSwitches.stream())
.map(deviceService::getDevice)
.forEach(device -> spawnTask(() -> deployDevice(device)));
}
}
private void updateRoutine() {
Stream.concat(leafSwitches.stream(), spineSwitches.stream())
.forEach(did -> spawnTask(() -> {
cleanUpDevice(did);
try {
Thread.sleep(CLEANUP_SLEEP);
} catch (InterruptedException e) {
log.warn("Cleanup sleep interrupted!");
Thread.interrupted();
}
deployDevice(deviceService.getDevice(did));
}));
}
private void cleanUpDevice(DeviceId deviceId) {
List<FlowRule> flowRulesToRemove = Lists.newArrayList();
flowRuleService.getFlowEntries(deviceId).forEach(fe -> {
if (fe.appId() == otherApp.appId.id()) {
flowRulesToRemove.add(fe);
}
});
if (flowRulesToRemove.size() > 0) {
log.info("Cleaning {} old flow rules from {}...", flowRulesToRemove.size(), deviceId);
removeFlowRules(flowRulesToRemove);
}
}
/**
* Executes a device deploy.
*
* @param device a device
*/
public void deployDevice(Device device) {
// Serialize executions per device ID using a concurrent map.
rulesInstalled.compute(device.id(), (did, deployed) -> {
Bmv2DeviceContext deviceContext = bmv2ContextService.getContext(device.id());
if (deviceContext == null) {
log.error("Unable to get context for device {}", device.id());
return deployed;
} else if (!deviceContext.equals(bmv2Context)) {
log.info("Swapping configuration to {} on device {}...", configurationName, device.id());
bmv2ContextService.triggerConfigurationSwap(device.id(), bmv2Context);
return deployed;
}
List<FlowRule> rules = deviceFlowRules.get(device.id());
if (initDevice(device.id())) {
if (deployed == null && rules != null && rules.size() > 0) {
log.info("Installing rules for {}...", did);
installFlowRules(rules);
return true;
}
} else {
log.warn("Filed to initialize device {}", device.id());
if (deployed != null && rules != null && rules.size() > 0) {
log.info("Removing rules for {}...", did);
removeFlowRules(rules);
return null;
}
}
return deployed;
});
}
private void spawnTask(Runnable task) {
executorService.execute(task);
}
private void installFlowRules(Collection<FlowRule> rules) {
FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
rules.forEach(opsBuilder::add);
flowRuleService.apply(opsBuilder.build());
}
private void removeFlowRules(Collection<FlowRule> rules) {
FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
rules.forEach(opsBuilder::remove);
flowRuleService.apply(opsBuilder.build());
}
/**
* Generates the flow rules to provide host-to-host connectivity for the given topology and hosts.
*
* @param topo a topology
* @param hosts a collection of hosts
*/
private synchronized void generateFlowRules(Topology topo, Collection<Host> hosts) {
if (flowRuleGenerated) {
log.debug("Flow rules have been already generated, aborting...");
return;
}
log.debug("Starting flow rules generator...");
TopologyGraph graph = topologyService.getGraph(topo);
Set<DeviceId> spines = Sets.newHashSet();
Set<DeviceId> leafs = Sets.newHashSet();
graph.getVertexes().stream()
.map(TopologyVertex::deviceId)
.forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
if (spines.size() != NUM_SPINES || leafs.size() != NUM_LEAFS) {
log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
spines.size(), leafs.size());
return;
}
for (DeviceId did : spines) {
int portCount = deviceService.getPorts(did).size();
// Expected port count: num leafs + 1 redundant leaf link
if (portCount != (NUM_LEAFS + 1)) {
log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
return;
}
}
for (DeviceId did : leafs) {
int portCount = deviceService.getPorts(did).size();
// Expected port count: num spines + host port + 1 redundant spine link
if (portCount != (NUM_SPINES + 2)) {
log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
return;
}
}
// Check hosts, number and exactly one per leaf
Map<DeviceId, Host> hostMap = Maps.newHashMap();
hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
if (hosts.size() != NUM_LEAFS || !leafs.equals(hostMap.keySet())) {
log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
return;
}
List<FlowRule> newFlowRules = Lists.newArrayList();
try {
for (DeviceId deviceId : leafs) {
Host srcHost = hostMap.get(deviceId);
Set<Host> dstHosts = hosts.stream().filter(h -> h != srcHost).collect(toSet());
newFlowRules.addAll(generateLeafRules(deviceId, srcHost, dstHosts, spines, topo));
}
for (DeviceId deviceId : spines) {
newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
}
} catch (FlowRuleGeneratorException e) {
log.warn("Exception while executing flow rule generator: ", e.toString());
return;
}
if (newFlowRules.size() == 0) {
// Something went wrong
log.error("0 flow rules generated, BUG?");
return;
}
// All good!
// Divide flow rules per device id...
ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder = ImmutableMap.builder();
concat(spines.stream(), leafs.stream())
.map(deviceId -> ImmutableList.copyOf(newFlowRules
.stream()
.filter(fr -> fr.deviceId().equals(deviceId))
.iterator()))
.forEach(frs -> mapBuilder.put(frs.get(0).deviceId(), frs));
this.deviceFlowRules = mapBuilder.build();
this.leafSwitches = ImmutableSet.copyOf(leafs);
this.spineSwitches = ImmutableSet.copyOf(spines);
// Avoid other executions to modify the generated flow rules.
flowRuleGenerated = true;
log.info("DONE! Generated {} flow rules for {} devices...", newFlowRules.size(), spines.size() + leafs.size());
// Deploy configuration.
spawnTask(this::deployRoutine);
}
/**
* Returns a new, pre-configured flow rule builder.
*
* @param did a device id
* @param tableName a table name
* @return a new flow rule builder
*/
protected FlowRule.Builder flowRuleBuilder(DeviceId did, String tableName) throws FlowRuleGeneratorException {
Map<String, Integer> tableMap = bmv2Context.interpreter().tableIdMap().inverse();
if (tableMap.get(tableName) == null) {
throw new FlowRuleGeneratorException("Unknown table " + tableName);
}
return DefaultFlowRule.builder()
.forDevice(did)
.forTable(tableMap.get(tableName))
.fromApp(appId)
.withPriority(FLOW_PRIORITY)
.makePermanent();
}
private List<Port> getHostPorts(DeviceId deviceId, Topology topology) {
// Get all non-fabric ports.
return deviceService
.getPorts(deviceId)
.stream()
.filter(p -> !isFabricPort(p, topology))
.collect(Collectors.toList());
}
private boolean isSpine(DeviceId deviceId, Topology topology) {
// True if all ports are fabric.
return getHostPorts(deviceId, topology).size() == 0;
}
protected boolean isFabricPort(Port port, Topology topology) {
// True if the port connects this device to another infrastructure device.
return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
}
/**
* A listener of topology events that executes a flow rule generation task each time a device is added.
*/
private class InternalTopologyListener implements TopologyListener {
@Override
public void event(TopologyEvent event) {
spawnTask(() -> generateFlowRules(event.subject(), Sets.newHashSet(hostService.getHosts())));
}
@Override
public boolean isRelevant(TopologyEvent event) {
return !appFreezed &&
// If at least one reason is of type DEVICE_ADDED.
event.reasons().stream().
filter(r -> r instanceof DeviceEvent)
.filter(r -> ((DeviceEvent) r).type() == DEVICE_ADDED)
.findAny()
.isPresent();
}
}
/**
* A listener of device events that executes a device deploy task each time a device is added, updated or
* re-connects.
*/
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
spawnTask(() -> deployDevice(event.subject()));
}
@Override
public boolean isRelevant(DeviceEvent event) {
return !appFreezed &&
(event.type() == DEVICE_ADDED ||
event.type() == DEVICE_UPDATED ||
(event.type() == DEVICE_AVAILABILITY_CHANGED &&
deviceService.isAvailable(event.subject().id())));
}
}
/**
* A listener of host events that generates flow rules each time a new host is added.
*/
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
spawnTask(() -> generateFlowRules(topologyService.currentTopology(),
Sets.newHashSet(hostService.getHosts())));
}
@Override
public boolean isRelevant(HostEvent event) {
return !appFreezed && event.type() == HOST_ADDED;
}
}
/**
* An exception occurred while generating flow rules for this fabric.
*/
public class FlowRuleGeneratorException extends Exception {
public FlowRuleGeneratorException() {
}
public FlowRuleGeneratorException(String msg) {
super(msg);
}
public FlowRuleGeneratorException(Exception cause) {
super(cause);
}
}
}
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Bmv2 demo app common classes.
*/
package org.onosproject.bmv2.demo.app.common;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}"
description="${project.description}">
<bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-app-bmv2-demo-common/${project.version}</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-app-bmv2-demo</artifactId>
<groupId>org.onosproject</groupId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-bmv2-demo-ecmp</artifactId>
<packaging>bundle</packaging>
<properties>
<onos.app.name>org.onosproject.bmv2-ecmp-fabric</onos.app.name>
<onos.app.title>P4/BMv2 Demo Fabric App v1 (ECMP)</onos.app.title>
<onos.app.category>Traffic Steering</onos.app.category>
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>P4/BMv2 demo application with ECMP support for a 2-stage clos fabric topology</onos.app.readme>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-bmv2-demo-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.ecmp;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Path;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.topology.DefaultTopologyVertex;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyGraph;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.onlab.packet.EthType.EtherType.IPV4;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpGroupTreatmentBuilder.groupIdOf;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.ECMP_GROUP_TABLE;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.TABLE0;
/**
* Implementation of an upgradable fabric app for the ECMP configuration.
*/
@Component(immediate = true)
public class EcmpFabricApp extends AbstractUpgradableFabricApp {
private static final String APP_NAME = "org.onosproject.bmv2-ecmp-fabric";
private static final String MODEL_NAME = "ECMP";
private static final String JSON_CONFIG_PATH = "/ecmp.json";
private static final Bmv2Configuration ECMP_CONFIGURATION = loadConfiguration();
private static final EcmpInterpreter ECMP_INTERPRETER = new EcmpInterpreter();
protected static final Bmv2DeviceContext ECMP_CONTEXT = new Bmv2DeviceContext(ECMP_CONFIGURATION, ECMP_INTERPRETER);
public EcmpFabricApp() {
super(APP_NAME, MODEL_NAME, ECMP_CONTEXT);
}
@Override
public boolean initDevice(DeviceId deviceId) {
// Nothing to do.
return true;
}
@Override
public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
Collection<DeviceId> availableSpines, Topology topo)
throws FlowRuleGeneratorException {
// Get ports which connect this leaf switch to hosts.
Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
.stream()
.filter(port -> !isFabricPort(port, topo))
.map(Port::number)
.collect(Collectors.toSet());
// Get ports which connect this leaf to the given available spines.
TopologyGraph graph = topologyService.getGraph(topo);
Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
.stream()
.filter(e -> availableSpines.contains(e.dst().deviceId()))
.map(e -> e.link().src().port())
.collect(Collectors.toSet());
if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
hostPorts.size(), fabricPorts.size());
throw new FlowRuleGeneratorException();
}
PortNumber hostPort = hostPorts.iterator().next();
List<FlowRule> rules = Lists.newArrayList();
TrafficTreatment treatment;
if (fabricPorts.size() > 1) {
// Do ECMP.
Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(leaf, fabricPorts);
rules.addAll(result.getRight());
ExtensionTreatment extTreatment = result.getLeft();
treatment = DefaultTrafficTreatment.builder().extension(extTreatment, leaf).build();
} else {
// Output on port.
PortNumber outPort = fabricPorts.iterator().next();
treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
}
// From srHost to dstHosts.
for (Host dstHost : dstHosts) {
FlowRule rule = flowRuleBuilder(leaf, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchInPort(hostPort)
.matchEthType(IPV4.ethType().toShort())
.matchEthSrc(srcHost.mac())
.matchEthDst(dstHost.mac())
.build())
.withTreatment(treatment)
.build();
rules.add(rule);
}
// From fabric ports to this leaf host.
for (PortNumber port : fabricPorts) {
FlowRule rule = flowRuleBuilder(leaf, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchInPort(port)
.matchEthType(IPV4.ethType().toShort())
.matchEthDst(srcHost.mac())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.setOutput(hostPort)
.build())
.build();
rules.add(rule);
}
return rules;
}
@Override
public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
throws FlowRuleGeneratorException {
List<FlowRule> rules = Lists.newArrayList();
// for each host
for (Host dstHost : dstHosts) {
Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
if (paths.size() == 0) {
log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
throw new FlowRuleGeneratorException();
}
TrafficTreatment treatment;
if (paths.size() == 1) {
// Only one path, do output on port.
PortNumber port = paths.iterator().next().src().port();
treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
} else {
// Multiple paths, do ECMP.
Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(deviceId, portNumbers);
rules.addAll(result.getRight());
treatment = DefaultTrafficTreatment.builder().extension(result.getLeft(), deviceId).build();
}
FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchEthType(IPV4.ethType().toShort())
.matchEthDst(dstHost.mac())
.build())
.withTreatment(treatment)
.build();
rules.add(rule);
}
return rules;
}
private Pair<ExtensionTreatment, List<FlowRule>> provisionEcmpTreatment(DeviceId deviceId,
Set<PortNumber> fabricPorts)
throws FlowRuleGeneratorException {
// Install ECMP group table entries that map from hash values to actual fabric ports...
int groupId = groupIdOf(deviceId, fabricPorts);
int groupSize = fabricPorts.size();
Iterator<PortNumber> portIterator = fabricPorts.iterator();
List<FlowRule> rules = Lists.newArrayList();
for (short i = 0; i < groupSize; i++) {
ExtensionSelector extSelector = new EcmpGroupTableSelectorBuilder()
.withGroupId(groupId)
.withSelector(i)
.build();
FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE)
.withSelector(
DefaultTrafficSelector.builder()
.extension(extSelector, deviceId)
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.setOutput(portIterator.next())
.build())
.build();
rules.add(rule);
}
ExtensionTreatment extTreatment = new EcmpGroupTreatmentBuilder()
.withGroupId(groupId)
.withGroupSize(groupSize)
.build();
return Pair.of(extTreatment, rules);
}
private static Bmv2Configuration loadConfiguration() {
try {
JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
EcmpFabricApp.class.getResourceAsStream(JSON_CONFIG_PATH)))).asObject();
return Bmv2DefaultConfiguration.parse(json);
} catch (IOException e) {
throw new RuntimeException("Unable to load configuration", e);
}
}
}
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.ecmp;
import com.google.common.collect.ImmutableMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2HeaderTypeModel;
import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
/**
* Builder of ECMP group table extension selector.
*/
public class EcmpGroupTableSelectorBuilder {
private int groupId;
private int selector;
/**
* Sets the ECMP group ID.
*
* @param groupId an integer value
* @return this
*/
public EcmpGroupTableSelectorBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the ECMP selector.
*
* @param selector an integer value
* @return this
*/
public EcmpGroupTableSelectorBuilder withSelector(int selector) {
this.selector = selector;
return this;
}
/**
* Returns a new extension selector.
*
* @return an extension selector
*/
public ExtensionSelector build() {
Bmv2HeaderTypeModel headerTypeModel = ECMP_CONTEXT.configuration().headerType(ECMP_METADATA_T);
int groupIdBitWidth = headerTypeModel.field(GROUP_ID).bitWidth();
int selectorBitWidth = headerTypeModel.field(SELECTOR).bitWidth();
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId),
groupIdBitWidth);
ImmutableByteSequence selectorBs = fitByteSequence(ImmutableByteSequence.copyFrom(selector),
selectorBitWidth);
Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
Bmv2ExactMatchParam hashMatch = new Bmv2ExactMatchParam(selectorBs);
return new Bmv2ExtensionSelector(ImmutableMap.of(
ECMP_METADATA + "." + GROUP_ID, groupIdMatch,
ECMP_METADATA + "." + SELECTOR, hashMatch));
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.ecmp;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2ActionModel;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import java.util.Map;
import java.util.Set;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
/**
* Builder of ECMP extension treatments.
*/
public class EcmpGroupTreatmentBuilder {
private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
private int groupId;
private int groupSize;
/**
* Sets the group ID.
*
* @param groupId an integer value
* @return this
*/
public EcmpGroupTreatmentBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the group size.
*
* @param groupSize an integer value
* @return this
*/
public EcmpGroupTreatmentBuilder withGroupSize(int groupSize) {
this.groupSize = groupSize;
return this;
}
/**
* Returns a new extension treatment.
*
* @return an extension treatment
*/
public ExtensionTreatment build() {
Bmv2ActionModel actionModel = ECMP_CONTEXT.configuration().action(ECMP_GROUP);
int groupIdBitWidth = actionModel.runtimeData(GROUP_ID).bitWidth();
int groupSizeBitWidth = actionModel.runtimeData(GROUP_SIZE).bitWidth();
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
ImmutableByteSequence groupSizeBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupSize),
groupSizeBitWidth);
return new Bmv2ExtensionTreatment(Bmv2Action.builder()
.withName(ECMP_GROUP)
.addParameter(groupIdBs)
.addParameter(groupSizeBs)
.build());
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
/**
* Returns a group ID for the given device and set of ports.
*
* @param deviceId a device ID
* @param ports a set of ports
* @return an integer value
*/
public static int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
// Counts the number of unique portNumber sets for each deviceId.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
(short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.ecmp;
import com.google.common.collect.ImmutableBiMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2Interpreter;
import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.instructions.Instruction;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.net.PortNumber.CONTROLLER;
import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
/**
* Implementation of a BMv2 interpreter for the ecmp.json configuration.
*/
public class EcmpInterpreter implements Bmv2Interpreter {
protected static final String ECMP_METADATA_T = "ecmp_metadata_t";
protected static final String ECMP_METADATA = "ecmp_metadata";
protected static final String SELECTOR = "selector";
protected static final String GROUP_ID = "groupId";
protected static final String GROUP_SIZE = "groupSize";
protected static final String ECMP_GROUP = "ecmp_group";
protected static final String ECMP_GROUP_TABLE = "ecmp_group_table";
protected static final String TABLE0 = "table0";
protected static final String SEND_TO_CPU = "send_to_cpu";
protected static final String DROP = "_drop";
protected static final String SET_EGRESS_PORT = "set_egress_port";
protected static final String PORT = "port";
private static final ImmutableBiMap<Criterion.Type, String> CRITERION_TYPE_MAP = ImmutableBiMap.of(
Criterion.Type.IN_PORT, "standard_metadata.ingress_port",
Criterion.Type.ETH_DST, "ethernet.dstAddr",
Criterion.Type.ETH_SRC, "ethernet.srcAddr",
Criterion.Type.ETH_TYPE, "ethernet.etherType");
private static final ImmutableBiMap<Integer, String> TABLE_ID_MAP = ImmutableBiMap.of(
0, TABLE0,
1, ECMP_GROUP_TABLE);
@Override
public ImmutableBiMap<Integer, String> tableIdMap() {
return TABLE_ID_MAP;
}
@Override
public ImmutableBiMap<Criterion.Type, String> criterionTypeMap() {
return CRITERION_TYPE_MAP;
}
@Override
public Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
throws Bmv2InterpreterException {
if (treatment.allInstructions().size() == 0) {
// No instructions means drop for us.
return actionWithName(DROP);
} else if (treatment.allInstructions().size() > 1) {
// Otherwise, we understand treatments with only 1 instruction.
throw new Bmv2InterpreterException("Treatment has multiple instructions");
}
Instruction instruction = treatment.allInstructions().get(0);
switch (instruction.type()) {
case OUTPUT:
OutputInstruction outInstruction = (OutputInstruction) instruction;
PortNumber port = outInstruction.port();
if (!port.isLogical()) {
return buildEgressAction(port, configuration);
} else if (port.equals(CONTROLLER)) {
return actionWithName(SEND_TO_CPU);
} else {
throw new Bmv2InterpreterException("Egress on logical port not supported: " + port);
}
case NOACTION:
return actionWithName(DROP);
default:
throw new Bmv2InterpreterException("Instruction type not supported: " + instruction.type().name());
}
}
private static Bmv2Action buildEgressAction(PortNumber port, Bmv2Configuration configuration)
throws Bmv2InterpreterException {
int portBitWidth = configuration.action(SET_EGRESS_PORT).runtimeData(PORT).bitWidth();
try {
ImmutableByteSequence portBs = fitByteSequence(ImmutableByteSequence.copyFrom(port.toLong()), portBitWidth);
return Bmv2Action.builder()
.withName(SET_EGRESS_PORT)
.addParameter(portBs)
.build();
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new Bmv2InterpreterException(e.getMessage());
}
}
private static Bmv2Action actionWithName(String name) {
return Bmv2Action.builder().withName(name).build();
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* BMv2 demo app for the ECMP configuration.
*/
package org.onosproject.bmv2.demo.app.ecmp;
\ No newline at end of file
/Users/carmelo/workspace/onos-p4-dev/p4src/build/ecmp.json
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2014-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-bmv2-demo</artifactId>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>ecmp</module>
<module>wcmp</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}"
description="${project.description}">
<bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-app-bmv2-demo-common/${project.version}</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-app-bmv2-demo</artifactId>
<groupId>org.onosproject</groupId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-bmv2-demo-wcmp</artifactId>
<packaging>bundle</packaging>
<properties>
<onos.app.name>org.onosproject.bmv2-wcmp-fabric</onos.app.name>
<onos.app.title>P4/BMv2 Demo Fabric App v2 (WCMP)</onos.app.title>
<onos.app.category>Traffic Steering</onos.app.category>
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>P4/BMv2 demo application with WCMP support for a 2-stage clos fabric topology</onos.app.readme>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-bmv2-demo-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.wcmp;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
import org.onosproject.bmv2.api.service.Bmv2Controller;
import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Path;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.topology.DefaultTopologyVertex;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyGraph;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.onlab.packet.EthType.EtherType.IPV4;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.groupIdOf;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.toPrefixLengths;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.TABLE0;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.WCMP_GROUP_TABLE;
/**
* Implementation of an upgradable fabric app for the WCMP configuration.
*/
@Component(immediate = true)
public class WcmpFabricApp extends AbstractUpgradableFabricApp {
private static final String APP_NAME = "org.onosproject.bmv2-wcmp-fabric";
private static final String MODEL_NAME = "WCMP";
private static final String JSON_CONFIG_PATH = "/wcmp.json";
private static final double MULTI_PORT_WEIGHT_COEFFICIENT = 0.85;
private static final Bmv2Configuration WCMP_CONFIGURATION = loadConfiguration();
private static final WcmpInterpreter WCMP_INTERPRETER = new WcmpInterpreter();
protected static final Bmv2DeviceContext WCMP_CONTEXT = new Bmv2DeviceContext(WCMP_CONFIGURATION, WCMP_INTERPRETER);
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private Bmv2Controller bmv2Controller;
/**
* TODO.
*/
public WcmpFabricApp() {
super(APP_NAME, MODEL_NAME, WCMP_CONTEXT);
}
@Override
public boolean initDevice(DeviceId deviceId) {
try {
Bmv2DeviceAgent agent = bmv2Controller.getAgent(deviceId);
for (Map.Entry<String, Bmv2Action> entry : WCMP_INTERPRETER.defaultActionsMap().entrySet()) {
agent.setTableDefaultAction(entry.getKey(), entry.getValue());
}
return true;
} catch (Bmv2RuntimeException e) {
log.error("Unable to init device {}: {}", deviceId, e.explain());
return false;
}
}
@Override
public List<FlowRule> generateLeafRules(DeviceId deviceId, Host srcHost, Collection<Host> dstHosts,
Collection<DeviceId> availableSpines, Topology topo)
throws FlowRuleGeneratorException {
Set<PortNumber> hostPortNumbers = Sets.newHashSet();
Set<PortNumber> fabricPortNumbers = Sets.newHashSet();
deviceService.getPorts(deviceId)
.forEach(p -> (isFabricPort(p, topo) ? fabricPortNumbers : hostPortNumbers).add(p.number()));
if (hostPortNumbers.size() != 1 || fabricPortNumbers.size() == 0) {
log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
hostPortNumbers.size(), fabricPortNumbers.size());
throw new FlowRuleGeneratorException();
}
PortNumber hostPort = hostPortNumbers.iterator().next();
TopologyGraph graph = topologyService.getGraph(topo);
// Map key: spine device id, value: leaf switch ports which connect to spine in the key.
Map<DeviceId, Set<PortNumber>> spineToPortsMap = Maps.newHashMap();
graph.getEdgesFrom(new DefaultTopologyVertex(deviceId)).forEach(edge -> {
spineToPortsMap.putIfAbsent(edge.dst().deviceId(), Sets.newHashSet());
spineToPortsMap.get(edge.dst().deviceId()).add(edge.link().src().port());
});
double baseWeight = 1d / spineToPortsMap.size();
int numSinglePorts = (int) spineToPortsMap.values().stream().filter(s -> s.size() == 1).count();
int numMultiPorts = spineToPortsMap.size() - numSinglePorts;
// Reduce weight portion assigned to multi-ports to mitigate flow assignment imbalance (measured empirically).
double multiPortBaseWeight = baseWeight * MULTI_PORT_WEIGHT_COEFFICIENT;
double excess = (baseWeight - multiPortBaseWeight) * numMultiPorts;
double singlePortBaseWeight = baseWeight + (excess / numSinglePorts);
Map<PortNumber, Double> weighedPortNumbers = Maps.newHashMap();
spineToPortsMap.forEach((did, portSet) -> {
double base = (portSet.size() == 1) ? singlePortBaseWeight : multiPortBaseWeight;
double weight = base / portSet.size();
portSet.forEach(portNumber -> weighedPortNumbers.put(portNumber, weight));
});
List<FlowRule> rules = Lists.newArrayList();
Pair<ExtensionTreatment, List<FlowRule>> result = provisionWcmpTreatment(deviceId, weighedPortNumbers);
ExtensionTreatment wcmpTreatment = result.getLeft();
rules.addAll(result.getRight());
// From src host to dst hosts, WCMP to all fabric ports.
for (Host dstHost : dstHosts) {
FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchInPort(hostPort)
.matchEthType(IPV4.ethType().toShort())
.matchEthSrc(srcHost.mac())
.matchEthDst(dstHost.mac())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.extension(wcmpTreatment, deviceId)
.build())
.build();
rules.add(rule);
}
// From fabric ports to src host.
for (PortNumber port : fabricPortNumbers) {
FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchInPort(port)
.matchEthType(IPV4.ethType().toShort())
.matchEthDst(srcHost.mac())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.setOutput(hostPort)
.build())
.build();
rules.add(rule);
}
return rules;
}
@Override
public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
throws FlowRuleGeneratorException {
List<FlowRule> rules = Lists.newArrayList();
for (Host dstHost : dstHosts) {
Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
if (paths.size() == 0) {
log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
throw new FlowRuleGeneratorException();
}
TrafficTreatment treatment;
if (paths.size() == 1) {
// Only one path.
PortNumber port = paths.iterator().next().src().port();
treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
} else {
// Multiple paths, do WCMP.
Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
double weight = 1d / portNumbers.size();
// Same weight for all ports.
Map<PortNumber, Double> weightedPortNumbers = portNumbers.stream()
.collect(Collectors.toMap(p -> p, p -> weight));
Pair<ExtensionTreatment, List<FlowRule>> result = provisionWcmpTreatment(deviceId, weightedPortNumbers);
rules.addAll(result.getRight());
treatment = DefaultTrafficTreatment.builder().extension(result.getLeft(), deviceId).build();
}
FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
.withSelector(
DefaultTrafficSelector.builder()
.matchEthType(IPV4.ethType().toShort())
.matchEthDst(dstHost.mac())
.build())
.withTreatment(treatment)
.build();
rules.add(rule);
}
return rules;
}
private Pair<ExtensionTreatment, List<FlowRule>> provisionWcmpTreatment(DeviceId deviceId,
Map<PortNumber, Double> weightedFabricPorts)
throws FlowRuleGeneratorException {
// Install WCMP group table entries that map from hash values to fabric ports.
int groupId = groupIdOf(deviceId, weightedFabricPorts);
List<PortNumber> portNumbers = Lists.newArrayList();
List<Double> weights = Lists.newArrayList();
weightedFabricPorts.forEach((p, w) -> {
portNumbers.add(p);
weights.add(w);
});
List<Integer> prefixLengths;
try {
prefixLengths = toPrefixLengths(weights);
} catch (WcmpGroupTreatmentBuilder.WcmpGroupException e) {
throw new FlowRuleGeneratorException(e);
}
List<FlowRule> rules = Lists.newArrayList();
for (int i = 0; i < portNumbers.size(); i++) {
ExtensionSelector extSelector = new WcmpGroupTableSelectorBuilder()
.withGroupId(groupId)
.withPrefixLength(prefixLengths.get(i))
.build();
FlowRule rule = flowRuleBuilder(deviceId, WCMP_GROUP_TABLE)
.withSelector(DefaultTrafficSelector.builder()
.extension(extSelector, deviceId)
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.setOutput(portNumbers.get(i))
.build())
.build();
rules.add(rule);
}
ExtensionTreatment extTreatment = new WcmpGroupTreatmentBuilder().withGroupId(groupId).build();
return Pair.of(extTreatment, rules);
}
private static Bmv2Configuration loadConfiguration() {
try {
JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
WcmpFabricApp.class.getResourceAsStream(JSON_CONFIG_PATH)))).asObject();
return Bmv2DefaultConfiguration.parse(json);
} catch (IOException e) {
throw new RuntimeException("Unable to load configuration", e);
}
}
}
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.wcmp;
import com.google.common.collect.ImmutableMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import static com.google.common.base.Preconditions.checkArgument;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
/**
* Builder of WCMP group table extension selector.
*/
public final class WcmpGroupTableSelectorBuilder {
private int groupId;
private int prefixLength;
/**
* Sets the WCMP group ID.
*
* @param groupId an integer value
* @return this
*/
public WcmpGroupTableSelectorBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the WCMP selector's prefix length.
*
* @param prefixLength an integer value
* @return this
*/
public WcmpGroupTableSelectorBuilder withPrefixLength(int prefixLength) {
this.prefixLength = prefixLength;
return this;
}
/**
* Returns a new extension selector.
*
* @return an extension selector
*/
public ExtensionSelector build() {
final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
final ImmutableByteSequence ones = ImmutableByteSequence.ofOnes(roundToBytes(selectorBitWidth));
checkArgument(prefixLength >= 1 && prefixLength <= selectorBitWidth,
"prefix length must be between 1 and " + selectorBitWidth);
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
Bmv2LpmMatchParam selectorMatch = new Bmv2LpmMatchParam(ones, prefixLength);
return new Bmv2ExtensionSelector(ImmutableMap.of(
WCMP_META + "." + GROUP_ID, groupIdMatch,
WCMP_META + "." + SELECTOR, selectorMatch));
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.wcmp;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toList;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
/**
* Builder of WCMP extension treatment.
*/
public final class WcmpGroupTreatmentBuilder {
private static final double MAX_ERROR = 0.0001;
private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
private int groupId;
/**
* Sets the WCMP group ID.
*
* @param groupId an integer value
* @return this
*/
public WcmpGroupTreatmentBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Returns a new extension treatment.
*
* @return an extension treatment
*/
public ExtensionTreatment build() {
checkArgument(groupId >= 0, "group id must be a non-zero positive integer");
ImmutableByteSequence groupIdBs = ImmutableByteSequence.copyFrom(groupId);
final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
try {
groupIdBs = fitByteSequence(groupIdBs, groupIdBitWidth);
return new Bmv2ExtensionTreatment(
Bmv2Action.builder()
.withName(WCMP_GROUP)
.addParameter(groupIdBs)
.build());
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
public static int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
// Counts the number of unique portNumber sets for each device ID.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
(pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
}
public static List<Integer> toPrefixLengths(List<Double> weigths) throws WcmpGroupException {
double weightSum = weigths.stream()
.mapToDouble(Double::doubleValue)
.map(WcmpGroupTreatmentBuilder::roundDouble)
.sum();
if (Math.abs(weightSum - 1) > MAX_ERROR) {
throw new WcmpGroupException("weights sum is expected to be 1, found was " + weightSum);
}
final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
final int availableBits = selectorBitWidth - 1;
List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
final long error = availableBits - bitSum;
if (error != 0) {
// Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
Long maxDiff = Collections.max(prefixDiffs);
int idx = prefixDiffs.indexOf(maxDiff);
prefixDiffs.remove(idx);
prefixDiffs.add(idx, maxDiff + error);
}
List<Integer> prefixLengths = Lists.newArrayList();
int prefix = 1;
for (Long p : prefixDiffs) {
prefixLengths.add(prefix);
prefix += p;
}
return ImmutableList.copyOf(prefixLengths);
}
private static double roundDouble(double n) {
// 5 digits precision.
return (double) Math.round(n * 100000d) / 100000d;
}
public static class WcmpGroupException extends Exception {
public WcmpGroupException(String s) {
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.bmv2.demo.app.wcmp;
import com.google.common.collect.ImmutableBiMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2Interpreter;
import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import java.util.Map;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.net.PortNumber.CONTROLLER;
/**
* Implementation of a BMv2 interpreter for the wcmp.json configuration.
*/
public final class WcmpInterpreter implements Bmv2Interpreter {
protected static final String WCMP_META_T = "wcmp_meta_t";
protected static final String WCMP_META = "wcmp_meta";
protected static final String SELECTOR = "selector";
protected static final String GROUP_ID = "groupId";
protected static final String WCMP_GROUP = "wcmp_group";
protected static final String WCMP_SET_SELECTOR = "wcmp_set_selector";
protected static final String WCMP_SET_SELECTOR_TABLE = "wcmp_set_selector_table";
protected static final String WCMP_GROUP_TABLE = "wcmp_group_table";
protected static final String TABLE0 = "table0";
protected static final String SEND_TO_CPU = "send_to_cpu";
protected static final String DROP = "_drop";
protected static final String SET_EGRESS_PORT = "set_egress_port";
protected static final String PORT = "port";
private static final ImmutableBiMap<Criterion.Type, String> CRITERION_TYPE_MAP = ImmutableBiMap.of(
Criterion.Type.IN_PORT, "standard_metadata.ingress_port",
Criterion.Type.ETH_DST, "ethernet.dstAddr",
Criterion.Type.ETH_SRC, "ethernet.srcAddr",
Criterion.Type.ETH_TYPE, "ethernet.etherType");
private static final ImmutableBiMap<Integer, String> TABLE_ID_MAP = ImmutableBiMap.of(
0, TABLE0,
1, WCMP_GROUP_TABLE);
private static final Map<String, Bmv2Action> DEFAULT_ACTIONS_MAP = ImmutableBiMap.of(
WCMP_SET_SELECTOR_TABLE, actionWithName(WCMP_SET_SELECTOR));
@Override
public ImmutableBiMap<Integer, String> tableIdMap() {
return TABLE_ID_MAP;
}
@Override
public ImmutableBiMap<Criterion.Type, String> criterionTypeMap() {
return CRITERION_TYPE_MAP;
}
public Map<String, Bmv2Action> defaultActionsMap() {
return DEFAULT_ACTIONS_MAP;
}
@Override
public Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
throws Bmv2InterpreterException {
if (treatment.allInstructions().size() == 0) {
// No instructions means drop for us.
return actionWithName(DROP);
} else if (treatment.allInstructions().size() > 1) {
// Otherwise, we understand treatments with only 1 instruction.
throw new Bmv2InterpreterException("Treatment has multiple instructions");
}
Instruction instruction = treatment.allInstructions().get(0);
switch (instruction.type()) {
case OUTPUT:
OutputInstruction outInstruction = (OutputInstruction) instruction;
PortNumber port = outInstruction.port();
if (!port.isLogical()) {
return buildEgressAction(port, configuration);
} else if (port.equals(CONTROLLER)) {
return actionWithName(SEND_TO_CPU);
} else {
throw new Bmv2InterpreterException("Egress on logical port not supported: " + port);
}
case NOACTION:
return actionWithName(DROP);
default:
throw new Bmv2InterpreterException("Instruction type not supported: " + instruction.type().name());
}
}
private static Bmv2Action buildEgressAction(PortNumber port, Bmv2Configuration configuration)
throws Bmv2InterpreterException {
int portBitWidth = configuration.action(SET_EGRESS_PORT).runtimeData(PORT).bitWidth();
try {
ImmutableByteSequence portBs = fitByteSequence(ImmutableByteSequence.copyFrom(port.toLong()), portBitWidth);
return Bmv2Action.builder()
.withName(SET_EGRESS_PORT)
.addParameter(portBs)
.build();
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new Bmv2InterpreterException(e.getMessage());
}
}
private static Bmv2Action actionWithName(String name) {
return Bmv2Action.builder().withName(name).build();
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* BMv2 demo app for the WCMP configuration.
*/
package org.onosproject.bmv2.demo.app.wcmp;
\ No newline at end of file
/Users/carmelo/workspace/onos-p4-dev/p4src/build/wcmp.json
\ No newline at end of file
......@@ -70,6 +70,7 @@
<module>graphitemetrics</module>
<module>xosclient</module>
<module>scalablegateway</module>
<module>bmv2-demo</module>
</modules>
......