Refactored PI-ECMP app to use action profiles of basic.p4
Also removed obsolete ecmp.p4-related code.
Change-Id: Idaca90becfff5fc312de2530bf7924ccd502e076
diff --git a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
index 5a40df4..bb15afc 100644
--- a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
@@ -18,9 +18,12 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
-import org.onlab.util.ImmutableByteSequence;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Path;
@@ -29,12 +32,17 @@
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiGroupKey;
import org.onosproject.net.pi.runtime.PiTableAction;
import org.onosproject.net.topology.DefaultTopologyVertex;
import org.onosproject.net.topology.Topology;
@@ -42,37 +50,44 @@
import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
import org.onosproject.pipelines.basic.PipeconfLoader;
-import java.util.Collection;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-import static java.lang.String.format;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toSet;
-import static org.onlab.packet.EthType.EtherType.IPV4;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_NEXT_HOP_ID;
import static org.onosproject.pipelines.basic.BasicConstants.ACT_SET_NEXT_HOP_ID;
import static org.onosproject.pipelines.basic.BasicConstants.HDR_NEXT_HOP_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_SELECTOR_ID;
import static org.onosproject.pipelines.basic.BasicConstants.TBL_TABLE0_ID;
-import static org.onosproject.pipelines.basic.EcmpConstants.TBL_ECMP_TABLE_ID;
-
+import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
/**
- * Implementation of an upgradable fabric app for the ECMP pipeconf.
+ * Implementation of an upgradable fabric app for the Basic pipeconf (basic.p4)
+ * with ECMP support.
*/
@Component(immediate = true)
public class EcmpFabricApp extends AbstractUpgradableFabricApp {
private static final String APP_NAME = "org.onosproject.pi-ecmp";
- private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+ private static final Map<DeviceId, Map<Set<PortNumber>, Short>>
+ DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+
+ private final Set<Pair<DeviceId, GroupKey>> groupKeys = Sets.newHashSet();
public EcmpFabricApp() {
- super(APP_NAME, singleton(PipeconfLoader.ECMP_PIPECONF));
+ super(APP_NAME, singleton(PipeconfLoader.BASIC_PIPECONF));
+ }
+
+ @Deactivate
+ public void deactivate() {
+ groupKeys.forEach(pair -> groupService.removeGroup(
+ pair.getLeft(), pair.getRight(), appId));
+ super.deactivate();
}
@Override
@@ -82,8 +97,10 @@
}
@Override
- public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
- Collection<DeviceId> availableSpines, Topology topo)
+ public List<FlowRule> generateLeafRules(DeviceId leaf, Host localHost,
+ Set<Host> remoteHosts,
+ Set<DeviceId> availableSpines,
+ Topology topo)
throws FlowRuleGeneratorException {
// Get ports which connect this leaf switch to hosts.
@@ -91,18 +108,19 @@
.stream()
.filter(port -> !isFabricPort(port, topo))
.map(Port::number)
- .collect(Collectors.toSet());
+ .collect(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))
+ 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());
+ .collect(toSet());
if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
- log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
+ log.error("Leaf has invalid port configuration: hostPorts={}, fabricPorts={}",
hostPorts.size(), fabricPorts.size());
throw new FlowRuleGeneratorException();
}
@@ -110,41 +128,40 @@
List<FlowRule> rules = Lists.newArrayList();
- TrafficTreatment treatment;
- if (fabricPorts.size() > 1) {
- // Do ECMP.
- Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(leaf, fabricPorts);
- rules.addAll(result.getRight());
- treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
- } else {
- // Output on port.
- PortNumber outPort = fabricPorts.iterator().next();
- treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
- }
+ // From local host to remote ones.
+ for (Host remoteHost : remoteHosts) {
+ int groupId = provisionGroup(leaf, fabricPorts);
- // From srHost to dstHosts.
- for (Host dstHost : dstHosts) {
- FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
- .withSelector(
- DefaultTrafficSelector.builder()
- .matchInPort(hostPort)
- .matchEthType(IPV4.ethType().toShort())
- .matchEthSrc(srcHost.mac())
- .matchEthDst(dstHost.mac())
- .build())
- .withTreatment(treatment)
+ rules.add(groupFlowRule(leaf, groupId));
+
+ PiTableAction piTableAction = PiAction.builder()
+ .withId(ACT_SET_NEXT_HOP_ID)
+ .withParameter(new PiActionParam(
+ ACT_PRM_NEXT_HOP_ID,
+ copyFrom(groupId)))
.build();
- rules.add(rule);
+
+ for (IpAddress ipAddr : remoteHost.ipAddresses()) {
+ FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
+ .withSelector(
+ DefaultTrafficSelector.builder()
+ .matchIPDst(ipAddr.toIpPrefix())
+ .build())
+ .withTreatment(
+ DefaultTrafficTreatment.builder()
+ .piTableAction(piTableAction)
+ .build())
+ .build();
+ rules.add(rule);
+ }
}
- // From fabric ports to this leaf host.
- for (PortNumber port : fabricPorts) {
+ // From remote hosts to the local one
+ for (IpAddress dstIpAddr : localHost.ipAddresses()) {
FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
.withSelector(
DefaultTrafficSelector.builder()
- .matchInPort(port)
- .matchEthType(IPV4.ethType().toShort())
- .matchEthDst(srcHost.mac())
+ .matchIPDst(dstIpAddr.toIpPrefix())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
@@ -158,97 +175,105 @@
}
@Override
- public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
+ public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts,
+ Topology topo)
throws FlowRuleGeneratorException {
List<FlowRule> rules = Lists.newArrayList();
- // for each host
- for (Host dstHost : dstHosts) {
+ // For each host pair (src -> dst)
+ for (Host dstHost : hosts) {
- Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
+ Set<Path> paths = topologyService.getPaths(
+ topo, spine, dstHost.location().deviceId());
if (paths.size() == 0) {
- log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
+ log.warn("No path between spine {} and host {}",
+ spine, dstHost);
throw new FlowRuleGeneratorException();
}
- TrafficTreatment treatment;
+ Set<PortNumber> ports = paths.stream()
+ .map(p -> p.src().port())
+ .collect(toSet());
- 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<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(deviceId, portNumbers);
- rules.addAll(result.getRight());
- treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
- }
+ int groupId = provisionGroup(spine, ports);
- FlowRule rule = flowRuleBuilder(deviceId, TBL_TABLE0_ID)
- .withSelector(
- DefaultTrafficSelector.builder()
- .matchEthType(IPV4.ethType().toShort())
- .matchEthDst(dstHost.mac())
- .build())
- .withTreatment(treatment)
+ rules.add(groupFlowRule(spine, groupId));
+
+ PiTableAction piTableAction = PiAction.builder()
+ .withId(ACT_SET_NEXT_HOP_ID)
+ .withParameter(new PiActionParam(ACT_PRM_NEXT_HOP_ID,
+ copyFrom(groupId)))
.build();
- rules.add(rule);
+ for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
+ FlowRule rule = flowRuleBuilder(spine, TBL_TABLE0_ID)
+ .withSelector(DefaultTrafficSelector.builder()
+ .matchIPDst(dstIpAddr.toIpPrefix())
+ .build())
+ .withTreatment(DefaultTrafficTreatment.builder()
+ .piTableAction(piTableAction)
+ .build())
+ .build();
+ rules.add(rule);
+ }
}
return rules;
}
- private Pair<PiTableAction, List<FlowRule>> provisionEcmpPiTableAction(DeviceId deviceId,
- Set<PortNumber> fabricPorts)
+ /**
+ * Provisions an ECMP group for the given device and set of ports, returns
+ * the group ID.
+ */
+ private int provisionGroup(DeviceId deviceId, Set<PortNumber> ports)
throws FlowRuleGeneratorException {
- // Install ECMP group table entries that map from hash values to actual fabric ports...
- int groupId = groupIdOf(deviceId, fabricPorts);
- if (fabricPorts.size() != HASHED_LINKS) {
- throw new FlowRuleGeneratorException(format(
- "Invalid number of fabric ports for %s, expected %d but found %d",
- deviceId, HASHED_LINKS, fabricPorts.size()));
- }
- Iterator<PortNumber> portIterator = fabricPorts.iterator();
- List<FlowRule> rules = Lists.newArrayList();
- for (short i = 0; i < HASHED_LINKS; i++) {
- FlowRule rule = flowRuleBuilder(deviceId, TBL_ECMP_TABLE_ID)
- .withSelector(
- buildEcmpTrafficSelector(groupId, i))
- .withTreatment(
- DefaultTrafficTreatment.builder()
- .setOutput(portIterator.next())
- .build())
- .build();
- rules.add(rule);
- }
+ int groupId = groupIdOf(deviceId, ports);
- PiTableAction piTableAction = buildEcmpPiTableAction(groupId);
+ // Group buckets
+ List<GroupBucket> bucketList = ports.stream()
+ .map(port -> DefaultTrafficTreatment.builder()
+ .setOutput(port)
+ .build())
+ .map(DefaultGroupBucket::createSelectGroupBucket)
+ .collect(Collectors.toList());
- return Pair.of(piTableAction, rules);
+ // Group cookie (with action profile ID)
+ PiGroupKey groupKey = new PiGroupKey(TBL_WCMP_TABLE_ID,
+ ACT_PRF_WCMP_SELECTOR_ID,
+ groupId);
+
+ log.info("Adding group {} to {}...", groupId, deviceId);
+ groupService.addGroup(
+ new DefaultGroupDescription(deviceId,
+ GroupDescription.Type.SELECT,
+ new GroupBuckets(bucketList),
+ groupKey,
+ groupId,
+ appId));
+
+ groupKeys.add(ImmutablePair.of(deviceId, groupKey));
+
+ return groupId;
}
- private PiTableAction buildEcmpPiTableAction(int groupId) {
-
- return PiAction.builder()
- .withId(ACT_SET_NEXT_HOP_ID)
- .withParameter(new PiActionParam(ACT_PRM_NEXT_HOP_ID,
- ImmutableByteSequence.copyFrom(groupId)))
- .build();
- }
-
- private TrafficSelector buildEcmpTrafficSelector(int groupId, int selector) {
- Criterion ecmpCriterion = PiCriterion.builder()
- .matchExact(HDR_NEXT_HOP_ID, groupId)
- .matchExact(HDR_SELECTOR_ID, selector)
- .build();
-
- return DefaultTrafficSelector.builder()
- .matchPi((PiCriterion) ecmpCriterion)
+ private FlowRule groupFlowRule(DeviceId deviceId, int groupId)
+ throws FlowRuleGeneratorException {
+ return flowRuleBuilder(deviceId, TBL_WCMP_TABLE_ID)
+ .withSelector(
+ DefaultTrafficSelector.builder()
+ .matchPi(
+ PiCriterion.builder()
+ .matchExact(HDR_NEXT_HOP_ID,
+ groupId)
+ .build())
+ .build())
+ .withTreatment(
+ DefaultTrafficTreatment.builder()
+ .piTableAction(PiActionGroupId.of(groupId))
+ .build())
.build();
}
@@ -259,4 +284,4 @@
return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
(short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
}
-}
\ No newline at end of file
+}