Prevent XConnect loop
Change-Id: I65c52342840ebef944a65b8c6f65a33448da59cf
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index f083d74..a41c58d 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -997,6 +997,11 @@
}
@Override
+ public boolean shouldProgram(DeviceId deviceId) {
+ return defaultRoutingHandler.shouldProgram(deviceId);
+ }
+
+ @Override
public ApplicationId appId() {
return appId;
}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index a29b784..1b28998 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -353,6 +353,14 @@
Map<DeviceId, Boolean> getShouldProgramCache();
/**
+ * Returns whether instance should program device or not.
+ *
+ * @param deviceId .
+ * @return boolean status saying instance should program device or not.
+ */
+ boolean shouldProgram(DeviceId deviceId);
+
+ /**
* Gets application id.
*
* @return application id
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java
index 25f1b01..4e82524 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java
@@ -22,6 +22,7 @@
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
+import java.util.List;
import java.util.Set;
/**
@@ -73,6 +74,24 @@
boolean hasXconnect(ConnectPoint cp);
/**
+ * Gives xconnect VLAN of given port of a device.
+ *
+ * @param deviceId Device ID
+ * @param port Port number
+ * @return true if given VLAN vlanId is XConnect VLAN on device deviceId.
+ */
+ List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port);
+
+ /**
+ * Checks given VLAN is XConnect VLAN in given device.
+ *
+ * @param deviceId Device ID
+ * @param vlanId VLAN ID
+ * @return true if given VLAN vlanId is XConnect VLAN on device deviceId.
+ */
+ boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId);
+
+ /**
* Returns the Xconnect next objective store.
*
* @return current contents of the xconnectNextObjStore
@@ -86,4 +105,12 @@
*/
void removeNextId(int nextId);
+ /**
+ * Returns Xconnect next objective ID associated with group device + vlan.
+ *
+ * @param deviceId - Device ID
+ * @param vlanId - VLAN ID
+ * @return Current associated group ID
+ */
+ int getNextId(DeviceId deviceId, VlanId vlanId);
}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
index a7eb664..b65faa9 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
@@ -24,6 +24,7 @@
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
@@ -33,6 +34,8 @@
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostLocation;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceEvent;
@@ -54,7 +57,12 @@
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intf.InterfaceService;
import org.onosproject.segmentrouting.SegmentRoutingService;
+import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
@@ -70,6 +78,11 @@
import org.slf4j.LoggerFactory;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -107,6 +120,12 @@
@Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY)
public SegmentRoutingService srService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ public InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ HostService hostService;
+
private static final String APP_NAME = "org.onosproject.xconnect";
private static final String ERROR_NOT_MASTER = "Not master controller";
@@ -116,11 +135,18 @@
private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
+ private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
+ private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
+
private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private ExecutorService deviceEventExecutor;
+ private final HostListener hostListener = new InternalHostListener();
+ private ExecutorService hostEventExecutor;
+
+
@Activate
void activate() {
appId = coreService.registerApplication(APP_NAME);
@@ -129,7 +155,8 @@
KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(XconnectManager.class)
- .register(XconnectKey.class);
+ .register(XconnectKey.class)
+ .register(VlanNextObjectiveStoreKey.class);
xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
.withName("onos-sr-xconnect")
@@ -144,11 +171,25 @@
.withSerializer(Serializer.using(serializer.build()))
.build();
+ xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
+ .withName("onos-sr-xconnect-l2multicast-next")
+ .withSerializer(Serializer.using(serializer.build()))
+ .build();
+ xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
+ .withName("onos-sr-xconnect-l2multicast-ports")
+ .withSerializer(Serializer.using(serializer.build()))
+ .build();
+
deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
groupedThreads("sr-xconnect-device-event", "%d", log));
deviceService.addListener(deviceListener);
+ hostEventExecutor = Executors.newSingleThreadExecutor(
+ groupedThreads("sr-xconnect-host-event", "%d", log));
+
+ hostService.addListener(hostListener);
+
log.info("Started");
}
@@ -156,9 +197,11 @@
void deactivate() {
xconnectStore.removeListener(xconnectListener);
deviceService.removeListener(deviceListener);
+ hostService.removeListener(hostListener);
codecService.unregisterCodec(XconnectDesc.class);
deviceEventExecutor.shutdown();
+ hostEventExecutor.shutdown();
log.info("Stopped");
}
@@ -166,7 +209,7 @@
@Override
public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
- deviceId, vlanId, ports);
+ deviceId, vlanId, ports);
final XconnectKey key = new XconnectKey(deviceId, vlanId);
xconnectStore.put(key, ports);
}
@@ -174,9 +217,15 @@
@Override
public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
log.info("Removing xconnect. deviceId={}, vlanId={}",
- deviceId, vlanId);
+ deviceId, vlanId);
final XconnectKey key = new XconnectKey(deviceId, vlanId);
xconnectStore.remove(key);
+
+ // Cleanup multicasting support, if any.
+ srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
+ cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
+ });
+
}
@Override
@@ -189,11 +238,27 @@
@Override
public boolean hasXconnect(ConnectPoint cp) {
return getXconnects().stream().anyMatch(desc ->
- desc.key().deviceId().equals(cp.deviceId()) && desc.ports().contains(cp.port())
+ desc.key().deviceId().equals(cp.deviceId())
+ && desc.ports().contains(cp.port())
);
}
@Override
+ public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
+ return getXconnects().stream()
+ .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
+ .map(XconnectDesc::key)
+ .map(XconnectKey::vlanId)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
+ return getXconnects().stream()
+ .anyMatch(desc -> desc.key().deviceId().equals(deviceId) && desc.key().vlanId().equals(vlanId));
+ }
+
+ @Override
public ImmutableMap<XconnectKey, Integer> getNext() {
if (xconnectNextObjStore != null) {
return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
@@ -203,6 +268,15 @@
}
@Override
+ public int getNextId(final DeviceId deviceId, final VlanId vlanId) {
+ Optional<Integer> nextObjective = getNext().entrySet().stream()
+ .filter(d -> d.getKey().deviceId().equals(deviceId) && d.getKey().vlanId().equals(vlanId))
+ .findFirst()
+ .map(Map.Entry::getValue);
+ return nextObjective.isPresent() ? nextObjective.get() : -1;
+ }
+
+ @Override
public void removeNextId(int nextId) {
xconnectNextObjStore.entrySet().forEach(e -> {
if (e.getValue().value() == nextId) {
@@ -260,6 +334,98 @@
}
}
+ private class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ hostEventExecutor.execute(() -> {
+
+ switch (event.type()) {
+ case HOST_MOVED:
+ log.trace("Processing host event {}", event);
+
+ Host host = event.subject();
+ Set<HostLocation> prevLocations = event.prevSubject().locations();
+ Set<HostLocation> newLocations = host.locations();
+
+ // Dual-home host port failure
+ // For each old location, in failed and paired devices update L2 vlan groups
+ Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
+
+ Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
+ Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
+
+ if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
+ .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
+ hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
+
+ List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
+ prevLocation.port());
+ xconnectVlans.forEach(xconnectVlan -> {
+ // Add single-home host into L2 multicast group at paired device side.
+ // Also append ACL rule to forward traffic from paired port to L2 multicast group.
+ newLocations.stream()
+ .filter(location -> location.deviceId().equals(pairDeviceId.get()))
+ .forEach(location -> populateL2Multicast(location.deviceId(),
+ srService.getPairLocalPort(
+ location.deviceId()).get(),
+ xconnectVlan,
+ Collections.singletonList(
+ location.port())));
+ // Ensure pair-port attached to xconnect vlan flooding group at dual home failed device.
+ updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
+ });
+ }
+ });
+
+ // Dual-home host port restoration
+ // For each new location, reverse xconnect loop prevention groups.
+ Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
+ final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
+ Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
+ if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
+ hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
+
+ List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
+ newLocation.port());
+ xconnectVlans.forEach(xconnectVlan -> {
+ // Remove recovered dual homed port from vlan L2 multicast group
+ prevLocations.stream()
+ .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
+ .forEach(prevLocation -> revokeL2Multicast(prevLocation.deviceId(),
+ srService.getPairLocalPort(
+ prevLocation.deviceId()).get(),
+ xconnectVlan,
+ Collections.singletonList(newLocation.port()))
+ );
+
+ // Remove pair-port from vlan's flooding group at dual home restored device,if needed.
+ if (!hasAccessPortInMulticastGroup(newLocation.deviceId(),
+ xconnectVlan,
+ pairLocalPort.get())) {
+ updateL2Flooding(newLocation.deviceId(),
+ pairLocalPort.get(),
+ xconnectVlan,
+ false);
+
+ // Clean L2 multicast group at pair-device; also update store.
+ cleanupL2MulticastRule(pairDeviceId.get(),
+ srService.getPairLocalPort(pairDeviceId.get()).get(),
+ xconnectVlan,
+ false);
+ }
+ });
+ }
+ });
+ break;
+
+ default:
+ log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
+ break;
+ }
+ });
+ }
+ }
+
private void init(DeviceId deviceId) {
getXconnects().stream()
.filter(desc -> desc.key().deviceId().equals(deviceId))
@@ -276,7 +442,7 @@
/**
* Populates XConnect groups and flows for given key.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param ports a set of ports to be cross-connected
*/
private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
@@ -285,7 +451,6 @@
return;
}
- ports = addPairPort(key.deviceId(), ports);
populateFilter(key, ports);
populateFwd(key, populateNext(key, ports));
populateAcl(key);
@@ -294,7 +459,7 @@
/**
* Populates filtering objectives for given XConnect.
*
- * @param key XConnect store key
+ * @param key XConnect store key
* @param ports XConnect ports
*/
private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
@@ -302,10 +467,10 @@
FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
- key, port),
+ key, port),
(objective, error) ->
log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
- key, port, error));
+ key, port, error));
flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
});
}
@@ -313,7 +478,7 @@
/**
* Populates next objectives for given XConnect.
*
- * @param key XConnect store key
+ * @param key XConnect store key
* @param ports XConnect ports
*/
private int populateNext(XconnectKey key, Set<PortNumber> ports) {
@@ -342,7 +507,7 @@
/**
* Populates bridging forwarding objectives for given XConnect.
*
- * @param key XConnect store key
+ * @param key XConnect store key
* @param nextId next objective id
*/
private void populateFwd(XconnectKey key, int nextId) {
@@ -371,7 +536,7 @@
/**
* Revokes XConnect groups and flows for given key.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param ports XConnect ports
*/
private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
@@ -380,7 +545,6 @@
return;
}
- ports = addPairPort(key.deviceId(), ports);
revokeFilter(key, ports);
if (xconnectNextObjStore.containsKey(key)) {
int nextId = xconnectNextObjStore.get(key).value();
@@ -395,7 +559,7 @@
/**
* Revokes filtering objectives for given XConnect.
*
- * @param key XConnect store key
+ * @param key XConnect store key
* @param ports XConnect ports
*/
private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
@@ -403,10 +567,10 @@
FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
- key, port),
+ key, port),
(objective, error) ->
log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
- key, port, error));
+ key, port, error));
flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
});
}
@@ -414,9 +578,9 @@
/**
* Revokes next objectives for given XConnect.
*
- * @param key XConnect store key
- * @param ports ports in the XConnect
- * @param nextId next objective id
+ * @param key XConnect store key
+ * @param ports ports in the XConnect
+ * @param nextId next objective id
* @param nextFuture completable future for this next objective operation
*/
private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
@@ -446,8 +610,8 @@
/**
* Revokes bridging forwarding objectives for given XConnect.
*
- * @param key XConnect store key
- * @param nextId next objective id
+ * @param key XConnect store key
+ * @param nextId next objective id
* @param fwdFuture completable future for this forwarding objective operation
*/
private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
@@ -489,9 +653,9 @@
/**
* Updates XConnect groups and flows for given key.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param prevPorts previous XConnect ports
- * @param ports new XConnect ports
+ * @param ports new XConnect ports
*/
private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
Set<PortNumber> ports) {
@@ -500,10 +664,12 @@
// remove old filter
prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
- revokeFilter(key, ImmutableSet.of(port)));
+ revokeFilter(key,
+ ImmutableSet.of(port)));
// install new filter
ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
- populateFilter(key, ImmutableSet.of(port)));
+ populateFilter(key,
+ ImmutableSet.of(port)));
CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
@@ -533,8 +699,8 @@
/**
* Creates a next objective builder for XConnect with given nextId.
*
- * @param key XConnect key
- * @param ports set of XConnect ports
+ * @param key XConnect key
+ * @param ports set of XConnect ports
* @param nextId next objective id
* @return next objective builder
*/
@@ -556,7 +722,7 @@
/**
* Creates a next objective builder for XConnect.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param ports set of XConnect ports
* @return next objective builder
*/
@@ -569,7 +735,7 @@
/**
* Creates a bridging forwarding objective builder for XConnect.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param nextId next ID of the broadcast group for this XConnect key
* @return forwarding objective builder
*/
@@ -617,7 +783,7 @@
/**
* Creates a filtering objective builder for XConnect.
*
- * @param key XConnect key
+ * @param key XConnect key
* @param port XConnect ports
* @return next objective builder
*/
@@ -631,18 +797,320 @@
}
/**
- * Add pair port to the given set of port.
+ * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
*
- * @param deviceId device Id
- * @param ports ports specified in the xconnect config
- * @return port specified in the xconnect config plus the pair port (if configured)
+ * @param deviceId Device ID
+ * @param port Port details
+ * @param vlanId VLAN ID
+ * @param install Whether to add or revoke pair link addition to flooding group
*/
- private Set<PortNumber> addPairPort(DeviceId deviceId, Set<PortNumber> ports) {
- if (srService == null) {
- return ports;
+ private void updateL2Flooding(DeviceId deviceId, final PortNumber port, VlanId vlanId, boolean install) {
+
+ // Ensure mastership on device
+ if (!mastershipService.isLocalMaster(deviceId)) {
+ return;
}
- Set<PortNumber> newPorts = Sets.newHashSet(ports);
- srService.getPairLocalPort(deviceId).ifPresent(newPorts::add);
- return newPorts;
+
+ // Locate L2 flooding group details for given xconnect vlan
+ int nextId = getNextId(deviceId, vlanId);
+ if (nextId == -1) {
+ log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
+ "Aborting pair group linking.", vlanId, deviceId);
+ return;
+ }
+
+ // Add pairing-port group to flooding group
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+ // treatment.popVlan();
+ treatment.setOutput(port);
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) ->
+ log.debug("Pair port added/removed to vlan {} next objective {} on {}",
+ vlanId, nextId, deviceId),
+ (objective, error) ->
+ log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
+ "Error : {}", vlanId, nextId, deviceId, error)
+ );
+ NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
+ .withId(nextId)
+ .withType(NextObjective.Type.BROADCAST)
+ .fromApp(srService.appId())
+ .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
+ .addTreatment(treatment.build());
+ if (install) {
+ flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
+ } else {
+ flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
+ }
+ log.debug("Submitted next objective {} for vlan: {} in device {}",
+ nextId, vlanId, deviceId);
}
+
+ /**
+ * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
+ * output to given port's L2 mulitcast group.
+ *
+ * @param deviceId Device ID
+ * @param pairPort Pair port number
+ * @param vlanId VLAN ID
+ * @param accessPorts List of access ports to be added into L2 multicast group
+ */
+ private void populateL2Multicast(DeviceId deviceId, final PortNumber pairPort,
+ VlanId vlanId, List<PortNumber> accessPorts) {
+
+ boolean multicastGroupExists = true;
+ int vlanMulticastNextId;
+
+ // Ensure enough rights to program pair device
+ if (!srService.shouldProgram(deviceId)) {
+ return;
+ }
+
+ // Step 1 : Populate single homed access ports into vlan's L2 multicast group
+ NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
+ .builder()
+ .withType(NextObjective.Type.BROADCAST)
+ .fromApp(srService.appId())
+ .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
+ .matchEthDst(MacAddress.IPV4_MULTICAST).build());
+ vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
+ if (vlanMulticastNextId == -1) {
+ // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
+ multicastGroupExists = false;
+ vlanMulticastNextId = flowObjectiveService.allocateNextId();
+ addMulticastGroupNextObjectiveId(deviceId, vlanId, vlanMulticastNextId);
+ vlanMulticastNextObjBuilder.addTreatment(
+ DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
+ );
+ }
+ vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
+ final int nextId = vlanMulticastNextId;
+ accessPorts.forEach(p -> {
+ TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
+ // Do vlan popup action based on interface configuration
+ if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
+ .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
+ egressAction.popVlan();
+ }
+ egressAction.setOutput(p);
+ vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
+ addMulticastGroupPort(deviceId, vlanId, p);
+ });
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) ->
+ log.debug("L2 multicast group installed/updated. "
+ + "NextObject Id {} on {} for subnet {} ",
+ nextId, deviceId, vlanId),
+ (objective, error) ->
+ log.warn("L2 multicast group failed to install/update. "
+ + " NextObject Id {} on {} for subnet {} : {}",
+ nextId, deviceId, vlanId, error)
+ );
+ if (!multicastGroupExists) {
+ flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
+
+ // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
+ TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
+ multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
+ multicastSelector.matchInPort(pairPort);
+ multicastSelector.matchVlanId(vlanId);
+ ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .nextStep(vlanMulticastNextId)
+ .withSelector(multicastSelector.build())
+ .withPriority(100)
+ .fromApp(srService.appId())
+ .makePermanent();
+ context = new DefaultObjectiveContext(
+ (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
+ deviceId,
+ pairPort,
+ vlanId),
+ (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
+ "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
+ flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
+ } else {
+ // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
+ vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
+ flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
+ }
+ }
+
+ /**
+ * Removes access ports from VLAN L2 multicast group on given deviceId.
+ *
+ * @param deviceId Device ID
+ * @param pairPort Pair port number
+ * @param vlanId VLAN ID
+ * @param accessPorts List of access ports to be added into L2 multicast group
+ */
+ private void revokeL2Multicast(DeviceId deviceId, final PortNumber pairPort,
+ VlanId vlanId, List<PortNumber> accessPorts) {
+
+ // Ensure enough rights to program pair device
+ if (!srService.shouldProgram(deviceId)) {
+ return;
+ }
+
+ int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
+ if (vlanMulticastNextId == -1) {
+ return;
+ }
+ NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
+ .builder()
+ .withType(NextObjective.Type.BROADCAST)
+ .fromApp(srService.appId())
+ .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
+ .withId(vlanMulticastNextId);
+ accessPorts.forEach(p -> {
+ TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
+ // Do vlan popup action based on interface configuration
+ if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
+ .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
+ egressAction.popVlan();
+ }
+ egressAction.setOutput(p);
+ vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
+ removeMulticastGroupPort(deviceId, vlanId, p);
+ });
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) ->
+ log.debug("L2 multicast group installed/updated. "
+ + "NextObject Id {} on {} for subnet {} ",
+ vlanMulticastNextId, deviceId, vlanId),
+ (objective, error) ->
+ log.warn("L2 multicast group failed to install/update. "
+ + " NextObject Id {} on {} for subnet {} : {}",
+ vlanMulticastNextId, deviceId, vlanId, error)
+ );
+ flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
+ }
+
+ /**
+ * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
+ * Normally multicast group is not removed if it contains access ports; which can be forced
+ * by "force" flag
+ *
+ * @param deviceId Device ID
+ * @param pairPort Pair port number
+ * @param vlanId VLAN ID
+ * @param force Forceful removal
+ */
+ private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
+
+ // Ensure enough rights to program pair device
+ if (!srService.shouldProgram(deviceId)) {
+ return;
+ }
+
+ // Ensure L2 multicast group doesn't contain access ports
+ if (hasAccessPortInMulticastGroup(deviceId, vlanId, pairPort) && !force) {
+ return;
+ }
+
+ // Load L2 multicast group details
+ int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
+ if (vlanMulticastNextId == -1) {
+ return;
+ }
+
+ // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
+ TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
+ l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
+ l2MulticastSelector.matchInPort(pairPort);
+ l2MulticastSelector.matchVlanId(vlanId);
+ ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .nextStep(vlanMulticastNextId)
+ .withSelector(l2MulticastSelector.build())
+ .withPriority(100)
+ .fromApp(srService.appId())
+ .makePermanent();
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
+ pairPort, vlanId),
+ (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
+ "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
+ flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
+
+ // Step 2 : Clear L2 multicast group associated with vlan
+ NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
+ .builder()
+ .withId(vlanMulticastNextId)
+ .withType(NextObjective.Type.BROADCAST)
+ .fromApp(srService.appId())
+ .withMeta(DefaultTrafficSelector.builder()
+ .matchVlanId(vlanId)
+ .matchEthDst(MacAddress.IPV4_MULTICAST).build())
+ .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
+ context = new DefaultObjectiveContext(
+ (objective) ->
+ log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
+ vlanMulticastNextId, deviceId, vlanId),
+ (objective, error) ->
+ log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
+ vlanMulticastNextId, deviceId, vlanId, error)
+ );
+ flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
+
+ // Finally clear store.
+ removeMulticastGroup(deviceId, vlanId);
+ }
+
+ private boolean isMulticastGroupExists(DeviceId deviceId, VlanId vlanId) {
+ return xconnectMulticastNextStore.asJavaMap().entrySet().stream()
+ .anyMatch(e -> e.getKey().deviceId().equals(deviceId) &&
+ e.getKey().vlanId().equals(vlanId));
+ }
+
+ private int getMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
+ Optional<Integer> nextId
+ = xconnectMulticastNextStore.asJavaMap().entrySet().stream()
+ .filter(e -> e.getKey().deviceId().equals(deviceId) &&
+ e.getKey().vlanId().equals(vlanId))
+ .findFirst()
+ .map(Map.Entry::getValue);
+ return nextId.orElse(-1);
+ }
+
+ private void addMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId, int nextId) {
+ if (nextId == -1) {
+ return;
+ }
+ VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+ xconnectMulticastNextStore.put(key, nextId);
+
+ // Update port store with empty entry.
+ xconnectMulticastPortsStore.put(key, new ArrayList<PortNumber>());
+ }
+
+ private void addMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
+ VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+ List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
+ ports.add(port);
+ xconnectMulticastPortsStore.put(key, ports);
+ }
+
+ private void removeMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
+ VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+ List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
+ ports.remove(port);
+ xconnectMulticastPortsStore.put(key, ports);
+ }
+
+ private void removeMulticastGroup(DeviceId deviceId, VlanId vlanId) {
+ VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+ xconnectMulticastPortsStore.remove(key);
+ xconnectMulticastNextStore.remove(key);
+ }
+
+ private boolean hasAccessPortInMulticastGroup(DeviceId deviceId, VlanId vlanId, PortNumber pairPort) {
+ VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+ if (!xconnectMulticastPortsStore.containsKey(key)) {
+ return false;
+ }
+ List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
+ return ports.stream().anyMatch(p -> !p.equals(pairPort));
+ }
+
}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
index 396643e..c5204ce 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
@@ -84,6 +84,8 @@
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline.*;
import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.L2_MULTICAST_TYPE;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.l2MulticastGroupKey;
import static org.onosproject.net.flow.criteria.Criterion.Type.TUNNEL_ID;
import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
import static org.onosproject.net.group.GroupDescription.Type.ALL;
@@ -96,29 +98,23 @@
*/
public class Ofdpa2GroupHandler {
protected final Logger log = getLogger(getClass());
-
// Services, Stores
protected GroupService groupService;
protected StorageService storageService;
protected FlowObjectiveStore flowObjectiveStore;
-
// index number for group creation
private AtomicCounter nextIndex;
-
protected DeviceId deviceId;
Cache<GroupKey, List<OfdpaGroupHandlerUtility.OfdpaNextGroup>> pendingAddNextObjectives;
Cache<NextObjective, List<GroupKey>> pendingRemoveNextObjectives;
Cache<GroupKey, Set<OfdpaGroupHandlerUtility.GroupChainElem>> pendingGroups;
ConcurrentHashMap<GroupKey, Set<NextObjective>> pendingUpdateNextObjectives;
-
// local store for pending bucketAdds - by design there can be multiple
// pending bucket for a group
protected ConcurrentHashMap<Integer, Set<NextObjective>> pendingBuckets =
new ConcurrentHashMap<>();
-
private ScheduledExecutorService groupCheckerExecutor =
Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa-%d", log));
-
/**
* Determines whether this pipeline support copy ttl instructions or not.
*
@@ -236,6 +232,64 @@
}
/**
+ * Similar to processBroadcastNextObjective but handles L2 Multicast Next Objectives.
+ *
+ * @param nextObj NextObjective of L2_MULTICAST with chained NextObjectives for single homed access ports
+ */
+ private void processL2MulticastNextObjective(NextObjective nextObj) {
+
+ VlanId assignedVlan = readVlanFromSelector(nextObj.meta());
+ if (assignedVlan == null) {
+ log.warn("VLAN ID required by L2 multicast next objective is missing. Aborting group creation.");
+ fail(nextObj, ObjectiveError.BADPARAMS);
+ return;
+ }
+
+ // Group info should contain only single homed hosts for a given vlanId
+ List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
+ createL2MulticastGroup(nextObj, assignedVlan, groupInfos);
+ }
+
+ private void createL2MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
+ // Realize & represent L2 multicast group in OFDPA driver layer
+ // TODO : Need to identify significance of OfdpaNextGroup.
+ Integer l2MulticastGroupId = L2_MULTICAST_TYPE | (vlanId.toShort() << 16);
+ final GroupKey l2MulticastGroupKey = l2MulticastGroupKey(vlanId, deviceId);
+ List<Deque<GroupKey>> l2MulticastAllGroup = Lists.newArrayList();
+ groupInfos.forEach(groupInfo -> {
+ Deque<GroupKey> groupKeyChain = new ArrayDeque<>();
+ groupKeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
+ groupKeyChain.addFirst(l2MulticastGroupKey);
+ l2MulticastAllGroup.add(groupKeyChain);
+ });
+ OfdpaNextGroup ofdpaL2MulticastGroup = new OfdpaNextGroup(l2MulticastAllGroup, nextObj);
+ updatePendingNextObjective(l2MulticastGroupKey, ofdpaL2MulticastGroup);
+ // Group Chain Hierarchy creation using group service and thus in device level
+ List<GroupBucket> l2McastBuckets = new ArrayList<>();
+ groupInfos.forEach(groupInfo -> {
+ // Points to L2 interface group directly.
+ TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment.builder();
+ trafficTreatment.group(new GroupId(groupInfo.innerMostGroupDesc().givenGroupId()));
+ GroupBucket bucket = DefaultGroupBucket.createAllGroupBucket(trafficTreatment.build());
+ l2McastBuckets.add(bucket);
+ });
+ GroupDescription l2MulticastGroupDescription =
+ new DefaultGroupDescription(
+ deviceId,
+ ALL,
+ new GroupBuckets(l2McastBuckets),
+ l2MulticastGroupKey,
+ l2MulticastGroupId,
+ nextObj.appId());
+ GroupChainElem l2MulticastGce = new GroupChainElem(l2MulticastGroupDescription,
+ groupInfos.size(), false, deviceId);
+ groupInfos.forEach(groupInfo -> {
+ updatePendingGroups(groupInfo.innerMostGroupDesc().appCookie(), l2MulticastGce);
+ groupService.addGroup(groupInfo.innerMostGroupDesc());
+ });
+ }
+
+ /**
* 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
* is broken up into a group chain. The following chains can be created
@@ -269,12 +323,10 @@
}
}
}
-
if (plainL2) {
createL2InterfaceGroup(nextObj);
return;
}
-
// In order to understand if it is a pseudowire related
// next objective we look for the tunnel id in the meta.
boolean isPw = false;
@@ -286,10 +338,8 @@
isPw = true;
}
}
-
if (mplsSwap && !isPw) {
log.debug("Creating a MPLS Swap -> MPLS Interface -> L2 Interface group chain.");
-
// break up simple next objective to GroupChain objects
GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
nextObj.appId(), true,
@@ -299,15 +349,12 @@
fail(nextObj, ObjectiveError.BADPARAMS);
return;
}
-
Deque<GroupKey> gkeyChain = new ArrayDeque<>();
gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie()); // l2 interface
gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie()); // mpls interface
-
// creating the mpls swap group and adding it to the chain
int nextGid = groupInfo.nextGroupDesc().givenGroupId();
int index = getNextAvailableIndex();
-
GroupDescription swapGroupDescription = createMplsSwap(
nextGid,
OfdpaMplsGroupSubType.MPLS_SWAP_LABEL,
@@ -321,13 +368,11 @@
1, false, deviceId);
updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), swapChainElem);
gkeyChain.addFirst(swapGroupKey);
-
// ensure nextObjective waits on the outermost groupKey
List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
allGroupKeys.add(gkeyChain);
OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
updatePendingNextObjective(swapGroupKey, 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.
@@ -355,10 +400,8 @@
List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
allGroupKeys.add(gkeyChain);
OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
-
// store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
updatePendingNextObjective(groupInfo.nextGroupDesc().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.
@@ -373,7 +416,6 @@
/**
* Creates a simple L2 Interface Group.
- *
* @param nextObj the next Objective
*/
private void createL2InterfaceGroup(NextObjective nextObj) {
@@ -383,25 +425,19 @@
fail(nextObj, ObjectiveError.BADPARAMS);
return;
}
-
List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
-
// There is only one L2 interface group in this case
GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc();
-
// Put all dependency information into allGroupKeys
List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
Deque<GroupKey> gkeyChain = new ArrayDeque<>();
gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
allGroupKeys.add(gkeyChain);
-
// Point the next objective to this group
OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
-
// Start installing the inner-most group
- groupService.addGroup(l2InterfaceGroupDesc);
- }
+ groupService.addGroup(l2InterfaceGroupDesc); }
/**
* Creates an Mpls group of type swap.
@@ -420,7 +456,6 @@
ApplicationId applicationId) {
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
treatment.setMpls(mplsLabel);
-
// We point the group to the next group.
treatment.group(new GroupId(nextGroupId));
GroupBucket groupBucket = DefaultGroupBucket
@@ -536,7 +571,6 @@
ins);
}
}
-
if (vlanid == null && meta != null) {
// use metadata if available
Criterion vidCriterion = meta.getCriterion(VLAN_VID);
@@ -553,14 +587,12 @@
}
}
}
-
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();
@@ -568,7 +600,6 @@
innerTtb.build().allInstructions().forEach(temp::add);
innerTtb = temp;
}
-
// assemble information for ofdpa l2interface group
int l2groupId = l2GroupId(vlanid, portNum);
// a globally unique groupkey that is different for ports in the same device,
@@ -576,7 +607,6 @@
// for the various group-types created out of the same next objective.
int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
-
// assemble information for outer group
GroupDescription outerGrpDesc;
if (mpls) {
@@ -623,11 +653,9 @@
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, deviceId);
updatePendingGroups(l2groupkey, gce);
-
// create group description for the inner l2 interface group
GroupBucket l2InterfaceGroupBucket =
DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
@@ -642,7 +670,6 @@
deviceId, Integer.toHexString(l2groupId),
l2groupkey, nextId);
return new GroupInfo(l2groupDescription, outerGrpDesc);
-
}
/**
@@ -661,8 +688,13 @@
return;
}
- List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
+ // Handling L2 multicast cases.
+ MacAddress dstMac = readEthDstFromSelector(nextObj.meta());
+ if (dstMac != null && dstMac.isMulticast()) {
+ processL2MulticastNextObjective(nextObj);
+ }
+ List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
IpPrefix ipDst = readIpDstFromSelector(nextObj.meta());
if (ipDst != null) {
if (ipDst.isMulticast()) {
@@ -679,10 +711,8 @@
private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj,
VlanId assignedVlan) {
ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
-
// break up broadcast next objective to multiple groups
Collection<TrafficTreatment> buckets = nextObj.next();
-
// each treatment is converted to an L2 interface group
for (TrafficTreatment treatment : buckets) {
TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
@@ -712,12 +742,10 @@
" broadcast nextObjective", ins.type());
}
}
-
if (portNum == null) {
log.warn("Can't find output port for the bucket {}.", treatment);
continue;
}
-
// assemble info for l2 interface group
VlanId l2InterfaceGroupVlan =
(egressVlan != null && !assignedVlan.equals(egressVlan)) ?
@@ -741,7 +769,6 @@
log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
deviceId, Integer.toHexString(l2InterfaceGroupId),
l2InterfaceGroupKey, nextObj.id());
-
groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
l2InterfaceGroupDescription));
}
@@ -754,7 +781,6 @@
// group for a vlan, its index is always the same - 0
Integer l2FloodGroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
final GroupKey l2FloodGroupKey = l2FloodGroupKey(vlanId, deviceId);
-
// collection of group buckets pointing to all the l2 interface groups
List<GroupBucket> l2floodBuckets = generateNextGroupBuckets(groupInfos, ALL);
// create the l2flood group-description to wait for all the
@@ -770,7 +796,6 @@
log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
deviceId, Integer.toHexString(l2FloodGroupId),
l2FloodGroupKey, nextObj.id());
-
// Put all dependency information into allGroupKeys
List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
groupInfos.forEach(groupInfo -> {
@@ -780,11 +805,9 @@
groupKeyChain.addFirst(l2FloodGroupKey);
allGroupKeys.add(groupKeyChain);
});
-
// Point the next objective to this group
OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
updatePendingNextObjective(l2FloodGroupKey, ofdpaGrp);
-
GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
groupInfos.size(), false, deviceId);
groupInfos.forEach(groupInfo -> {
@@ -825,24 +848,20 @@
gkeyChain.addFirst(l3MulticastGroupKey);
allGroupKeys.add(gkeyChain);
});
-
// Point the next objective to this group
OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
-
GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
groupInfos.size(), false, deviceId);
groupInfos.forEach(groupInfo -> {
// Point this group (L3 multicast) to the next group
updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), outerGce);
-
// Point next group to inner-most group, if any
if (!groupInfo.nextGroupDesc().equals(groupInfo.innerMostGroupDesc())) {
GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc(),
1, false, deviceId);
updatePendingGroups(groupInfo.innerMostGroupDesc().appCookie(), innerGce);
}
-
// Start installing the inner-most group
groupService.addGroup(groupInfo.innerMostGroupDesc());
});
@@ -868,7 +887,6 @@
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) {
@@ -898,11 +916,9 @@
// 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());
@@ -952,7 +968,6 @@
}
}
}
-
Deque<GroupKey> gKeyChain = new ArrayDeque<>();
// here we only deal with 0 and 1 label push
if (labelsPushed == 0) {
@@ -980,11 +995,9 @@
}
gKeyChain.addFirst(noLabelGroupInfo.innerMostGroupDesc().appCookie());
gKeyChain.addFirst(noLabelGroupInfo.nextGroupDesc().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,
@@ -1011,7 +1024,6 @@
if (requireVlanPopBeforeMplsPush()) {
l3vpnTtb.pushVlan().setVlanId(VlanId.vlanId(VlanId.RESERVED));
}
-
GroupBucket l3vpnGrpBkt =
DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
int l3vpnIndex = getNextAvailableIndex();
@@ -1035,24 +1047,19 @@
gKeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc().appCookie());
gKeyChain.addFirst(onelabelGroupInfo.nextGroupDesc().appCookie());
gKeyChain.addFirst(l3vpnGroupKey);
-
//now we can replace the outerGrpDesc with the one we just created
onelabelGroupInfo.nextGroupDesc(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:{} group key:{} 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);
}
@@ -1089,7 +1096,6 @@
fail(nextObjective, ObjectiveError.UNSUPPORTED);
return;
}
-
// first check to see if bucket being added is not a duplicate of an
// existing bucket. If it is for an existing output port, then its a
// duplicate.
@@ -1100,7 +1106,6 @@
deviceId);
Set<TrafficTreatment> nonDuplicateBuckets = Sets.newHashSet();
NextObjective objectiveToAdd;
-
nextObjective.next().forEach(trafficTreatment -> {
PortNumber portNumber = readOutPortFromTreatment(trafficTreatment);
if (portNumber == null) {
@@ -1125,7 +1130,6 @@
nonDuplicateBuckets.add(trafficTreatment);
}
});
-
if (duplicateBuckets.isEmpty()) {
// use the original objective
objectiveToAdd = nextObjective;
@@ -1140,7 +1144,6 @@
.withMeta(nextObjective.meta())
.fromApp(nextObjective.appId());
nonDuplicateBuckets.forEach(builder::addTreatment);
-
ObjectiveContext context = nextObjective.context().orElse(null);
objectiveToAdd = builder.addToExisting(context);
} else {
@@ -1150,7 +1153,6 @@
pass(nextObjective);
return;
}
-
if (nextObjective.type() == NextObjective.Type.HASHED) {
addBucketToHashGroup(objectiveToAdd, allActiveKeys);
} else if (nextObjective.type() == NextObjective.Type.BROADCAST) {
@@ -1165,10 +1167,8 @@
List<GroupInfo> unsentGroups = new ArrayList<>();
List<GroupBucket> newBuckets;
createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
-
// now we can create the buckets to add to the outermost L3 ECMP group
newBuckets = generateNextGroupBuckets(unsentGroups, SELECT);
-
// retrieve the original L3 ECMP group
Group l3ecmpGroup = retrieveTopLevelGroup(allActiveKeys, deviceId,
groupService, nextObjective.id());
@@ -1178,7 +1178,6 @@
}
GroupKey l3ecmpGroupKey = l3ecmpGroup.appCookie();
int l3ecmpGroupId = l3ecmpGroup.id().id();
-
// Although GroupDescriptions are not necessary for adding buckets to
// existing groups, we still use one in the GroupChainElem. When the latter is
// processed, the info will be extracted for the bucketAdd call to groupService
@@ -1205,7 +1204,6 @@
log.debug("Adding to L3ECMP: device:{} gid:{} group key:{} nextId:{}",
deviceId, Integer.toHexString(l3ecmpGroupId),
l3ecmpGroupKey, nextObjective.id());
-
unsentGroups.forEach(groupInfo -> {
// send the innermost group
log.debug("Sending innermost group {} in group chain on device {} ",
@@ -1257,7 +1255,6 @@
GroupKey l2floodGroupKey = l2FloodGroup.appCookie();
int l2floodGroupId = l2FloodGroup.id().id();
List<GroupBucket> newBuckets = generateNextGroupBuckets(groupInfos, ALL);
-
GroupDescription l2FloodGroupDescription =
new DefaultGroupDescription(deviceId,
ALL,
@@ -1265,7 +1262,6 @@
l2floodGroupKey,
l2floodGroupId,
nextObj.appId());
-
GroupChainElem l2FloodGroupChainElement =
new GroupChainElem(l2FloodGroupDescription,
groupInfos.size(),
@@ -1275,7 +1271,6 @@
//ensure assignedVlan applies to the chosen group
VlanId floodGroupVlan = extractVlanIdFromGroupId(l2floodGroupId);
-
if (!floodGroupVlan.equals(assignedVlan)) {
log.warn("VLAN ID {} does not match Flood group {} to which bucket is "
+ "being added, for next:{} in dev:{}. Abort.", assignedVlan,
@@ -1290,7 +1285,6 @@
newBucketChain.addFirst(groupInfo.nextGroupDesc().appCookie());
newBucketChain.addFirst(l2floodGroupKey);
addedKeys.add(newBucketChain);
-
log.debug("Adding to L2FLOOD: device:{} gid:{} group key:{} nextId:{}",
deviceId, Integer.toHexString(l2floodGroupId),
l2floodGroupKey, nextObj.id());
@@ -1298,7 +1292,6 @@
log.debug("Sending innermost group {} in group chain on device {} ",
Integer.toHexString(groupInfo.innerMostGroupDesc().givenGroupId()),
deviceId);
-
updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), l2FloodGroupChainElement);
DeviceId innerMostGroupDevice = groupInfo.innerMostGroupDesc().deviceId();
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
index 73d1d7d..eae78fd 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
@@ -56,6 +56,8 @@
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.ExtensionCriterion;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion;
import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion;
@@ -1851,6 +1853,15 @@
? null : ((VlanIdCriterion) criterion).vlanId();
}
+ static MacAddress readEthDstFromSelector(TrafficSelector selector) {
+ if (selector == null) {
+ return null;
+ }
+ Criterion criterion = selector.getCriterion(Criterion.Type.ETH_DST);
+ return (criterion == null)
+ ? null : ((EthCriterion) criterion).mac();
+ }
+
static IpPrefix readIpDstFromSelector(TrafficSelector selector) {
if (selector == null) {
return null;
@@ -1886,6 +1897,14 @@
return null;
}
+ static ExtensionSelector readExtensionFromSelector(TrafficSelector selector) {
+ if (selector == null) {
+ return null;
+ }
+ ExtensionCriterion criterion = (ExtensionCriterion) selector.getCriterion(Criterion.Type.EXTENSION);
+ return (criterion == null) ? null : criterion.extensionSelector();
+ }
+
/**
* Utility class that retries sending flows a fixed number of times, even if
* some of the attempts are successful. Used only for forwarding objectives.
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
index 31b92a8..5e48ebf 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
@@ -77,6 +77,7 @@
static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
static final int L3_ECMP_TYPE = 0x70000000;
static final int L2_FLOOD_TYPE = 0x40000000;
+ static final int L2_MULTICAST_TYPE = 0x30000000;
static final int TYPE_MASK = 0x0fffffff;
static final int SUBTYPE_MASK = 0x00ffffff;
@@ -453,6 +454,12 @@
return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash));
}
+ public static GroupKey l2MulticastGroupKey(VlanId vlanId, DeviceId deviceId) {
+ int hash = Objects.hash(deviceId, vlanId);
+ hash = L2_MULTICAST_TYPE | TYPE_MASK & hash;
+ return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash));
+ }
+
public static int l2GroupId(VlanId vlanId, long portNum) {
return L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum;
}