Segment Routing bug fix and enhancement
Bugfix:
- Add MPLS BOS matching
- Fix NPE caused by race between filter objective and broadcast next objective
Enhancement:
- Move group handler out from OFDPA pipeline
- Move ARP request from rule populator to packet request
Change-Id: I0ba40e10f7cb7f97277df86725fbd2546a62e890
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index b3e432c..1a37efd 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -513,7 +513,6 @@
public void populatePortAddressingRules(DeviceId deviceId) {
rulePopulator.populateRouterMacVlanFilters(deviceId);
rulePopulator.populateRouterIpPunts(deviceId);
- rulePopulator.populateArpPunts(deviceId);
}
/**
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index d4aa770..8543c86 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -303,6 +303,7 @@
// TODO Handle the case of Bos == false
sbuilder.matchEthType(Ethernet.MPLS_UNICAST);
sbuilder.matchMplsLabel(MplsLabel.mplsLabel(segmentId));
+ sbuilder.matchMplsBos(true);
TrafficSelector selector = sbuilder.build();
// setup metadata to pass to nextObjective - indicate the vlan on egress
@@ -525,39 +526,6 @@
}
/**
- * Creates a forwarding objective to punt all IP packets, destined to the
- * router's port IP addresses, to the controller. Note that the input
- * port should not be matched on, as these packets can come from any input.
- * Furthermore, these are applied only by the master instance.
- *
- * @param deviceId the switch dpid for the router
- */
- public void populateArpPunts(DeviceId deviceId) {
- if (!srManager.mastershipService.isLocalMaster(deviceId)) {
- log.debug("Not installing port-IP punts - not the master for dev:{} ",
- deviceId);
- return;
- }
-
- ForwardingObjective.Builder puntArp = DefaultForwardingObjective.builder();
- TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
- TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
- sbuilder.matchEthType(Ethernet.TYPE_ARP);
- tbuilder.setOutput(PortNumber.CONTROLLER);
- puntArp.withSelector(sbuilder.build());
- puntArp.withTreatment(tbuilder.build());
- puntArp.withFlag(Flag.VERSATILE)
- .withPriority(HIGHEST_PRIORITY)
- .makePermanent()
- .fromApp(srManager.appId);
- log.debug("Installing forwarding objective to punt ARPs");
- srManager.flowObjectiveService.
- forward(deviceId,
- puntArp.add(new SRObjectiveContext(deviceId,
- SRObjectiveContext.ObjectiveType.FORWARDING)));
- }
-
- /**
* Populates a forwarding objective to send packets that miss other high
* priority Bridging Table entries to a group that contains all ports of
* its subnet.
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 7bcdfeb..0f20451 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -51,6 +51,7 @@
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
+import org.onosproject.net.packet.PacketPriority;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceConfiguration;
import org.onosproject.segmentrouting.config.SegmentRoutingConfig;
@@ -90,6 +91,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -297,6 +299,11 @@
linkService.addListener(linkListener);
deviceService.addListener(deviceListener);
+ // Request ARP packet-in
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(Ethernet.TYPE_ARP);
+ packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId, Optional.empty());
+
cfgListener.configureNetwork();
log.info("Started");
@@ -307,6 +314,11 @@
cfgService.removeListener(cfgListener);
cfgService.unregisterConfigFactory(cfgFactory);
+ // Withdraw ARP packet-in
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(Ethernet.TYPE_ARP);
+ packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId, Optional.empty());
+
packetService.removeProcessor(processor);
linkService.removeListener(linkListener);
deviceService.removeListener(deviceListener);
@@ -697,7 +709,8 @@
flowObjectiveService,
nsNextObjStore,
subnetNextObjStore,
- portNextObjStore);
+ portNextObjStore,
+ this);
} catch (DeviceConfigNotFoundException e) {
log.warn(e.getMessage() + " Aborting processDeviceAdded.");
return;
@@ -766,7 +779,8 @@
flowObjectiveService,
nsNextObjStore,
subnetNextObjStore,
- portNextObjStore);
+ portNextObjStore,
+ segmentRoutingManager);
} catch (DeviceConfigNotFoundException e) {
log.warn(e.getMessage() + " Aborting configureNetwork.");
return;
@@ -836,16 +850,7 @@
private ForwardingObjective.Builder getForwardingObjectiveBuilder(
DeviceId deviceId, MacAddress mac, VlanId vlanId,
PortNumber outport) {
- // match rule
- TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
- sbuilder.matchEthDst(mac);
- sbuilder.matchVlanId(vlanId);
-
- TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
- tbuilder.immediate().popVlan();
- tbuilder.immediate().setOutput(outport);
-
- // for switch pipelines that need it, provide outgoing vlan as metadata
+ // Get assigned VLAN for the subnet
VlanId outvlan = null;
Ip4Prefix subnet = deviceConfiguration.getPortSubnet(deviceId, outport);
if (subnet == null) {
@@ -853,6 +858,25 @@
} else {
outvlan = getSubnetAssignedVlanId(deviceId, subnet);
}
+
+ // match rule
+ TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+ sbuilder.matchEthDst(mac);
+ /*
+ * Note: for untagged packets, match on the assigned VLAN.
+ * for tagged packets, match on its incoming VLAN.
+ */
+ if (vlanId.equals(VlanId.NONE)) {
+ sbuilder.matchVlanId(outvlan);
+ } else {
+ sbuilder.matchVlanId(vlanId);
+ }
+
+ TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+ tbuilder.immediate().popVlan();
+ tbuilder.immediate().setOutput(outport);
+
+ // for switch pipelines that need it, provide outgoing vlan as metadata
TrafficSelector meta = DefaultTrafficSelector.builder()
.matchVlanId(outvlan).build();
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
index 32c5365..4866b82 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
@@ -24,6 +24,7 @@
import org.onosproject.net.Link;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.link.LinkService;
+import org.onosproject.segmentrouting.SegmentRoutingManager;
import org.onosproject.segmentrouting.config.DeviceProperties;
import org.onosproject.store.service.EventuallyConsistentMap;
@@ -46,7 +47,7 @@
* 8) what about ecmp no label case
*/
public class DefaultEdgeGroupHandler extends DefaultGroupHandler {
-
+ // TODO Access stores through srManager
protected DefaultEdgeGroupHandler(DeviceId deviceId,
ApplicationId appId,
DeviceProperties config,
@@ -58,9 +59,10 @@
EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
Integer> subnetNextObjStore,
EventuallyConsistentMap<PortNextObjectiveStoreKey,
- Integer> portNextObjStore) {
+ Integer> portNextObjStore,
+ SegmentRoutingManager srManager) {
super(deviceId, appId, config, linkService, flowObjService,
- nsNextObjStore, subnetNextObjStore, portNextObjStore);
+ nsNextObjStore, subnetNextObjStore, portNextObjStore, srManager);
}
@Override
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
index 2986c50..a4b89c1 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -87,6 +87,7 @@
SubnetNextObjectiveStoreKey, Integer> subnetNextObjStore = null;
protected EventuallyConsistentMap<
PortNextObjectiveStoreKey, Integer> portNextObjStore = null;
+ private SegmentRoutingManager srManager;
protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
.register(URI.class).register(HashSet.class)
@@ -96,6 +97,7 @@
.register(GroupBucketIdentifier.class)
.register(GroupBucketIdentifier.BucketOutputType.class);
+ // TODO Access stores through srManager
protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId,
DeviceProperties config,
LinkService linkService,
@@ -105,7 +107,8 @@
EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
Integer> subnetNextObjStore,
EventuallyConsistentMap<PortNextObjectiveStoreKey,
- Integer> portNextObjStore) {
+ Integer> portNextObjStore,
+ SegmentRoutingManager srManager) {
this.deviceId = checkNotNull(deviceId);
this.appId = checkNotNull(appId);
this.deviceConfig = checkNotNull(config);
@@ -123,6 +126,7 @@
this.nsNextObjStore = nsNextObjStore;
this.subnetNextObjStore = subnetNextObjStore;
this.portNextObjStore = portNextObjStore;
+ this.srManager = srManager;
populateNeighborMaps();
}
@@ -153,7 +157,8 @@
EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
Integer> subnetNextObjStore,
EventuallyConsistentMap<PortNextObjectiveStoreKey,
- Integer> portNextObjStore)
+ Integer> portNextObjStore,
+ SegmentRoutingManager srManager)
throws DeviceConfigNotFoundException {
// handle possible exception in the caller
if (config.isEdgeDevice(deviceId)) {
@@ -162,14 +167,17 @@
flowObjService,
nsNextObjStore,
subnetNextObjStore,
- portNextObjStore);
+ portNextObjStore,
+ srManager
+ );
} else {
return new DefaultTransitGroupHandler(deviceId, appId, config,
linkService,
flowObjService,
nsNextObjStore,
subnetNextObjStore,
- portNextObjStore);
+ portNextObjStore,
+ srManager);
}
}
@@ -663,11 +671,17 @@
return;
}
+ VlanId assignedVlanId =
+ srManager.getSubnetAssignedVlanId(this.deviceId, subnet);
+ TrafficSelector metadata =
+ DefaultTrafficSelector.builder().matchVlanId(assignedVlanId).build();
+
int nextId = flowObjectiveService.allocateNextId();
NextObjective.Builder nextObjBuilder = DefaultNextObjective
.builder().withId(nextId)
- .withType(NextObjective.Type.BROADCAST).fromApp(appId);
+ .withType(NextObjective.Type.BROADCAST).fromApp(appId)
+ .withMeta(metadata);
ports.forEach(port -> {
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
index 7a43e73..5bc7ede 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
@@ -23,6 +23,7 @@
import org.onosproject.net.Link;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.link.LinkService;
+import org.onosproject.segmentrouting.SegmentRoutingManager;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceProperties;
import org.onosproject.store.service.EventuallyConsistentMap;
@@ -40,7 +41,7 @@
* 2) all ports to D3 + with no label push,
*/
public class DefaultTransitGroupHandler extends DefaultGroupHandler {
-
+ // TODO Access stores through srManager
protected DefaultTransitGroupHandler(DeviceId deviceId,
ApplicationId appId,
DeviceProperties config,
@@ -52,9 +53,10 @@
EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
Integer> subnetNextObjStore,
EventuallyConsistentMap<PortNextObjectiveStoreKey,
- Integer> portNextObjStore) {
+ Integer> portNextObjStore,
+ SegmentRoutingManager srManager) {
super(deviceId, appId, config, linkService, flowObjService,
- nsNextObjStore, subnetNextObjStore, portNextObjStore);
+ nsNextObjStore, subnetNextObjStore, portNextObjStore, srManager);
}
@Override
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
index ef143dc..4b0d518 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
@@ -27,6 +27,7 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.MplsLabel;
import org.onosproject.core.ApplicationId;
+import org.onosproject.segmentrouting.SegmentRoutingManager;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceProperties;
import org.onosproject.segmentrouting.grouphandler.GroupBucketIdentifier.BucketOutputType;
@@ -60,6 +61,7 @@
* @param nsNextObjStore NeighborSet next objective store map
* @param subnetNextObjStore subnet next objective store map
*/
+ // TODO Access stores through srManager
public PolicyGroupHandler(DeviceId deviceId,
ApplicationId appId,
DeviceProperties config,
@@ -70,9 +72,10 @@
EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
Integer> subnetNextObjStore,
EventuallyConsistentMap<PortNextObjectiveStoreKey,
- Integer> portNextObjStore) {
+ Integer> portNextObjStore,
+ SegmentRoutingManager srManager) {
super(deviceId, appId, config, linkService, flowObjService,
- nsNextObjStore, subnetNextObjStore, portNextObjStore);
+ nsNextObjStore, subnetNextObjStore, portNextObjStore, srManager);
}
public PolicyGroupIdentifier createPolicyGroupChain(String id,
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java b/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
index 8fd9097..cd1ac19 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
@@ -106,13 +106,13 @@
for (PortNumber pnum : portnums) {
// update storage
- port2Vlan.put(pnum, storeVlan);
- Set<PortNumber> vlanPorts = vlan2Port.get(storeVlan);
+ ofdpa2GroupHandler.port2Vlan.put(pnum, storeVlan);
+ Set<PortNumber> vlanPorts = ofdpa2GroupHandler.vlan2Port.get(storeVlan);
if (vlanPorts == null) {
vlanPorts = Collections.newSetFromMap(
new ConcurrentHashMap<PortNumber, Boolean>());
vlanPorts.add(pnum);
- vlan2Port.put(storeVlan, vlanPorts);
+ ofdpa2GroupHandler.vlan2Port.put(storeVlan, vlanPorts);
} else {
vlanPorts.add(pnum);
}
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
new file mode 100644
index 0000000..2f95ce8
--- /dev/null
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
@@ -0,0 +1,1159 @@
+package org.onosproject.driver.pipeline;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.behaviour.PipelinerContext;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+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.VlanIdCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupService;
+import org.slf4j.Logger;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Group handler for OFDPA2 pipeline.
+ */
+public class OFDPA2GroupHandler {
+ /*
+ * OFDPA requires group-id's to have a certain form.
+ * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
+ * L3 Unicast Groups have <4bits-2><28bits-index>
+ * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
+ * L3 ECMP Groups have <4bits-7><28bits-index>
+ * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
+ * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
+ */
+ private static final int L2INTERFACEMASK = 0x0;
+ private static final int L3UNICASTMASK = 0x20000000;
+ private static final int MPLSINTERFACEMASK = 0x90000000;
+ private static final int L3ECMPMASK = 0x70000000;
+ private static final int L2FLOODMASK = 0x40000000;
+ private static final int L3VPNMASK = 0x92000000;
+
+ private final Logger log = getLogger(getClass());
+ private ServiceDirectory serviceDirectory;
+ protected GroupService groupService;
+
+ private DeviceId deviceId;
+ private FlowObjectiveStore flowObjectiveStore;
+ private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
+ private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
+ private ScheduledExecutorService groupChecker =
+ Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
+
+ // index number for group creation
+ private AtomicInteger l3vpnindex = new AtomicInteger(0);
+
+ // local stores for port-vlan mapping
+ protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
+ protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
+
+ // local store for pending bucketAdds - by design there can only be one
+ // pending bucket for a group
+ protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
+
+ protected void init(DeviceId deviceId, PipelinerContext context) {
+ this.deviceId = deviceId;
+ this.flowObjectiveStore = context.store();
+ this.serviceDirectory = context.directory();
+ this.groupService = serviceDirectory.get(GroupService.class);
+
+ pendingNextObjectives = CacheBuilder.newBuilder()
+ .expireAfterWrite(20, TimeUnit.SECONDS)
+ .removalListener((
+ RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
+ if (notification.getCause() == RemovalCause.EXPIRED) {
+ notification.getValue().forEach(ofdpaNextGrp ->
+ OFDPA2Pipeline.fail(ofdpaNextGrp.nextObj,
+ ObjectiveError.GROUPINSTALLATIONFAILED));
+
+ }
+ }).build();
+ pendingGroups = new ConcurrentHashMap<>();
+ groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
+
+ groupService.addListener(new InnerGroupListener());
+ }
+
+ protected void addGroup(NextObjective nextObjective) {
+ switch (nextObjective.type()) {
+ case SIMPLE:
+ Collection<TrafficTreatment> treatments = nextObjective.next();
+ if (treatments.size() != 1) {
+ log.error("Next Objectives of type Simple should only have a "
+ + "single Traffic Treatment. Next Objective Id:{}",
+ nextObjective.id());
+ OFDPA2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+ return;
+ }
+ processSimpleNextObjective(nextObjective);
+ break;
+ case BROADCAST:
+ processBroadcastNextObjective(nextObjective);
+ break;
+ case HASHED:
+ processHashedNextObjective(nextObjective);
+ break;
+ case FAILOVER:
+ OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
+ log.warn("Unsupported next objective type {}", nextObjective.type());
+ break;
+ default:
+ OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
+ log.warn("Unknown next objective type {}", nextObjective.type());
+ }
+ }
+
+ /**
+ * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
+ * a chain of groups. The simple Next Objective passed
+ * in by the application has to be broken up into a group chain
+ * comprising of an L3 Unicast Group that points to an L2 Interface
+ * Group which in-turn points to an output port. In some cases, the simple
+ * next Objective can just be an L2 interface without the need for chaining.
+ *
+ * @param nextObj the nextObjective of type SIMPLE
+ */
+ private void processSimpleNextObjective(NextObjective nextObj) {
+ TrafficTreatment treatment = nextObj.next().iterator().next();
+ // determine if plain L2 or L3->L2
+ boolean plainL2 = true;
+ for (Instruction ins : treatment.allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
+ l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
+ plainL2 = false;
+ break;
+ }
+ }
+ }
+
+ if (plainL2) {
+ createL2InterfaceGroup(nextObj);
+ return;
+ }
+
+ // break up simple next objective to GroupChain objects
+ GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
+ nextObj.appId(), false,
+ nextObj.meta());
+ if (groupInfo == null) {
+ log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
+ return;
+ }
+ // create object for local and distributed storage
+ Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+ gkeyChain.addFirst(groupInfo.innerGrpDesc.appCookie());
+ gkeyChain.addFirst(groupInfo.outerGrpDesc.appCookie());
+ OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
+ Collections.singletonList(gkeyChain),
+ nextObj);
+
+ // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
+ updatePendingNextObjective(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp);
+
+ // now we are ready to send the l2 groupDescription (inner), as all the stores
+ // that will get async replies have been updated. By waiting to update
+ // the stores, we prevent nasty race conditions.
+ groupService.addGroup(groupInfo.innerGrpDesc);
+ }
+
+ private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
+ List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
+ nextList.add(value);
+ List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
+ .putIfAbsent(key, nextList);
+ if (ret != null) {
+ ret.add(value);
+ }
+ }
+
+ private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
+ Set<GroupChainElem> gceSet = Collections.newSetFromMap(
+ new ConcurrentHashMap<GroupChainElem, Boolean>());
+ gceSet.add(gce);
+ Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
+ if (retval != null) {
+ retval.add(gce);
+ }
+ }
+
+ /**
+ * Creates a simple L2 Interface Group.
+ *
+ * @param nextObj the next Objective
+ */
+ private void createL2InterfaceGroup(NextObjective nextObj) {
+ // only allowed actions are vlan pop and outport
+ TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+ PortNumber portNum = null;
+ for (Instruction ins : nextObj.next().iterator().next().allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ switch (l2ins.subtype()) {
+ case VLAN_POP:
+ ttb.add(l2ins);
+ break;
+ default:
+ break;
+ }
+ } else if (ins.type() == Instruction.Type.OUTPUT) {
+ portNum = ((Instructions.OutputInstruction) ins).port();
+ ttb.add(ins);
+ } else {
+ log.warn("Driver does not handle this type of TrafficTreatment"
+ + " instruction in simple nextObjectives: {}", ins.type());
+ }
+ }
+ //use the vlanid associated with the port
+ VlanId vlanid = port2Vlan.get(portNum);
+
+ if (vlanid == null && nextObj.meta() != null) {
+ // use metadata vlan info if available
+ Criterion vidCriterion = nextObj.meta().getCriterion(Criterion.Type.VLAN_VID);
+ if (vidCriterion != null) {
+ vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
+ }
+ }
+
+ if (vlanid == null) {
+ log.error("Driver cannot process an L2/L3 group chain without "
+ + "egress vlan information for dev: {} port:{}",
+ deviceId, portNum);
+ return;
+ }
+
+ // assemble information for ofdpa l2interface group
+ Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum.toLong();
+ // a globally unique groupkey that is different for ports in the same devices
+ // but different for the same portnumber on different devices. Also different
+ // for the various group-types created out of the same next objective.
+ int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
+ final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
+
+ // create group description for the l2interfacegroup
+ GroupBucket l2interfaceGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(ttb.build());
+ GroupDescription l2groupDescription =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ l2interfaceGroupBucket)),
+ l2groupkey,
+ l2groupId,
+ nextObj.appId());
+ log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l2groupId),
+ l2groupkey, nextObj.id());
+
+ // create object for local and distributed storage
+ Deque<GroupKey> singleKey = new ArrayDeque<>();
+ singleKey.addFirst(l2groupkey);
+ OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
+ Collections.singletonList(singleKey),
+ nextObj);
+
+ // store l2groupkey for the nextObjective that depends on it
+ updatePendingNextObjective(l2groupkey, ofdpaGrp);
+ // send the group description to the group service
+ groupService.addGroup(l2groupDescription);
+ }
+
+ /**
+ * Creates one of two possible group-chains from the treatment
+ * passed in. Depending on the MPLS boolean, this method either creates
+ * an L3Unicast Group --> L2Interface Group, if mpls is false;
+ * or MPLSInterface Group --> L2Interface Group, if mpls is true;
+ * The returned 'inner' group description is always the L2 Interface group.
+ *
+ * @param treatment that needs to be broken up to create the group chain
+ * @param nextId of the next objective that needs this group chain
+ * @param appId of the application that sent this next objective
+ * @param mpls determines if L3Unicast or MPLSInterface group is created
+ * @param meta metadata passed in by the application as part of the nextObjective
+ * @return GroupInfo containing the GroupDescription of the
+ * L2Interface group(inner) and the GroupDescription of the (outer)
+ * L3Unicast/MPLSInterface group. May return null if there is an
+ * error in processing the chain
+ */
+ private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
+ ApplicationId appId, boolean mpls,
+ TrafficSelector meta) {
+ // for the l2interface group, get vlan and port info
+ // for the outer group, get the src/dst mac, and vlan info
+ TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
+ TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
+ VlanId vlanid = null;
+ long portNum = 0;
+ boolean setVlan = false, popVlan = false;
+ for (Instruction ins : treatment.allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ switch (l2ins.subtype()) {
+ case ETH_DST:
+ outerTtb.setEthDst(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
+ break;
+ case ETH_SRC:
+ outerTtb.setEthSrc(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
+ break;
+ case VLAN_ID:
+ vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
+ outerTtb.setVlanId(vlanid);
+ setVlan = true;
+ break;
+ case VLAN_POP:
+ innerTtb.popVlan();
+ popVlan = true;
+ break;
+ case DEC_MPLS_TTL:
+ case MPLS_LABEL:
+ case MPLS_POP:
+ case MPLS_PUSH:
+ case VLAN_PCP:
+ case VLAN_PUSH:
+ default:
+ break;
+ }
+ } else if (ins.type() == Instruction.Type.OUTPUT) {
+ portNum = ((Instructions.OutputInstruction) ins).port().toLong();
+ innerTtb.add(ins);
+ } else {
+ log.warn("Driver does not handle this type of TrafficTreatment"
+ + " instruction in nextObjectives: {}", ins.type());
+ }
+ }
+
+ if (vlanid == null && meta != null) {
+ // use metadata if available
+ Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
+ if (vidCriterion != null) {
+ vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
+ }
+ // if vlan is not set, use the vlan in metadata for outerTtb
+ if (vlanid != null && !setVlan) {
+ outerTtb.setVlanId(vlanid);
+ }
+ }
+
+ if (vlanid == null) {
+ log.error("Driver cannot process an L2/L3 group chain without "
+ + "egress vlan information for dev: {} port:{}",
+ deviceId, portNum);
+ return null;
+ }
+
+ if (!setVlan && !popVlan) {
+ // untagged outgoing port
+ TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
+ temp.popVlan();
+ innerTtb.build().allInstructions().forEach(i -> temp.add(i));
+ innerTtb = temp;
+ }
+
+ // assemble information for ofdpa l2interface group
+ Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum;
+ // a globally unique groupkey that is different for ports in the same devices
+ // but different for the same portnumber on different devices. Also different
+ // for the various group-types created out of the same next objective.
+ int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum);
+ final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
+
+ // assemble information for outer group
+ GroupDescription outerGrpDesc = null;
+ if (mpls) {
+ // outer group is MPLSInteface
+ Integer mplsgroupId = MPLSINTERFACEMASK | (int) portNum;
+ // using mplsinterfacemask in groupkey to differentiate from l2interface
+ int mplsgk = MPLSINTERFACEMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
+ final GroupKey mplsgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(mplsgk));
+ outerTtb.group(new DefaultGroupId(l2groupId));
+ // create the mpls-interface group description to wait for the
+ // l2 interface group to be processed
+ GroupBucket mplsinterfaceGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
+ outerGrpDesc = new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ mplsinterfaceGroupBucket)),
+ mplsgroupkey,
+ mplsgroupId,
+ appId);
+ log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
+ deviceId, Integer.toHexString(mplsgroupId),
+ mplsgroupkey, nextId);
+ } else {
+ // outer group is L3Unicast
+ Integer l3groupId = L3UNICASTMASK | (int) portNum;
+ int l3gk = L3UNICASTMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
+ final GroupKey l3groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3gk));
+ outerTtb.group(new DefaultGroupId(l2groupId));
+ // create the l3unicast group description to wait for the
+ // l2 interface group to be processed
+ GroupBucket l3unicastGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
+ outerGrpDesc = new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ l3unicastGroupBucket)),
+ l3groupkey,
+ l3groupId,
+ appId);
+ log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
+ deviceId, Integer.toHexString(l3groupId),
+ l3groupkey, nextId);
+ }
+
+ // store l2groupkey with the groupChainElem for the outer-group that depends on it
+ GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
+ updatePendingGroups(l2groupkey, gce);
+
+ // create group description for the inner l2interfacegroup
+ GroupBucket l2interfaceGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
+ GroupDescription l2groupDescription =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ l2interfaceGroupBucket)),
+ l2groupkey,
+ l2groupId,
+ appId);
+ log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l2groupId),
+ l2groupkey, nextId);
+ return new GroupInfo(l2groupDescription, outerGrpDesc);
+
+ }
+
+ /**
+ * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
+ * a chain of groups. The broadcast Next Objective passed in by the application
+ * has to be broken up into a group chain comprising of an
+ * L2 Flood group whose buckets point to L2 Interface groups.
+ *
+ * @param nextObj the nextObjective of type BROADCAST
+ */
+ private void processBroadcastNextObjective(NextObjective nextObj) {
+ // break up broadcast next objective to multiple groups
+ Collection<TrafficTreatment> buckets = nextObj.next();
+
+ // Read VLAN information from the metadata
+ TrafficSelector metadata = nextObj.meta();
+ Criterion criterion = metadata.getCriterion(Criterion.Type.VLAN_VID);
+ if (criterion == null) {
+ log.warn("Required VLAN ID info in nextObj metadata but not found. Aborting");
+ return;
+ }
+ VlanId vlanId = ((VlanIdCriterion) criterion).vlanId();
+
+ // each treatment is converted to an L2 interface group
+ List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>();
+ List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
+ for (TrafficTreatment treatment : buckets) {
+ TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
+ PortNumber portNum = null;
+ // ensure that the only allowed treatments are pop-vlan and output
+ for (Instruction ins : treatment.allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ switch (l2ins.subtype()) {
+ case VLAN_POP:
+ newTreatment.add(l2ins);
+ break;
+ default:
+ log.debug("action {} not permitted for broadcast nextObj",
+ l2ins.subtype());
+ break;
+ }
+ } else if (ins.type() == Instruction.Type.OUTPUT) {
+ portNum = ((Instructions.OutputInstruction) ins).port();
+ newTreatment.add(ins);
+ } else {
+ log.debug("TrafficTreatment of type {} not permitted in "
+ + " broadcast nextObjective", ins.type());
+ }
+ }
+
+ // Ensure that all ports of this broadcast nextObj are in the same vlan
+ // XXX maybe HA issue here?
+ VlanId expectedVlanId = port2Vlan.putIfAbsent(portNum, vlanId);
+ if (expectedVlanId != null && !vlanId.equals(expectedVlanId)) {
+ log.error("Driver requires all ports in a broadcast nextObj "
+ + "to be in the same vlan. Different vlans found "
+ + "{} and {}. Aborting group creation", vlanId, expectedVlanId);
+ return;
+ }
+
+
+ // assemble info for l2 interface group
+ int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
+ final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
+ Integer l2groupId = L2INTERFACEMASK | (vlanId.toShort() << 16) |
+ (int) portNum.toLong();
+ GroupBucket l2interfaceGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
+ GroupDescription l2interfaceGroupDescription =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ l2interfaceGroupBucket)),
+ l2groupkey,
+ l2groupId,
+ nextObj.appId());
+ log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
+ deviceId, Integer.toHexString(l2groupId),
+ l2groupkey, nextObj.id());
+
+ Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+ gkeyChain.addFirst(l2groupkey);
+
+ // store the info needed to create this group
+ l2interfaceGroupDescs.add(l2interfaceGroupDescription);
+ allGroupKeys.add(gkeyChain);
+ }
+
+ // assemble info for l2 flood group
+ Integer l2floodgroupId = L2FLOODMASK | (vlanId.toShort() << 16) | nextObj.id();
+ int l2floodgk = L2FLOODMASK | nextObj.id() << 12;
+ final GroupKey l2floodgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2floodgk));
+ // collection of group buckets pointing to all the l2 interface groups
+ List<GroupBucket> l2floodBuckets = new ArrayList<>();
+ for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
+ TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+ ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
+ GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
+ l2floodBuckets.add(abucket);
+ }
+ // create the l2flood group-description to wait for all the
+ // l2interface groups to be processed
+ GroupDescription l2floodGroupDescription =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.ALL,
+ new GroupBuckets(l2floodBuckets),
+ l2floodgroupkey,
+ l2floodgroupId,
+ nextObj.appId());
+ GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
+ l2interfaceGroupDescs.size(),
+ false);
+ log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
+ deviceId, Integer.toHexString(l2floodgroupId),
+ l2floodgroupkey, nextObj.id());
+
+ // create objects for local and distributed storage
+ allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey));
+ OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
+
+ // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
+ // that depends on it
+ updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
+
+ for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
+ // store all l2groupkeys with the groupChainElem for the l2floodgroup
+ // that depends on it
+ updatePendingGroups(l2intGrpDesc.appCookie(), gce);
+ // send groups for all l2 interface groups
+ groupService.addGroup(l2intGrpDesc);
+ }
+ }
+
+
+
+ /**
+ * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
+ * a chain of groups. The hashed Next Objective passed in by the application
+ * has to be broken up into a group chain comprising of an
+ * L3 ECMP group as the top level group. Buckets of this group can point
+ * to a variety of groups in a group chain, depending on the whether
+ * MPLS labels are being pushed or not.
+ * <p>
+ * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
+ * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
+ * check the nextObjective meta to see what is matching before being
+ * sent to this nextObjective.
+ *
+ * @param nextObj the nextObjective of type HASHED
+ */
+ private void processHashedNextObjective(NextObjective nextObj) {
+ // storage for all group keys in the chain of groups created
+ List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
+ List<GroupInfo> unsentGroups = new ArrayList<>();
+ createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
+
+ // now we can create the outermost L3 ECMP group
+ List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
+ for (GroupInfo gi : unsentGroups) {
+ // create ECMP bucket to point to the outer group
+ TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+ ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
+ GroupBucket sbucket = DefaultGroupBucket
+ .createSelectGroupBucket(ttb.build());
+ l3ecmpGroupBuckets.add(sbucket);
+ }
+ int l3ecmpGroupId = L3ECMPMASK | nextObj.id() << 12;
+ GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
+ GroupDescription l3ecmpGroupDesc =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.SELECT,
+ new GroupBuckets(l3ecmpGroupBuckets),
+ l3ecmpGroupKey,
+ l3ecmpGroupId,
+ nextObj.appId());
+ GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
+ l3ecmpGroupBuckets.size(),
+ false);
+
+ // create objects for local and distributed storage
+ allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
+ OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
+
+ // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
+ // that depends on it
+ updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
+
+ log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l3ecmpGroupId),
+ l3ecmpGroupKey, nextObj.id());
+ // finally we are ready to send the innermost groups
+ for (GroupInfo gi : unsentGroups) {
+ log.debug("Sending innermost group {} in group chain on device {} ",
+ Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
+ updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
+ groupService.addGroup(gi.innerGrpDesc);
+ }
+
+ }
+
+ /**
+ * Creates group chains for all buckets in a hashed group, and stores the
+ * GroupInfos and GroupKeys for all the groups in the lists passed in, which
+ * should be empty.
+ * <p>
+ * Does not create the top level ECMP group. Does not actually send the
+ * groups to the groupService.
+ *
+ * @param nextObj the Next Objective with buckets that need to be converted
+ * to group chains
+ * @param allGroupKeys a list to store groupKey for each bucket-group-chain
+ * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
+ */
+ private void createHashBucketChains(NextObjective nextObj,
+ List<Deque<GroupKey>> allGroupKeys,
+ List<GroupInfo> unsentGroups) {
+ // break up hashed next objective to multiple groups
+ Collection<TrafficTreatment> buckets = nextObj.next();
+
+ for (TrafficTreatment bucket : buckets) {
+ //figure out how many labels are pushed in each bucket
+ int labelsPushed = 0;
+ MplsLabel innermostLabel = null;
+ for (Instruction ins : bucket.allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
+ labelsPushed++;
+ }
+ if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
+ if (innermostLabel == null) {
+ innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).mplsLabel();
+ }
+ }
+ }
+ }
+
+ Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+ // XXX we only deal with 0 and 1 label push right now
+ if (labelsPushed == 0) {
+ GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+ nextObj.appId(), false,
+ nextObj.meta());
+ if (nolabelGroupInfo == null) {
+ log.error("Could not process nextObj={} in dev:{}",
+ nextObj.id(), deviceId);
+ return;
+ }
+ gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie());
+ gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie());
+
+ // we can't send the inner group description yet, as we have to
+ // create the dependent ECMP group first. So we store..
+ unsentGroups.add(nolabelGroupInfo);
+
+ } else if (labelsPushed == 1) {
+ GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+ nextObj.appId(), true,
+ nextObj.meta());
+ if (onelabelGroupInfo == null) {
+ log.error("Could not process nextObj={} in dev:{}",
+ nextObj.id(), deviceId);
+ return;
+ }
+ // we need to add another group to this chain - the L3VPN group
+ TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
+ l3vpnTtb.pushMpls()
+ .setMpls(innermostLabel)
+ .setMplsBos(true)
+ .copyTtlOut()
+ .group(new DefaultGroupId(
+ onelabelGroupInfo.outerGrpDesc.givenGroupId()));
+ GroupBucket l3vpnGrpBkt =
+ DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
+ int l3vpngroupId = L3VPNMASK | l3vpnindex.incrementAndGet();
+ int l3vpngk = L3VPNMASK | nextObj.id() << 12 | l3vpnindex.get();
+ GroupKey l3vpngroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3vpngk));
+ GroupDescription l3vpnGroupDesc =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(
+ l3vpnGrpBkt)),
+ l3vpngroupkey,
+ l3vpngroupId,
+ nextObj.appId());
+ GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
+ updatePendingGroups(onelabelGroupInfo.outerGrpDesc.appCookie(), l3vpnGce);
+
+ gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie());
+ gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie());
+ gkeyChain.addFirst(l3vpngroupkey);
+
+ //now we can replace the outerGrpDesc with the one we just created
+ onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc;
+
+ // we can't send the innermost group yet, as we have to create
+ // the dependent ECMP group first. So we store ...
+ unsentGroups.add(onelabelGroupInfo);
+
+ log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l3vpngroupId),
+ l3vpngroupkey, nextObj.id());
+
+ } else {
+ log.warn("Driver currently does not handle more than 1 MPLS "
+ + "labels. Not processing nextObjective {}", nextObj.id());
+ return;
+ }
+
+ // all groups in this chain
+ allGroupKeys.add(gkeyChain);
+ }
+ }
+
+ /**
+ * Adds a bucket to the top level group of a group-chain, and creates the chain.
+ *
+ * @param nextObjective the next group to add a bucket to
+ * @param next the representation of the existing group-chain for this next objective
+ */
+ protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
+ if (nextObjective.type() != NextObjective.Type.HASHED) {
+ log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
+ nextObjective.type(), deviceId, nextObjective.id());
+ return;
+ }
+ if (nextObjective.next().size() > 1) {
+ log.warn("Only one bucket can be added at a time");
+ return;
+ }
+ // storage for all group keys in the chain of groups created
+ List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
+ List<GroupInfo> unsentGroups = new ArrayList<>();
+ createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
+
+ // now we can create the outermost L3 ECMP group bucket to add
+ GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
+ TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+ ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
+ GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
+
+ // recreate the original L3 ECMP group id and description
+ int l3ecmpGroupId = L3ECMPMASK | nextObjective.id() << 12;
+ GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
+
+ // Although GroupDescriptions are not necessary for adding buckets to
+ // existing groups, we use one in the GroupChainElem. When the latter is
+ // processed, the info will be extracted for the bucketAdd call to groupService
+ GroupDescription l3ecmpGroupDesc =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.SELECT,
+ new GroupBuckets(Collections.singletonList(sbucket)),
+ l3ecmpGroupKey,
+ l3ecmpGroupId,
+ nextObjective.appId());
+ GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
+
+ // update original NextGroup with new bucket-chain
+ // don't need to update pendingNextObjectives -- group already exists
+ Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
+ newBucketChain.addFirst(l3ecmpGroupKey);
+ List<Deque<GroupKey>> allOriginalKeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
+ allOriginalKeys.add(newBucketChain);
+ flowObjectiveStore.putNextGroup(nextObjective.id(),
+ new OfdpaNextGroup(allOriginalKeys, nextObjective));
+
+ log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l3ecmpGroupId),
+ l3ecmpGroupKey, nextObjective.id());
+ // send the innermost group
+ log.debug("Sending innermost group {} in group chain on device {} ",
+ Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
+ updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
+ groupService.addGroup(gi.innerGrpDesc);
+
+ }
+
+ /**
+ * Removes the bucket in the top level group of a possible group-chain. Does
+ * not remove the groups in a group-chain pointed to by this bucket, as they
+ * may be in use (referenced by other groups) elsewhere.
+ *
+ * @param nextObjective the next group to remove a bucket from
+ * @param next the representation of the existing group-chain for this next objective
+ */
+ protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
+ if (nextObjective.type() != NextObjective.Type.HASHED) {
+ log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
+ nextObjective.type(), deviceId, nextObjective.id());
+ return;
+ }
+ Collection<TrafficTreatment> treatments = nextObjective.next();
+ TrafficTreatment treatment = treatments.iterator().next();
+ // find the bucket to remove by noting the outport, and figuring out the
+ // top-level group in the group-chain that indirectly references the port
+ PortNumber outport = null;
+ for (Instruction ins : treatment.allInstructions()) {
+ if (ins instanceof Instructions.OutputInstruction) {
+ outport = ((Instructions.OutputInstruction) ins).port();
+ break;
+ }
+ }
+ if (outport == null) {
+ log.error("next objective {} has no outport", nextObjective.id());
+ return;
+ }
+
+ List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
+ Deque<GroupKey> foundChain = null;
+ int index = 0;
+ for (Deque<GroupKey> gkeys : allgkeys) {
+ GroupKey groupWithPort = gkeys.peekLast();
+ Group group = groupService.getGroup(deviceId, groupWithPort);
+ if (group == null) {
+ log.warn("Inconsistent group chain");
+ continue;
+ }
+ // last group in group chain should have a single bucket pointing to port
+ List<Instruction> lastIns = group.buckets().buckets().iterator()
+ .next().treatment().allInstructions();
+ for (Instruction i : lastIns) {
+ if (i instanceof Instructions.OutputInstruction) {
+ PortNumber lastport = ((Instructions.OutputInstruction) i).port();
+ if (lastport.equals(outport)) {
+ foundChain = gkeys;
+ break;
+ }
+ }
+ }
+ if (foundChain != null) {
+ break;
+ }
+ index++;
+ }
+ if (foundChain != null) {
+ //first groupkey is the one we want to modify
+ GroupKey modGroupKey = foundChain.peekFirst();
+ Group modGroup = groupService.getGroup(deviceId, modGroupKey);
+ //second groupkey is the one we wish to remove the reference to
+ GroupKey pointedGroupKey = null;
+ int i = 0;
+ for (GroupKey gk : foundChain) {
+ if (i++ == 1) {
+ pointedGroupKey = gk;
+ break;
+ }
+ }
+ Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
+ GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
+ DefaultTrafficTreatment.builder()
+ .group(pointedGroup.id())
+ .build());
+ GroupBuckets removeBuckets = new GroupBuckets(Collections
+ .singletonList(bucket));
+ log.debug("Removing buckets from group id {} for next id {} in device {}",
+ modGroup.id(), nextObjective.id(), deviceId);
+ groupService.removeBucketsFromGroup(deviceId, modGroupKey,
+ removeBuckets, modGroupKey,
+ nextObjective.appId());
+ //update store
+ allgkeys.remove(index);
+ flowObjectiveStore.putNextGroup(nextObjective.id(),
+ new OfdpaNextGroup(allgkeys, nextObjective));
+ } else {
+ log.warn("Could not find appropriate group-chain for removing bucket"
+ + " for next id {} in dev:{}", nextObjective.id(), deviceId);
+ }
+ }
+
+ /**
+ * Removes all groups in multiple possible group-chains that represent the next
+ * objective.
+ *
+ * @param nextObjective the next objective to remove
+ * @param next the NextGroup that represents the existing group-chain for
+ * this next objective
+ */
+ protected void removeGroup(NextObjective nextObjective, NextGroup next) {
+ List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
+ allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
+ groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
+ flowObjectiveStore.removeNextGroup(nextObjective.id());
+ }
+
+ /**
+ * Processes next element of a group chain. Assumption is that if this
+ * group points to another group, the latter has already been created
+ * and this driver has received notification for it. A second assumption is
+ * that if there is another group waiting for this group then the appropriate
+ * stores already have the information to act upon the notification for the
+ * creation of this group.
+ * <p>
+ * The processing of the GroupChainElement depends on the number of groups
+ * this element is waiting on. For all group types other than SIMPLE, a
+ * GroupChainElement could be waiting on multiple groups.
+ *
+ * @param gce the group chain element to be processed next
+ */
+ private void processGroupChain(GroupChainElem gce) {
+ int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
+ if (waitOnGroups != 0) {
+ log.debug("GCE: {} not ready to be processed", gce);
+ return;
+ }
+ log.debug("GCE: {} ready to be processed", gce);
+ if (gce.addBucketToGroup) {
+ groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
+ gce.groupDescription.appCookie(),
+ gce.groupDescription.buckets(),
+ gce.groupDescription.appCookie(),
+ gce.groupDescription.appId());
+ } else {
+ groupService.addGroup(gce.groupDescription);
+ }
+ }
+
+ private class GroupChecker implements Runnable {
+ @Override
+ public void run() {
+ Set<GroupKey> keys = pendingGroups.keySet().stream()
+ .filter(key -> groupService.getGroup(deviceId, key) != null)
+ .collect(Collectors.toSet());
+ Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
+ .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
+ .collect(Collectors.toSet());
+ keys.addAll(otherkeys);
+
+ keys.stream().forEach(key ->
+ processPendingGroupsOrNextObjectives(key, false));
+ }
+ }
+
+ private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
+ //first check for group chain
+ Set<GroupChainElem> gceSet = pendingGroups.remove(key);
+ if (gceSet != null) {
+ for (GroupChainElem gce : gceSet) {
+ log.info("Group service {} group key {} in device {}. "
+ + "Processing next group in group chain with group id {}",
+ (added) ? "ADDED" : "processed",
+ key, deviceId,
+ Integer.toHexString(gce.groupDescription.givenGroupId()));
+ processGroupChain(gce);
+ }
+ } else {
+ // otherwise chain complete - check for waiting nextObjectives
+ List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
+ if (nextGrpList != null) {
+ pendingNextObjectives.invalidate(key);
+ nextGrpList.forEach(nextGrp -> {
+ log.info("Group service {} group key {} in device:{}. "
+ + "Done implementing next objective: {} <<-->> gid:{}",
+ (added) ? "ADDED" : "processed",
+ key, deviceId, nextGrp.nextObjective().id(),
+ Integer.toHexString(groupService.getGroup(deviceId, key)
+ .givenGroupId()));
+ OFDPA2Pipeline.pass(nextGrp.nextObjective());
+ flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
+ // check if addBuckets waiting for this completion
+ NextObjective pendBkt = pendingBuckets
+ .remove(nextGrp.nextObjective().id());
+ if (pendBkt != null) {
+ addBucketToGroup(pendBkt, nextGrp);
+ }
+ });
+ }
+ }
+ }
+
+ private class InnerGroupListener implements GroupListener {
+ @Override
+ public void event(GroupEvent event) {
+ log.trace("received group event of type {}", event.type());
+ if (event.type() == GroupEvent.Type.GROUP_ADDED) {
+ GroupKey key = event.subject().appCookie();
+ processPendingGroupsOrNextObjectives(key, true);
+ }
+ }
+ }
+
+ /**
+ * Utility class for moving group information around.
+ */
+ private class GroupInfo {
+ private GroupDescription innerGrpDesc;
+ private GroupDescription outerGrpDesc;
+
+ GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) {
+ this.innerGrpDesc = innerGrpDesc;
+ this.outerGrpDesc = outerGrpDesc;
+ }
+ }
+
+ /**
+ * Represents an entire group-chain that implements a Next-Objective from
+ * the application. The objective is represented as a list of deques, where
+ * each deque is a separate chain of groups.
+ * <p>
+ * For example, an ECMP group with 3 buckets, where each bucket points to
+ * a group chain of L3 Unicast and L2 interface groups will look like this:
+ * <ul>
+ * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+ * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+ * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+ * </ul>
+ * where the first element of each deque is the same, representing the
+ * top level ECMP group, while every other element represents a unique groupKey.
+ * <p>
+ * Also includes information about the next objective that
+ * resulted in this group-chain.
+ *
+ */
+ protected class OfdpaNextGroup implements NextGroup {
+ private final NextObjective nextObj;
+ private final List<Deque<GroupKey>> gkeys;
+
+ public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
+ this.gkeys = gkeys;
+ this.nextObj = nextObj;
+ }
+
+ @SuppressWarnings("unused")
+ public List<Deque<GroupKey>> groupKey() {
+ return gkeys;
+ }
+
+ public NextObjective nextObjective() {
+ return nextObj;
+ }
+
+ @Override
+ public byte[] data() {
+ return OFDPA2Pipeline.appKryo.serialize(gkeys);
+ }
+ }
+
+ /**
+ * Represents a group element that is part of a chain of groups.
+ * Stores enough information to create a Group Description to add the group
+ * to the switch by requesting the Group Service. Objects instantiating this
+ * class are meant to be temporary and live as long as it is needed to wait for
+ * preceding groups in the group chain to be created.
+ */
+ private class GroupChainElem {
+ private GroupDescription groupDescription;
+ private AtomicInteger waitOnGroups;
+ private boolean addBucketToGroup;
+
+ GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
+ boolean addBucketToGroup) {
+ this.groupDescription = groupDescription;
+ this.waitOnGroups = new AtomicInteger(waitOnGroups);
+ this.addBucketToGroup = addBucketToGroup;
+ }
+
+ /**
+ * This methods atomically decrements the counter for the number of
+ * groups this GroupChainElement is waiting on, for notifications from
+ * the Group Service. When this method returns a value of 0, this
+ * GroupChainElement is ready to be processed.
+ *
+ * @return integer indication of the number of notifications being waited on
+ */
+ int decrementAndGetGroupsWaitedOn() {
+ return waitOnGroups.decrementAndGet();
+ }
+
+ @Override
+ public String toString() {
+ return (Integer.toHexString(groupDescription.givenGroupId()) +
+ " groupKey: " + groupDescription.appCookie() +
+ " waiting-on-groups: " + waitOnGroups.get() +
+ " addBucketToGroup: " + addBucketToGroup +
+ " device: " + deviceId);
+ }
+ }
+}
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
index cb1a650..c507e9c 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
@@ -15,7 +15,6 @@
*/
package org.onosproject.driver.pipeline;
-import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.ArrayDeque;
@@ -24,25 +23,17 @@
import java.util.Collections;
import java.util.Deque;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
-import org.onlab.packet.MplsLabel;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
-import org.onosproject.core.DefaultGroupId;
+import org.onosproject.driver.pipeline.OFDPA2GroupHandler.OfdpaNextGroup;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
@@ -62,7 +53,6 @@
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.criteria.Criterion;
-import org.onosproject.net.flow.criteria.Criterion.Type;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
@@ -74,8 +64,6 @@
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.FlowObjectiveStore;
@@ -83,31 +71,18 @@
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveError;
-import org.onosproject.net.group.DefaultGroupBucket;
-import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
-import org.onosproject.net.group.GroupBucket;
-import org.onosproject.net.group.GroupBuckets;
-import org.onosproject.net.group.GroupDescription;
-import org.onosproject.net.group.GroupEvent;
import org.onosproject.net.group.GroupKey;
-import org.onosproject.net.group.GroupListener;
import org.onosproject.net.group.GroupService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.slf4j.Logger;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.RemovalCause;
-import com.google.common.cache.RemovalNotification;
-
/**
* Driver for Broadcom's OF-DPA v2.0 TTP.
*
*/
public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeliner {
-
protected static final int PORT_TABLE = 0;
protected static final int VLAN_TABLE = 10;
protected static final int TMAC_TABLE = 20;
@@ -124,22 +99,6 @@
protected static final int DEFAULT_PRIORITY = 0x8000;
protected static final int LOWEST_PRIORITY = 0x0;
- /*
- * OFDPA requires group-id's to have a certain form.
- * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
- * L3 Unicast Groups have <4bits-2><28bits-index>
- * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
- * L3 ECMP Groups have <4bits-7><28bits-index>
- * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
- * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
- */
- private static final int L2INTERFACEMASK = 0x0;
- private static final int L3UNICASTMASK = 0x20000000;
- private static final int MPLSINTERFACEMASK = 0x90000000;
- private static final int L3ECMPMASK = 0x70000000;
- private static final int L2FLOODMASK = 0x40000000;
- private static final int L3VPNMASK = 0x92000000;
-
private final Logger log = getLogger(getClass());
private ServiceDirectory serviceDirectory;
protected FlowRuleService flowRuleService;
@@ -149,7 +108,7 @@
protected DeviceId deviceId;
protected ApplicationId driverId;
protected DeviceService deviceService;
- protected KryoNamespace appKryo = new KryoNamespace.Builder()
+ protected static KryoNamespace appKryo = new KryoNamespace.Builder()
.register(KryoNamespaces.API)
.register(GroupKey.class)
.register(DefaultGroupKey.class)
@@ -158,67 +117,36 @@
.register(ArrayDeque.class)
.build();
- private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
- private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
+ protected OFDPA2GroupHandler ofdpa2GroupHandler;
- private ScheduledExecutorService groupChecker =
- Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner",
- "ofdpa2-%d"));
private Set<IPCriterion> sentIpFilters = Collections.newSetFromMap(
- new ConcurrentHashMap<IPCriterion, Boolean>());
-
- // local stores for port-vlan mapping
- Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<PortNumber, VlanId>();
- Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<VlanId,
- Set<PortNumber>>();
-
- // local store for pending bucketAdds - by design there can only be one
- // pending bucket for a group
- ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
-
- // index number for group creation
- AtomicInteger l3vpnindex = new AtomicInteger(0);
-
+ new ConcurrentHashMap<>());
@Override
public void init(DeviceId deviceId, PipelinerContext context) {
this.serviceDirectory = context.directory();
this.deviceId = deviceId;
- pendingNextObjectives = CacheBuilder.newBuilder()
- .expireAfterWrite(20, TimeUnit.SECONDS)
- .removalListener((
- RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
- if (notification.getCause() == RemovalCause.EXPIRED) {
- notification.getValue().forEach(ofdpaNextGrp ->
- fail(ofdpaNextGrp.nextObj,
- ObjectiveError.GROUPINSTALLATIONFAILED));
-
- }
- }).build();
-
- groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
- pendingGroups = new ConcurrentHashMap<GroupKey, Set<GroupChainElem>>();
+ // Initialize OFDPA group handler
+ ofdpa2GroupHandler = new OFDPA2GroupHandler();
+ ofdpa2GroupHandler.init(deviceId, context);
coreService = serviceDirectory.get(CoreService.class);
flowRuleService = serviceDirectory.get(FlowRuleService.class);
groupService = serviceDirectory.get(GroupService.class);
flowObjectiveStore = context.store();
deviceService = serviceDirectory.get(DeviceService.class);
- groupService.addListener(new InnerGroupListener());
driverId = coreService.registerApplication(
"org.onosproject.driver.OFDPA2Pipeline");
- // OF-DPA does not require initializing the pipeline as it puts default
- // rules automatically in the hardware. However emulation of OFDPA in
- // software switches does require table-miss-entries.
initializePipeline();
-
}
protected void initializePipeline() {
-
+ // OF-DPA does not require initializing the pipeline as it puts default
+ // rules automatically in the hardware. However emulation of OFDPA in
+ // software switches does require table-miss-entries.
}
//////////////////////////////////////
@@ -288,19 +216,19 @@
}
log.debug("Processing NextObjective id{} in dev{} - add group",
nextObjective.id(), deviceId);
- addGroup(nextObjective);
+ ofdpa2GroupHandler.addGroup(nextObjective);
break;
case ADD_TO_EXISTING:
if (nextGroup != null) {
log.debug("Processing NextObjective id{} in dev{} - add bucket",
nextObjective.id(), deviceId);
- addBucketToGroup(nextObjective, nextGroup);
+ ofdpa2GroupHandler.addBucketToGroup(nextObjective, nextGroup);
} else {
// it is possible that group-chain has not been fully created yet
log.debug("Waiting to add bucket to group for next-id:{} in dev:{}",
nextObjective.id(), deviceId);
// by design only one pending bucket is allowed for the group
- pendingBuckets.put(nextObjective.id(), nextObjective);
+ ofdpa2GroupHandler.pendingBuckets.put(nextObjective.id(), nextObjective);
}
break;
case REMOVE:
@@ -311,7 +239,7 @@
}
log.debug("Processing NextObjective id{} in dev{} - remove group",
nextObjective.id(), deviceId);
- removeGroup(nextObjective, nextGroup);
+ ofdpa2GroupHandler.removeGroup(nextObjective, nextGroup);
break;
case REMOVE_FROM_EXISTING:
if (nextGroup == null) {
@@ -321,7 +249,7 @@
}
log.debug("Processing NextObjective id{} in dev{} - remove bucket",
nextObjective.id(), deviceId);
- removeBucketFromGroup(nextObjective, nextGroup);
+ ofdpa2GroupHandler.removeBucketFromGroup(nextObjective, nextGroup);
break;
default:
log.warn("Unsupported operation {}", nextObjective.op());
@@ -514,13 +442,13 @@
for (PortNumber pnum : portnums) {
// update storage
- port2Vlan.put(pnum, storeVlan);
- Set<PortNumber> vlanPorts = vlan2Port.get(storeVlan);
+ ofdpa2GroupHandler.port2Vlan.put(pnum, storeVlan);
+ Set<PortNumber> vlanPorts = ofdpa2GroupHandler.vlan2Port.get(storeVlan);
if (vlanPorts == null) {
vlanPorts = Collections.newSetFromMap(
new ConcurrentHashMap<PortNumber, Boolean>());
vlanPorts.add(pnum);
- vlan2Port.put(storeVlan, vlanPorts);
+ ofdpa2GroupHandler.vlan2Port.put(storeVlan, vlanPorts);
} else {
vlanPorts.add(pnum);
}
@@ -711,12 +639,9 @@
TrafficSelector selector = fwd.selector();
EthTypeCriterion ethType = (EthTypeCriterion) selector
.getCriterion(Criterion.Type.ETH_TYPE);
- if ((ethType == null) ||
+ return !((ethType == null) ||
((ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
- (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST))) {
- return false;
- }
- return true;
+ (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)));
}
private boolean isSupportedEthDstObjective(ForwardingObjective fwd) {
@@ -725,10 +650,7 @@
.getCriterion(Criterion.Type.ETH_DST);
VlanIdCriterion vlanId = (VlanIdCriterion) selector
.getCriterion(Criterion.Type.VLAN_VID);
- if (ethDst == null && vlanId == null) {
- return false;
- }
- return true;
+ return !(ethDst == null && vlanId == null);
}
/**
@@ -742,7 +664,7 @@
EthTypeCriterion ethType =
(EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
- int forTableId = -1;
+ int forTableId;
TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder();
if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) {
filteredSelector.matchEthType(Ethernet.TYPE_IPV4)
@@ -926,1053 +848,15 @@
return null;
}
- private void pass(Objective obj) {
+ protected static void pass(Objective obj) {
if (obj.context().isPresent()) {
obj.context().get().onSuccess(obj);
}
}
- protected void fail(Objective obj, ObjectiveError error) {
+ protected static void fail(Objective obj, ObjectiveError error) {
if (obj.context().isPresent()) {
obj.context().get().onError(obj, error);
}
}
-
- //////////////////////////////////////
- // Group handling
- //////////////////////////////////////
-
- private void addGroup(NextObjective nextObjective) {
- switch (nextObjective.type()) {
- case SIMPLE:
- Collection<TrafficTreatment> treatments = nextObjective.next();
- if (treatments.size() != 1) {
- log.error("Next Objectives of type Simple should only have a "
- + "single Traffic Treatment. Next Objective Id:{}",
- nextObjective.id());
- fail(nextObjective, ObjectiveError.BADPARAMS);
- return;
- }
- processSimpleNextObjective(nextObjective);
- break;
- case BROADCAST:
- processBroadcastNextObjective(nextObjective);
- break;
- case HASHED:
- processHashedNextObjective(nextObjective);
- break;
- case FAILOVER:
- fail(nextObjective, ObjectiveError.UNSUPPORTED);
- log.warn("Unsupported next objective type {}", nextObjective.type());
- break;
- default:
- fail(nextObjective, ObjectiveError.UNKNOWN);
- log.warn("Unknown next objective type {}", nextObjective.type());
- }
- }
-
- /**
- * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
- * a chain of groups. The simple Next Objective passed
- * in by the application has to be broken up into a group chain
- * comprising of an L3 Unicast Group that points to an L2 Interface
- * Group which in-turn points to an output port. In some cases, the simple
- * next Objective can just be an L2 interface without the need for chaining.
- *
- * @param nextObj the nextObjective of type SIMPLE
- */
- private void processSimpleNextObjective(NextObjective nextObj) {
- TrafficTreatment treatment = nextObj.next().iterator().next();
- // determine if plain L2 or L3->L2
- boolean plainL2 = true;
- for (Instruction ins : treatment.allInstructions()) {
- if (ins.type() == Instruction.Type.L2MODIFICATION) {
- L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
- if (l2ins.subtype() == L2SubType.ETH_DST ||
- l2ins.subtype() == L2SubType.ETH_SRC) {
- plainL2 = false;
- break;
- }
- }
- }
-
- if (plainL2) {
- createL2InterfaceGroup(nextObj);
- return;
- }
-
- // break up simple next objective to GroupChain objects
- GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
- nextObj.appId(), false,
- nextObj.meta());
- if (groupInfo == null) {
- log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
- return;
- }
- // create object for local and distributed storage
- Deque<GroupKey> gkeyChain = new ArrayDeque<>();
- gkeyChain.addFirst(groupInfo.innerGrpDesc.appCookie());
- gkeyChain.addFirst(groupInfo.outerGrpDesc.appCookie());
- OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
- Collections.singletonList(gkeyChain),
- nextObj);
-
- // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
- updatePendingNextObjective(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp);
-
- // now we are ready to send the l2 groupDescription (inner), as all the stores
- // that will get async replies have been updated. By waiting to update
- // the stores, we prevent nasty race conditions.
- groupService.addGroup(groupInfo.innerGrpDesc);
- }
-
- private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
- List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
- nextList.add(value);
- List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
- .putIfAbsent(key, nextList);
- if (ret != null) {
- ret.add(value);
- }
- }
-
- private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
- Set<GroupChainElem> gceSet = Collections.newSetFromMap(
- new ConcurrentHashMap<GroupChainElem, Boolean>());
- gceSet.add(gce);
- Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
- if (retval != null) {
- retval.add(gce);
- }
- }
-
- /**
- * Creates a simple L2 Interface Group.
- *
- * @param nextObj the next Objective
- */
- private void createL2InterfaceGroup(NextObjective nextObj) {
- // only allowed actions are vlan pop and outport
- TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
- PortNumber portNum = null;
- for (Instruction ins : nextObj.next().iterator().next().allInstructions()) {
- if (ins.type() == Instruction.Type.L2MODIFICATION) {
- L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
- switch (l2ins.subtype()) {
- case VLAN_POP:
- ttb.add(l2ins);
- break;
- default:
- break;
- }
- } else if (ins.type() == Instruction.Type.OUTPUT) {
- portNum = ((OutputInstruction) ins).port();
- ttb.add(ins);
- } else {
- log.warn("Driver does not handle this type of TrafficTreatment"
- + " instruction in simple nextObjectives: {}", ins.type());
- }
- }
- //use the vlanid associated with the port
- VlanId vlanid = port2Vlan.get(portNum);
-
- if (vlanid == null && nextObj.meta() != null) {
- // use metadata vlan info if available
- Criterion vidCriterion = nextObj.meta().getCriterion(Type.VLAN_VID);
- if (vidCriterion != null) {
- vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
- }
- }
-
- if (vlanid == null) {
- log.error("Driver cannot process an L2/L3 group chain without "
- + "egress vlan information for dev: {} port:{}",
- deviceId, portNum);
- return;
- }
-
- // assemble information for ofdpa l2interface group
- Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum.toLong();
- // a globally unique groupkey that is different for ports in the same devices
- // but different for the same portnumber on different devices. Also different
- // for the various group-types created out of the same next objective.
- int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
- final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
-
- // create group description for the l2interfacegroup
- GroupBucket l2interfaceGroupBucket =
- DefaultGroupBucket.createIndirectGroupBucket(ttb.build());
- GroupDescription l2groupDescription =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- l2interfaceGroupBucket)),
- l2groupkey,
- l2groupId,
- nextObj.appId());
- log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
- deviceId, Integer.toHexString(l2groupId),
- l2groupkey, nextObj.id());
-
- // create object for local and distributed storage
- Deque<GroupKey> singleKey = new ArrayDeque<>();
- singleKey.addFirst(l2groupkey);
- OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
- Collections.singletonList(singleKey),
- nextObj);
-
- // store l2groupkey for the nextObjective that depends on it
- updatePendingNextObjective(l2groupkey, ofdpaGrp);
- // send the group description to the group service
- groupService.addGroup(l2groupDescription);
- }
-
- /**
- * Creates one of two possible group-chains from the treatment
- * passed in. Depending on the MPLS boolean, this method either creates
- * an L3Unicast Group --> L2Interface Group, if mpls is false;
- * or MPLSInterface Group --> L2Interface Group, if mpls is true;
- * The returned 'inner' group description is always the L2 Interface group.
- *
- * @param treatment that needs to be broken up to create the group chain
- * @param nextId of the next objective that needs this group chain
- * @param appId of the application that sent this next objective
- * @param mpls determines if L3Unicast or MPLSInterface group is created
- * @param meta metadata passed in by the application as part of the nextObjective
- * @return GroupInfo containing the GroupDescription of the
- * L2Interface group(inner) and the GroupDescription of the (outer)
- * L3Unicast/MPLSInterface group. May return null if there is an
- * error in processing the chain
- */
- private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
- ApplicationId appId, boolean mpls,
- TrafficSelector meta) {
- // for the l2interface group, get vlan and port info
- // for the outer group, get the src/dst mac, and vlan info
- TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
- TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
- VlanId vlanid = null;
- long portNum = 0;
- boolean setVlan = false, popVlan = false;
- for (Instruction ins : treatment.allInstructions()) {
- if (ins.type() == Instruction.Type.L2MODIFICATION) {
- L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
- switch (l2ins.subtype()) {
- case ETH_DST:
- outerTtb.setEthDst(((ModEtherInstruction) l2ins).mac());
- break;
- case ETH_SRC:
- outerTtb.setEthSrc(((ModEtherInstruction) l2ins).mac());
- break;
- case VLAN_ID:
- vlanid = ((ModVlanIdInstruction) l2ins).vlanId();
- outerTtb.setVlanId(vlanid);
- setVlan = true;
- break;
- case VLAN_POP:
- innerTtb.popVlan();
- popVlan = true;
- break;
- case DEC_MPLS_TTL:
- case MPLS_LABEL:
- case MPLS_POP:
- case MPLS_PUSH:
- case VLAN_PCP:
- case VLAN_PUSH:
- default:
- break;
- }
- } else if (ins.type() == Instruction.Type.OUTPUT) {
- portNum = ((OutputInstruction) ins).port().toLong();
- innerTtb.add(ins);
- } else {
- log.warn("Driver does not handle this type of TrafficTreatment"
- + " instruction in nextObjectives: {}", ins.type());
- }
- }
-
- if (vlanid == null && meta != null) {
- // use metadata if available
- Criterion vidCriterion = meta.getCriterion(Type.VLAN_VID);
- if (vidCriterion != null) {
- vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
- }
- // if vlan is not set, use the vlan in metadata for outerTtb
- if (vlanid != null && !setVlan) {
- outerTtb.setVlanId(vlanid);
- }
- }
-
- if (vlanid == null) {
- log.error("Driver cannot process an L2/L3 group chain without "
- + "egress vlan information for dev: {} port:{}",
- deviceId, portNum);
- return null;
- }
-
- if (!setVlan && !popVlan) {
- // untagged outgoing port
- TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
- temp.popVlan();
- innerTtb.build().allInstructions().forEach(i -> temp.add(i));
- innerTtb = temp;
- }
-
- // assemble information for ofdpa l2interface group
- Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum;
- // a globally unique groupkey that is different for ports in the same devices
- // but different for the same portnumber on different devices. Also different
- // for the various group-types created out of the same next objective.
- int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum);
- final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
-
- // assemble information for outer group
- GroupDescription outerGrpDesc = null;
- if (mpls) {
- // outer group is MPLSInteface
- Integer mplsgroupId = MPLSINTERFACEMASK | (int) portNum;
- // using mplsinterfacemask in groupkey to differentiate from l2interface
- int mplsgk = MPLSINTERFACEMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
- final GroupKey mplsgroupkey = new DefaultGroupKey(appKryo.serialize(mplsgk));
- outerTtb.group(new DefaultGroupId(l2groupId));
- // create the mpls-interface group description to wait for the
- // l2 interface group to be processed
- GroupBucket mplsinterfaceGroupBucket =
- DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
- outerGrpDesc = new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- mplsinterfaceGroupBucket)),
- mplsgroupkey,
- mplsgroupId,
- appId);
- log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
- deviceId, Integer.toHexString(mplsgroupId),
- mplsgroupkey, nextId);
- } else {
- // outer group is L3Unicast
- Integer l3groupId = L3UNICASTMASK | (int) portNum;
- int l3gk = L3UNICASTMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
- final GroupKey l3groupkey = new DefaultGroupKey(appKryo.serialize(l3gk));
- outerTtb.group(new DefaultGroupId(l2groupId));
- // create the l3unicast group description to wait for the
- // l2 interface group to be processed
- GroupBucket l3unicastGroupBucket =
- DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
- outerGrpDesc = new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- l3unicastGroupBucket)),
- l3groupkey,
- l3groupId,
- appId);
- log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
- deviceId, Integer.toHexString(l3groupId),
- l3groupkey, nextId);
- }
-
- // store l2groupkey with the groupChainElem for the outer-group that depends on it
- GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
- updatePendingGroups(l2groupkey, gce);
-
- // create group description for the inner l2interfacegroup
- GroupBucket l2interfaceGroupBucket =
- DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
- GroupDescription l2groupDescription =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- l2interfaceGroupBucket)),
- l2groupkey,
- l2groupId,
- appId);
- log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
- deviceId, Integer.toHexString(l2groupId),
- l2groupkey, nextId);
- return new GroupInfo(l2groupDescription, outerGrpDesc);
-
- }
-
- /**
- * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
- * a chain of groups. The broadcast Next Objective passed in by the application
- * has to be broken up into a group chain comprising of an
- * L2 Flood group whose buckets point to L2 Interface groups.
- *
- * @param nextObj the nextObjective of type BROADCAST
- */
- private void processBroadcastNextObjective(NextObjective nextObj) {
- // break up broadcast next objective to multiple groups
- Collection<TrafficTreatment> buckets = nextObj.next();
-
- // each treatment is converted to an L2 interface group
- VlanId vlanid = null;
- List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>();
- List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
- for (TrafficTreatment treatment : buckets) {
- TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
- PortNumber portNum = null;
- // ensure that the only allowed treatments are pop-vlan and output
- for (Instruction ins : treatment.allInstructions()) {
- if (ins.type() == Instruction.Type.L2MODIFICATION) {
- L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
- switch (l2ins.subtype()) {
- case VLAN_POP:
- newTreatment.add(l2ins);
- break;
- default:
- log.debug("action {} not permitted for broadcast nextObj",
- l2ins.subtype());
- break;
- }
- } else if (ins.type() == Instruction.Type.OUTPUT) {
- portNum = ((OutputInstruction) ins).port();
- newTreatment.add(ins);
- } else {
- log.debug("TrafficTreatment of type {} not permitted in "
- + " broadcast nextObjective", ins.type());
- }
- }
-
- // also ensure that all ports are in the same vlan
- // XXX maybe HA issue here?
- VlanId thisvlanid = port2Vlan.get(portNum);
- if (vlanid == null) {
- vlanid = thisvlanid;
- } else {
- if (!vlanid.equals(thisvlanid)) {
- log.error("Driver requires all ports in a broadcast nextObj "
- + "to be in the same vlan. Different vlans found "
- + "{} and {}. Aborting group creation", vlanid, thisvlanid);
- return;
- }
- }
-
- // assemble info for l2 interface group
- int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
- final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
- Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) |
- (int) portNum.toLong();
- GroupBucket l2interfaceGroupBucket =
- DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
- GroupDescription l2interfaceGroupDescription =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- l2interfaceGroupBucket)),
- l2groupkey,
- l2groupId,
- nextObj.appId());
- log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
- deviceId, Integer.toHexString(l2groupId),
- l2groupkey, nextObj.id());
-
- Deque<GroupKey> gkeyChain = new ArrayDeque<>();
- gkeyChain.addFirst(l2groupkey);
-
- // store the info needed to create this group
- l2interfaceGroupDescs.add(l2interfaceGroupDescription);
- allGroupKeys.add(gkeyChain);
- }
-
- // assemble info for l2 flood group
- Integer l2floodgroupId = L2FLOODMASK | (vlanid.toShort() << 16) | nextObj.id();
- int l2floodgk = L2FLOODMASK | nextObj.id() << 12;
- final GroupKey l2floodgroupkey = new DefaultGroupKey(appKryo.serialize(l2floodgk));
- // collection of group buckets pointing to all the l2 interface groups
- List<GroupBucket> l2floodBuckets = new ArrayList<>();
- for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
- TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
- ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
- GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
- l2floodBuckets.add(abucket);
- }
- // create the l2flood group-description to wait for all the
- // l2interface groups to be processed
- GroupDescription l2floodGroupDescription =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.ALL,
- new GroupBuckets(l2floodBuckets),
- l2floodgroupkey,
- l2floodgroupId,
- nextObj.appId());
- GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
- l2interfaceGroupDescs.size(),
- false);
- log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
- deviceId, Integer.toHexString(l2floodgroupId),
- l2floodgroupkey, nextObj.id());
-
- // create objects for local and distributed storage
- allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey));
- OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
-
- // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
- // that depends on it
- updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
-
- for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
- // store all l2groupkeys with the groupChainElem for the l2floodgroup
- // that depends on it
- updatePendingGroups(l2intGrpDesc.appCookie(), gce);
- // send groups for all l2 interface groups
- groupService.addGroup(l2intGrpDesc);
- }
- }
-
- /**
- * Utility class for moving group information around.
- *
- */
- private class GroupInfo {
- private GroupDescription innerGrpDesc;
- private GroupDescription outerGrpDesc;
-
- GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) {
- this.innerGrpDesc = innerGrpDesc;
- this.outerGrpDesc = outerGrpDesc;
- }
- }
-
- /**
- * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
- * a chain of groups. The hashed Next Objective passed in by the application
- * has to be broken up into a group chain comprising of an
- * L3 ECMP group as the top level group. Buckets of this group can point
- * to a variety of groups in a group chain, depending on the whether
- * MPLS labels are being pushed or not.
- * <p>
- * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
- * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
- * check the nextObjective meta to see what is matching before being
- * sent to this nextObjective.
- *
- * @param nextObj the nextObjective of type HASHED
- */
- private void processHashedNextObjective(NextObjective nextObj) {
- // storage for all group keys in the chain of groups created
- List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
- List<GroupInfo> unsentGroups = new ArrayList<>();
- createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
-
- // now we can create the outermost L3 ECMP group
- List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
- for (GroupInfo gi : unsentGroups) {
- // create ECMP bucket to point to the outer group
- TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
- ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
- GroupBucket sbucket = DefaultGroupBucket
- .createSelectGroupBucket(ttb.build());
- l3ecmpGroupBuckets.add(sbucket);
- }
- int l3ecmpGroupId = L3ECMPMASK | nextObj.id() << 12;
- GroupKey l3ecmpGroupKey = new DefaultGroupKey(appKryo.serialize(l3ecmpGroupId));
- GroupDescription l3ecmpGroupDesc =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.SELECT,
- new GroupBuckets(l3ecmpGroupBuckets),
- l3ecmpGroupKey,
- l3ecmpGroupId,
- nextObj.appId());
- GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
- l3ecmpGroupBuckets.size(),
- false);
-
- // create objects for local and distributed storage
- allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
- OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
-
- // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
- // that depends on it
- updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
-
- log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
- deviceId, Integer.toHexString(l3ecmpGroupId),
- l3ecmpGroupKey, nextObj.id());
- // finally we are ready to send the innermost groups
- for (GroupInfo gi : unsentGroups) {
- log.debug("Sending innermost group {} in group chain on device {} ",
- Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
- updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
- groupService.addGroup(gi.innerGrpDesc);
- }
-
- }
-
- /**
- * Creates group chains for all buckets in a hashed group, and stores the
- * GroupInfos and GroupKeys for all the groups in the lists passed in, which
- * should be empty.
- * <p>
- * Does not create the top level ECMP group. Does not actually send the
- * groups to the groupService.
- *
- * @param nextObj the Next Objective with buckets that need to be converted
- * to group chains
- * @param allGroupKeys a list to store groupKey for each bucket-group-chain
- * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
- */
- private void createHashBucketChains(NextObjective nextObj,
- List<Deque<GroupKey>> allGroupKeys,
- List<GroupInfo> unsentGroups) {
- // break up hashed next objective to multiple groups
- Collection<TrafficTreatment> buckets = nextObj.next();
-
- for (TrafficTreatment bucket : buckets) {
- //figure out how many labels are pushed in each bucket
- int labelsPushed = 0;
- MplsLabel innermostLabel = null;
- for (Instruction ins : bucket.allInstructions()) {
- if (ins.type() == Instruction.Type.L2MODIFICATION) {
- L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
- if (l2ins.subtype() == L2SubType.MPLS_PUSH) {
- labelsPushed++;
- }
- if (l2ins.subtype() == L2SubType.MPLS_LABEL) {
- if (innermostLabel == null) {
- innermostLabel = ((ModMplsLabelInstruction) l2ins).mplsLabel();
- }
- }
- }
- }
-
- Deque<GroupKey> gkeyChain = new ArrayDeque<>();
- // XXX we only deal with 0 and 1 label push right now
- if (labelsPushed == 0) {
- GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
- nextObj.appId(), false,
- nextObj.meta());
- if (nolabelGroupInfo == null) {
- log.error("Could not process nextObj={} in dev:{}",
- nextObj.id(), deviceId);
- return;
- }
- gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie());
- gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie());
-
- // we can't send the inner group description yet, as we have to
- // create the dependent ECMP group first. So we store..
- unsentGroups.add(nolabelGroupInfo);
-
- } else if (labelsPushed == 1) {
- GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
- nextObj.appId(), true,
- nextObj.meta());
- if (onelabelGroupInfo == null) {
- log.error("Could not process nextObj={} in dev:{}",
- nextObj.id(), deviceId);
- return;
- }
- // we need to add another group to this chain - the L3VPN group
- TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
- l3vpnTtb.pushMpls()
- .setMpls(innermostLabel)
- .setMplsBos(true)
- .copyTtlOut()
- .group(new DefaultGroupId(
- onelabelGroupInfo.outerGrpDesc.givenGroupId()));
- GroupBucket l3vpnGrpBkt =
- DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
- int l3vpngroupId = L3VPNMASK | l3vpnindex.incrementAndGet();
- int l3vpngk = L3VPNMASK | nextObj.id() << 12 | l3vpnindex.get();
- GroupKey l3vpngroupkey = new DefaultGroupKey(appKryo.serialize(l3vpngk));
- GroupDescription l3vpnGroupDesc =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.INDIRECT,
- new GroupBuckets(Collections.singletonList(
- l3vpnGrpBkt)),
- l3vpngroupkey,
- l3vpngroupId,
- nextObj.appId());
- GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
- updatePendingGroups(onelabelGroupInfo.outerGrpDesc.appCookie(), l3vpnGce);
-
- gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie());
- gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie());
- gkeyChain.addFirst(l3vpngroupkey);
-
- //now we can replace the outerGrpDesc with the one we just created
- onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc;
-
- // we can't send the innermost group yet, as we have to create
- // the dependent ECMP group first. So we store ...
- unsentGroups.add(onelabelGroupInfo);
-
- log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
- deviceId, Integer.toHexString(l3vpngroupId),
- l3vpngroupkey, nextObj.id());
-
- } else {
- log.warn("Driver currently does not handle more than 1 MPLS "
- + "labels. Not processing nextObjective {}", nextObj.id());
- return;
- }
-
- // all groups in this chain
- allGroupKeys.add(gkeyChain);
- }
- }
-
- /**
- * Adds a bucket to the top level group of a group-chain, and creates the chain.
- *
- * @param nextObjective the next group to add a bucket to
- * @param next the representation of the existing group-chain for this next objective
- */
- private void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
- if (nextObjective.type() != NextObjective.Type.HASHED) {
- log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
- nextObjective.type(), deviceId, nextObjective.id());
- return;
- }
- if (nextObjective.next().size() > 1) {
- log.warn("Only one bucket can be added at a time");
- return;
- }
- // storage for all group keys in the chain of groups created
- List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
- List<GroupInfo> unsentGroups = new ArrayList<>();
- createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
-
- // now we can create the outermost L3 ECMP group bucket to add
- GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
- TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
- ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
- GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
-
- // recreate the original L3 ECMP group id and description
- int l3ecmpGroupId = L3ECMPMASK | nextObjective.id() << 12;
- GroupKey l3ecmpGroupKey = new DefaultGroupKey(appKryo.serialize(l3ecmpGroupId));
-
- // Although GroupDescriptions are not necessary for adding buckets to
- // existing groups, we use one in the GroupChainElem. When the latter is
- // processed, the info will be extracted for the bucketAdd call to groupService
- GroupDescription l3ecmpGroupDesc =
- new DefaultGroupDescription(
- deviceId,
- GroupDescription.Type.SELECT,
- new GroupBuckets(Collections.singletonList(sbucket)),
- l3ecmpGroupKey,
- l3ecmpGroupId,
- nextObjective.appId());
- GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
-
- // update original NextGroup with new bucket-chain
- // don't need to update pendingNextObjectives -- group already exists
- Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
- newBucketChain.addFirst(l3ecmpGroupKey);
- List<Deque<GroupKey>> allOriginalKeys = appKryo.deserialize(next.data());
- allOriginalKeys.add(newBucketChain);
- flowObjectiveStore.putNextGroup(nextObjective.id(),
- new OfdpaNextGroup(allOriginalKeys, nextObjective));
-
- log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
- deviceId, Integer.toHexString(l3ecmpGroupId),
- l3ecmpGroupKey, nextObjective.id());
- // send the innermost group
- log.debug("Sending innermost group {} in group chain on device {} ",
- Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
- updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
- groupService.addGroup(gi.innerGrpDesc);
-
- }
-
- /**
- * Removes the bucket in the top level group of a possible group-chain. Does
- * not remove the groups in a group-chain pointed to by this bucket, as they
- * may be in use (referenced by other groups) elsewhere.
- *
- * @param nextObjective the next group to remove a bucket from
- * @param next the representation of the existing group-chain for this next objective
- */
- private void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
- if (nextObjective.type() != NextObjective.Type.HASHED) {
- log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
- nextObjective.type(), deviceId, nextObjective.id());
- return;
- }
- Collection<TrafficTreatment> treatments = nextObjective.next();
- TrafficTreatment treatment = treatments.iterator().next();
- // find the bucket to remove by noting the outport, and figuring out the
- // top-level group in the group-chain that indirectly references the port
- PortNumber outport = null;
- for (Instruction ins : treatment.allInstructions()) {
- if (ins instanceof OutputInstruction) {
- outport = ((OutputInstruction) ins).port();
- break;
- }
- }
- if (outport == null) {
- log.error("next objective {} has no outport", nextObjective.id());
- return;
- }
-
- List<Deque<GroupKey>> allgkeys = appKryo.deserialize(next.data());
- Deque<GroupKey> foundChain = null;
- int index = 0;
- for (Deque<GroupKey> gkeys : allgkeys) {
- GroupKey groupWithPort = gkeys.peekLast();
- Group group = groupService.getGroup(deviceId, groupWithPort);
- if (group == null) {
- log.warn("Inconsistent group chain");
- continue;
- }
- // last group in group chain should have a single bucket pointing to port
- List<Instruction> lastIns = group.buckets().buckets().iterator()
- .next().treatment().allInstructions();
- for (Instruction i : lastIns) {
- if (i instanceof OutputInstruction) {
- PortNumber lastport = ((OutputInstruction) i).port();
- if (lastport.equals(outport)) {
- foundChain = gkeys;
- break;
- }
- }
- }
- if (foundChain != null) {
- break;
- }
- index++;
- }
- if (foundChain != null) {
- //first groupkey is the one we want to modify
- GroupKey modGroupKey = foundChain.peekFirst();
- Group modGroup = groupService.getGroup(deviceId, modGroupKey);
- //second groupkey is the one we wish to remove the reference to
- GroupKey pointedGroupKey = null;
- int i = 0;
- for (GroupKey gk : foundChain) {
- if (i++ == 1) {
- pointedGroupKey = gk;
- break;
- }
- }
- Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
- GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
- DefaultTrafficTreatment.builder()
- .group(pointedGroup.id())
- .build());
- GroupBuckets removeBuckets = new GroupBuckets(Collections
- .singletonList(bucket));
- log.debug("Removing buckets from group id {} for next id {} in device {}",
- modGroup.id(), nextObjective.id(), deviceId);
- groupService.removeBucketsFromGroup(deviceId, modGroupKey,
- removeBuckets, modGroupKey,
- nextObjective.appId());
- //update store
- allgkeys.remove(index);
- flowObjectiveStore.putNextGroup(nextObjective.id(),
- new OfdpaNextGroup(allgkeys, nextObjective));
- } else {
- log.warn("Could not find appropriate group-chain for removing bucket"
- + " for next id {} in dev:{}", nextObjective.id(), deviceId);
- }
- }
-
- /**
- * Removes all groups in multiple possible group-chains that represent the next
- * objective.
- *
- * @param nextObjective the next objective to remove
- * @param next the NextGroup that represents the existing group-chain for
- * this next objective
- */
- private void removeGroup(NextObjective nextObjective, NextGroup next) {
- List<Deque<GroupKey>> allgkeys = appKryo.deserialize(next.data());
- allgkeys.forEach(groupChain -> {
- groupChain.forEach(groupKey ->
- groupService.removeGroup(deviceId, groupKey, nextObjective.appId()));
- });
- flowObjectiveStore.removeNextGroup(nextObjective.id());
- }
-
- /**
- * Processes next element of a group chain. Assumption is that if this
- * group points to another group, the latter has already been created
- * and this driver has received notification for it. A second assumption is
- * that if there is another group waiting for this group then the appropriate
- * stores already have the information to act upon the notification for the
- * creation of this group.
- * <p>
- * The processing of the GroupChainElement depends on the number of groups
- * this element is waiting on. For all group types other than SIMPLE, a
- * GroupChainElement could be waiting on multiple groups.
- *
- * @param gce the group chain element to be processed next
- */
- private void processGroupChain(GroupChainElem gce) {
- int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
- if (waitOnGroups != 0) {
- log.debug("GCE: {} not ready to be processed", gce);
- return;
- }
- log.debug("GCE: {} ready to be processed", gce);
- if (gce.addBucketToGroup) {
- groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
- gce.groupDescription.appCookie(),
- gce.groupDescription.buckets(),
- gce.groupDescription.appCookie(),
- gce.groupDescription.appId());
- } else {
- groupService.addGroup(gce.groupDescription);
- }
- }
-
- private class GroupChecker implements Runnable {
- @Override
- public void run() {
- Set<GroupKey> keys = pendingGroups.keySet().stream()
- .filter(key -> groupService.getGroup(deviceId, key) != null)
- .collect(Collectors.toSet());
- Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
- .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
- .collect(Collectors.toSet());
- keys.addAll(otherkeys);
-
- keys.stream().forEach(key ->
- processPendingGroupsOrNextObjectives(key, false));
- }
- }
-
- private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
- //first check for group chain
- Set<GroupChainElem> gceSet = pendingGroups.remove(key);
- if (gceSet != null) {
- for (GroupChainElem gce : gceSet) {
- log.info("Group service {} group key {} in device {}. "
- + "Processing next group in group chain with group id {}",
- (added) ? "ADDED" : "processed",
- key, deviceId,
- Integer.toHexString(gce.groupDescription.givenGroupId()));
- processGroupChain(gce);
- }
- } else {
- // otherwise chain complete - check for waiting nextObjectives
- List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
- if (nextGrpList != null) {
- pendingNextObjectives.invalidate(key);
- nextGrpList.forEach(nextGrp -> {
- log.info("Group service {} group key {} in device:{}. "
- + "Done implementing next objective: {} <<-->> gid:{}",
- (added) ? "ADDED" : "processed",
- key, deviceId, nextGrp.nextObjective().id(),
- Integer.toHexString(groupService.getGroup(deviceId, key)
- .givenGroupId()));
- pass(nextGrp.nextObjective());
- flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
- // check if addBuckets waiting for this completion
- NextObjective pendBkt = pendingBuckets
- .remove(nextGrp.nextObjective().id());
- if (pendBkt != null) {
- addBucketToGroup(pendBkt, nextGrp);
- }
- });
- }
- }
- }
-
- private class InnerGroupListener implements GroupListener {
- @Override
- public void event(GroupEvent event) {
- log.trace("received group event of type {}", event.type());
- if (event.type() == GroupEvent.Type.GROUP_ADDED) {
- GroupKey key = event.subject().appCookie();
- processPendingGroupsOrNextObjectives(key, true);
- }
- }
- }
-
- /**
- * Represents an entire group-chain that implements a Next-Objective from
- * the application. The objective is represented as a list of deques, where
- * each deque is a separate chain of groups.
- * <p>
- * For example, an ECMP group with 3 buckets, where each bucket points to
- * a group chain of L3 Unicast and L2 interface groups will look like this:
- * <ul>
- * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
- * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
- * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
- * </ul>
- * where the first element of each deque is the same, representing the
- * top level ECMP group, while every other element represents a unique groupKey.
- * <p>
- * Also includes information about the next objective that
- * resulted in this group-chain.
- *
- */
- private class OfdpaNextGroup implements NextGroup {
- private final NextObjective nextObj;
- private final List<Deque<GroupKey>> gkeys;
-
- public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
- this.gkeys = gkeys;
- this.nextObj = nextObj;
- }
-
- @SuppressWarnings("unused")
- public List<Deque<GroupKey>> groupKey() {
- return gkeys;
- }
-
- public NextObjective nextObjective() {
- return nextObj;
- }
-
- @Override
- public byte[] data() {
- return appKryo.serialize(gkeys);
- }
-
- }
-
- /**
- * Represents a group element that is part of a chain of groups.
- * Stores enough information to create a Group Description to add the group
- * to the switch by requesting the Group Service. Objects instantiating this
- * class are meant to be temporary and live as long as it is needed to wait for
- * preceding groups in the group chain to be created.
- */
- private class GroupChainElem {
- private GroupDescription groupDescription;
- private AtomicInteger waitOnGroups;
- private boolean addBucketToGroup;
-
- GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
- boolean addBucketToGroup) {
- this.groupDescription = groupDescription;
- this.waitOnGroups = new AtomicInteger(waitOnGroups);
- this.addBucketToGroup = addBucketToGroup;
- }
-
- /**
- * This methods atomically decrements the counter for the number of
- * groups this GroupChainElement is waiting on, for notifications from
- * the Group Service. When this method returns a value of 0, this
- * GroupChainElement is ready to be processed.
- *
- * @return integer indication of the number of notifications being waited on
- */
- int decrementAndGetGroupsWaitedOn() {
- return waitOnGroups.decrementAndGet();
- }
-
- @Override
- public String toString() {
- return (Integer.toHexString(groupDescription.givenGroupId()) +
- " groupKey: " + groupDescription.appCookie() +
- " waiting-on-groups: " + waitOnGroups.get() +
- " addBucketToGroup: " + addBucketToGroup +
- " device: " + deviceId);
- }
- }
-
}
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
index dba4557..5be8a3f 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
@@ -51,6 +51,7 @@
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.MplsBosCriterion;
import org.onosproject.net.flow.criteria.MplsCriterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
@@ -593,9 +594,10 @@
.matchEthType(Ethernet.MPLS_UNICAST)
.matchMplsLabel(((MplsCriterion)
selector.getCriterion(Criterion.Type.MPLS_LABEL)).label());
- //TODO: Add Match for BoS
- //if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) {
- //}
+ if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) {
+ filteredSelectorBuilder.matchMplsBos(((MplsBosCriterion)
+ selector.getCriterion(Type.MPLS_BOS)).mplsBos());
+ }
forTableId = mplsTableId;
log.debug("processing MPLS specific forwarding objective:{} in dev:{}",
fwd.id(), deviceId);
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
index 91f2679..2b71ff7 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
@@ -34,6 +34,7 @@
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.MplsBosCriterion;
import org.onosproject.net.flow.criteria.MplsCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
@@ -116,9 +117,10 @@
.matchEthType(Ethernet.MPLS_UNICAST)
.matchMplsLabel(((MplsCriterion)
selector.getCriterion(Criterion.Type.MPLS_LABEL)).label());
- //TODO: Add Match for BoS
- //if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) {
- //}
+ if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) {
+ filteredSelectorBuilder.matchMplsBos(((MplsBosCriterion)
+ selector.getCriterion(Criterion.Type.MPLS_BOS)).mplsBos());
+ }
forTableId = mplsTableId;
log.debug("processing MPLS specific forwarding objective");
}