Charles Chan
Committed by Gerrit Code Review

CORD-348 multicast support in SegmentRouting and vRouter

In this submission:
* Setup/teardown multicast route according to SinkAdded/SinkRemoved event
    - ingressVlan and egressVlan is configurable through network config
* Change behavior of OFDPA VLAN assignment
    - Always use the VLAN in metadata if present
* Bugfix of writing immutable object

NOT in this submission (coming soon):
* Error handling (e.g. link/device failure recovery)

Change-Id: I9be11af04eb2d6456b865c7e59e96cc02370f846
......@@ -29,11 +29,13 @@ import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceEvent;
import org.onosproject.incubator.net.intf.InterfaceListener;
......@@ -44,9 +46,12 @@ import org.onosproject.incubator.net.routing.RouteListener;
import org.onosproject.incubator.net.routing.RouteService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
......@@ -102,6 +107,9 @@ public class SingleSwitchFibInstaller {
protected NetworkConfigService networkConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfigRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
......@@ -123,6 +131,7 @@ public class SingleSwitchFibInstaller {
private List<String> interfaces;
private ApplicationId coreAppId;
private ApplicationId routerAppId;
// Reference count for how many times a next hop is used by a route
......@@ -138,13 +147,25 @@ public class SingleSwitchFibInstaller {
private InternalInterfaceListener internalInterfaceList = new InternalInterfaceListener();
private InternalRouteListener routeListener = new InternalRouteListener();
private ConfigFactory<ApplicationId, McastConfig> mcastConfigFactory =
new ConfigFactory<ApplicationId, McastConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
McastConfig.class, "multicast") {
@Override
public McastConfig createConfig() {
return new McastConfig();
}
};
@Activate
protected void activate(ComponentContext context) {
componentConfigService.registerProperties(getClass());
modified(context);
coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
routerAppId = coreService.registerApplication(RoutingService.ROUTER_APP_ID);
networkConfigRegistry.registerConfigFactory(mcastConfigFactory);
deviceListener = new InternalDeviceListener();
deviceService.addListener(deviceListener);
......@@ -368,6 +389,7 @@ public class SingleSwitchFibInstaller {
}
createFilteringObjective(install, intf);
createMcastFilteringObjective(install, intf);
}
}
......@@ -380,10 +402,14 @@ public class SingleSwitchFibInstaller {
}
createFilteringObjective(install, intf);
createMcastFilteringObjective(install, intf);
}
//create filtering objective for interface
private void createFilteringObjective(boolean install, Interface intf) {
VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
VlanId.vlanId(ASSIGNED_VLAN) :
egressVlan();
FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
// first add filter for the interface
......@@ -393,12 +419,12 @@ public class SingleSwitchFibInstaller {
fob.withPriority(PRIORITY_OFFSET);
if (intf.vlan() == VlanId.NONE) {
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(VlanId.vlanId(ASSIGNED_VLAN)).build();
.pushVlan().setVlanId(assignedVlan).build();
fob.withMeta(tt);
}
fob.permit().fromApp(routerAppId);
sendFilteringObjective(install, fob, intf);
if (controlPlaneConnectPoint != null) {
// then add the same mac/vlan filters for control-plane connect point
fob.withKey(Criteria.matchInPort(controlPlaneConnectPoint.port()));
......@@ -406,6 +432,27 @@ public class SingleSwitchFibInstaller {
}
}
//create filtering objective for multicast traffic
private void createMcastFilteringObjective(boolean install, Interface intf) {
VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
VlanId.vlanId(ASSIGNED_VLAN) :
egressVlan();
FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
// first add filter for the interface
fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
MacAddress.IPV4_MULTICAST_MASK))
.addCondition(Criteria.matchVlanId(ingressVlan()));
fob.withPriority(PRIORITY_OFFSET);
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(assignedVlan).build();
fob.withMeta(tt);
fob.permit().fromApp(routerAppId);
sendFilteringObjective(install, fob, intf);
}
private void sendFilteringObjective(boolean install, FilteringObjective.Builder fob,
Interface intf) {
......@@ -419,6 +466,18 @@ public class SingleSwitchFibInstaller {
flowObjectiveService.filter(deviceId, filter);
}
private VlanId ingressVlan() {
McastConfig mcastConfig =
networkConfigService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
}
private VlanId egressVlan() {
McastConfig mcastConfig =
networkConfigService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
}
private class InternalRouteListener implements RouteListener {
@Override
public void event(RouteEvent event) {
......@@ -490,7 +549,6 @@ public class SingleSwitchFibInstaller {
}
private class InternalInterfaceListener implements InterfaceListener {
@Override
public void event(InterfaceEvent event) {
Interface intf = event.subject();
......
......@@ -40,6 +40,7 @@ import org.onosproject.incubator.net.routing.RouteServiceAdapter;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
......@@ -105,6 +106,7 @@ public class SingleSwitchFibInstallerTest {
private final Set<Interface> interfaces = Sets.newHashSet();
private InterfaceService interfaceService;
private NetworkConfigService networkConfigService;
private NetworkConfigRegistry networkConfigRegistry;
private FlowObjectiveService flowObjectiveService;
private DeviceService deviceService;
private static final ApplicationId APPID = TestApplicationId.create("foo");
......@@ -128,13 +130,15 @@ public class SingleSwitchFibInstallerTest {
interfaceService = createMock(InterfaceService.class);
networkConfigService = createMock(NetworkConfigService.class);
networkConfigRegistry = createMock(NetworkConfigRegistry.class);
flowObjectiveService = createMock(FlowObjectiveService.class);
deviceService = new TestDeviceService();
CoreService coreService = createNiceMock(CoreService.class);
expect(coreService.registerApplication(anyString())).andReturn(APPID);
expect(coreService.registerApplication(anyString())).andReturn(APPID).anyTimes();
replay(coreService);
sSfibInstaller.networkConfigService = networkConfigService;
sSfibInstaller.networkConfigRegistry = networkConfigRegistry;
sSfibInstaller.interfaceService = interfaceService;
sSfibInstaller.flowObjectiveService = flowObjectiveService;
sSfibInstaller.coreService = coreService;
......
......@@ -3,9 +3,9 @@ COMPILE_DEPS = [
'//lib:org.apache.karaf.shell.console',
'//lib:javax.ws.rs-api',
'//cli:onos-cli',
'//core/store/serializers:onos-core-serializers',
'//incubator/api:onos-incubator-api',
'//utils/rest:onlab-rest',
'//core/store/serializers:onos-core-serializers',
]
TEST_DEPS = [
......
......@@ -51,7 +51,11 @@
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
......@@ -84,12 +88,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
......@@ -103,7 +105,6 @@
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.segmentrouting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.Path;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.DefaultNextObjective;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.mcast.McastEvent;
import org.onosproject.net.mcast.McastRouteInfo;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.segmentrouting.grouphandler.McastNextObjectiveStoreKey;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Multicast event handler.
*/
public class McastEventHandler {
private static final Logger log = LoggerFactory.getLogger(McastEventHandler.class);
private final SegmentRoutingManager srManager;
private final ApplicationId coreAppId;
private StorageService storageService;
private TopologyService topologyService;
private final KryoNamespace.Builder kryoBuilder;
private final ConsistentMap<McastNextObjectiveStoreKey, NextObjective> mcastNextObjStore;
/**
* Constructs the McastEventHandler.
*
* @param srManager Segment Routing manager
*/
public McastEventHandler(SegmentRoutingManager srManager) {
coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
this.srManager = srManager;
this.storageService = srManager.storageService;
this.topologyService = srManager.topologyService;
kryoBuilder = new KryoNamespace.Builder()
.register(KryoNamespaces.API)
.register(McastNextObjectiveStoreKey.class);
mcastNextObjStore = storageService
.<McastNextObjectiveStoreKey, NextObjective>consistentMapBuilder()
.withName("onos-mcast-nextobj-store")
.withSerializer(Serializer.using(kryoBuilder.build()))
.build();
}
/**
* Processes the SOURCE_ADDED event.
*
* @param event McastEvent with SOURCE_ADDED type
*/
protected void processSourceAdded(McastEvent event) {
log.info("processSourceAdded {}", event);
McastRouteInfo mcastRouteInfo = event.subject();
if (!mcastRouteInfo.isComplete()) {
log.info("Incompleted McastRouteInfo. Abort.");
return;
}
ConnectPoint source = mcastRouteInfo.source().orElse(null);
Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
IpAddress mcastIp = mcastRouteInfo.route().group();
sinks.forEach(sink -> {
processSinkAddedInternal(source, sink, mcastIp);
});
}
/**
* Processes the SINK_ADDED event.
*
* @param event McastEvent with SINK_ADDED type
*/
protected void processSinkAdded(McastEvent event) {
log.info("processSinkAdded {}", event);
McastRouteInfo mcastRouteInfo = event.subject();
if (!mcastRouteInfo.isComplete()) {
log.info("Incompleted McastRouteInfo. Abort.");
return;
}
ConnectPoint source = mcastRouteInfo.source().orElse(null);
ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
IpAddress mcastIp = mcastRouteInfo.route().group();
processSinkAddedInternal(source, sink, mcastIp);
}
/**
* Processes the SINK_REMOVED event.
*
* @param event McastEvent with SINK_REMOVED type
*/
protected void processSinkRemoved(McastEvent event) {
log.info("processSinkRemoved {}", event);
McastRouteInfo mcastRouteInfo = event.subject();
if (!mcastRouteInfo.isComplete()) {
log.info("Incompleted McastRouteInfo. Abort.");
return;
}
ConnectPoint source = mcastRouteInfo.source().orElse(null);
ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
IpAddress mcastIp = mcastRouteInfo.route().group();
VlanId assignedVlan = assignedVlan();
// When source and sink are on the same device
if (source.deviceId().equals(sink.deviceId())) {
// Source and sink are on even the same port. There must be something wrong.
if (source.port().equals(sink.port())) {
log.warn("Sink is on the same port of source. Abort");
return;
}
removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
return;
}
// Process the egress device
boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
// If this is the last sink on the device, also update upstream
Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
if (mcastPath.isPresent()) {
List<Link> links = Lists.newArrayList(mcastPath.get().links());
Collections.reverse(links);
for (Link link : links) {
if (isLast) {
isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
mcastIp, assignedVlan);
}
}
}
}
/**
* Establishes a path from source to sink for given multicast group.
*
* @param source connect point of the multicast source
* @param sink connection point of the multicast sink
* @param mcastIp multicast group IP address
*/
private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
IpAddress mcastIp) {
VlanId assignedVlan = assignedVlan();
// When source and sink are on the same device
if (source.deviceId().equals(sink.deviceId())) {
// Source and sink are on even the same port. There must be something wrong.
if (source.port().equals(sink.port())) {
log.warn("Sink is on the same port of source. Abort");
return;
}
addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
return;
}
// Process the ingress device
addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
// Find a path. If present, create/update groups and flows for each hop
Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
if (mcastPath.isPresent()) {
mcastPath.get().links().forEach(link -> {
addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
});
// Process the egress device
addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
}
}
/**
* Adds filtering objective for given device and port.
*
* @param deviceId device ID
* @param port ingress port number
* @param assignedVlan assigned VLAN ID
*/
private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
// Do nothing if the port is configured as suppressed
ConnectPoint connectPt = new ConnectPoint(deviceId, port);
if (srManager.deviceConfiguration.suppressSubnet().contains(connectPt) ||
srManager.deviceConfiguration.suppressHost().contains(connectPt)) {
log.info("Ignore suppressed port {}", connectPt);
return;
}
FilteringObjective.Builder filtObjBuilder =
filterObjBuilder(deviceId, port, assignedVlan);
srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add());
// TODO add objective context
}
/**
* Adds a port to given multicast group on given device. This involves the
* update of L3 multicast group and multicast routing table entry.
*
* @param deviceId device ID
* @param port port to be added
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
*/
private void addPortToDevice(DeviceId deviceId, PortNumber port,
IpAddress mcastIp, VlanId assignedVlan) {
log.info("Add port {} to {}. mcastIp={}, assignedVlan={}",
port, deviceId, mcastIp, assignedVlan);
McastNextObjectiveStoreKey mcastNextObjectiveStoreKey =
new McastNextObjectiveStoreKey(mcastIp, deviceId);
ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
if (!mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) {
// First time someone request this mcast group via this device
portBuilder.add(port);
} else {
// This device already serves some subscribers of this mcast group
NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value();
// Stop if the port is already in the nextobj
Set<PortNumber> existingPorts = getPorts(nextObj.next());
if (existingPorts.contains(port)) {
log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
return;
}
portBuilder.addAll(existingPorts).add(port).build();
}
// Create, store and apply the new nextObj and fwdObj
NextObjective newNextObj =
nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
ForwardingObjective fwdObj =
fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
mcastNextObjStore.put(mcastNextObjectiveStoreKey, newNextObj);
srManager.flowObjectiveService.next(deviceId, newNextObj);
srManager.flowObjectiveService.forward(deviceId, fwdObj);
// TODO add objective callback
}
/**
* Removes a port from given multicast group on given device.
* This involves the update of L3 multicast group and multicast routing
* table entry.
*
* @param deviceId device ID
* @param port port to be added
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @return true if this is the last sink on this device
*/
private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
IpAddress mcastIp, VlanId assignedVlan) {
log.info("Remove port {} from {}. mcastIp={}, assignedVlan={}",
port, deviceId, mcastIp, assignedVlan);
McastNextObjectiveStoreKey mcastNextObjectiveStoreKey =
new McastNextObjectiveStoreKey(mcastIp, deviceId);
// This device is not serving this multicast group
if (!mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) {
log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
return false;
}
NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value();
Set<PortNumber> existingPorts = getPorts(nextObj.next());
// This device does not serve this multicast group
if (!existingPorts.contains(port)) {
log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
return false;
}
// Copy and modify the ImmutableSet
existingPorts = Sets.newHashSet(existingPorts);
existingPorts.remove(port);
NextObjective newNextObj;
ForwardingObjective fwdObj;
if (existingPorts.isEmpty()) {
// If this is the last sink, remove flows and groups
// NOTE: Rely on GroupStore garbage collection rather than explicitly
// remove L3MG since there might be other flows/groups refer to
// the same L2IG
fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove();
mcastNextObjStore.remove(mcastNextObjectiveStoreKey);
srManager.flowObjectiveService.forward(deviceId, fwdObj);
} else {
// If this is not the last sink, update flows and groups
newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
mcastNextObjStore.put(mcastNextObjectiveStoreKey, newNextObj);
srManager.flowObjectiveService.next(deviceId, newNextObj);
srManager.flowObjectiveService.forward(deviceId, fwdObj);
}
// TODO add objective callback
return existingPorts.isEmpty();
}
/**
* Creates a next objective builder for multicast.
*
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @param outPorts set of output port numbers
* @return next objective builder
*/
private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
VlanId assignedVlan, Set<PortNumber> outPorts) {
int nextId = srManager.flowObjectiveService.allocateNextId();
TrafficSelector metadata =
DefaultTrafficSelector.builder()
.matchVlanId(assignedVlan)
.matchIPDst(mcastIp.toIpPrefix())
.build();
NextObjective.Builder nextObjBuilder = DefaultNextObjective
.builder().withId(nextId)
.withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
.withMeta(metadata);
outPorts.forEach(port -> {
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
if (egressVlan().equals(VlanId.NONE)) {
tBuilder.popVlan();
}
tBuilder.setOutput(port);
nextObjBuilder.addTreatment(tBuilder.build());
});
return nextObjBuilder;
}
/**
* Creates a forwarding objective builder for multicast.
*
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @param nextId next ID of the L3 multicast group
* @return forwarding objective builder
*/
private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
VlanId assignedVlan, int nextId) {
TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
sbuilder.matchEthType(Ethernet.TYPE_IPV4);
sbuilder.matchIPDst(mcastPrefix);
TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
metabuilder.matchVlanId(assignedVlan);
ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
fwdBuilder.withSelector(sbuilder.build())
.withMeta(metabuilder.build())
.nextStep(nextId)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(srManager.appId)
.withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
return fwdBuilder;
}
/**
* Creates a filtering objective builder for multicast.
*
* @param deviceId Device ID
* @param ingressPort ingress port of the multicast stream
* @param assignedVlan assigned VLAN ID
* @return filtering objective builder
*/
private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
VlanId assignedVlan) {
FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
filtBuilder.withKey(Criteria.matchInPort(ingressPort))
.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
MacAddress.IPV4_MULTICAST_MASK))
.addCondition(Criteria.matchVlanId(egressVlan()))
.withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
// vlan assignment is valid only if this instance is master
if (srManager.mastershipService.isLocalMaster(deviceId)) {
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(assignedVlan).build();
filtBuilder.withMeta(tt);
}
return filtBuilder.permit().fromApp(srManager.appId);
}
/**
* Gets output ports information from treatments.
*
* @param treatments collection of traffic treatments
* @return set of output port numbers
*/
private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
treatments.forEach(treatment -> {
treatment.allInstructions().stream()
.filter(instr -> instr instanceof OutputInstruction)
.forEach(instr -> {
builder.add(((OutputInstruction) instr).port());
});
});
return builder.build();
}
/**
* Gets a path from src to dst.
* If a path was allocated before, returns the allocated path.
* Otherwise, randomly pick one from available paths.
*
* @param src source device ID
* @param dst destination device ID
* @param mcastIp multicast group
* @return an optional path from src to dst
*/
private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
List<Path> allPaths = Lists.newArrayList(
topologyService.getPaths(topologyService.currentTopology(), src, dst));
if (allPaths.isEmpty()) {
log.warn("Fail to find a path from {} to {}. Abort.", src, dst);
return Optional.empty();
}
// If one of the available path is used before, use the same path
McastNextObjectiveStoreKey mcastNextObjectiveStoreKey =
new McastNextObjectiveStoreKey(mcastIp, src);
if (mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) {
NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value();
Set<PortNumber> existingPorts = getPorts(nextObj.next());
for (Path path : allPaths) {
PortNumber srcPort = path.links().get(0).src().port();
if (existingPorts.contains(srcPort)) {
return Optional.of(path);
}
}
}
// Otherwise, randomly pick a path
Collections.shuffle(allPaths);
return allPaths.stream().findFirst();
}
/**
* Gets egress VLAN from McastConfig.
*
* @return egress VLAN or VlanId.NONE if not configured
*/
private VlanId egressVlan() {
McastConfig mcastConfig =
srManager.cfgService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
}
/**
* Gets assigned VLAN according to the value of egress VLAN.
*
* @return assigned VLAN
*/
private VlanId assignedVlan() {
return (egressVlan().equals(VlanId.NONE)) ?
VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) :
egressVlan();
}
}
......@@ -73,8 +73,8 @@ public class NetworkConfigEventHandler {
SegmentRoutingAppConfig config = (SegmentRoutingAppConfig) event.config().get();
SegmentRoutingAppConfig prevConfig = (SegmentRoutingAppConfig) event.prevConfig().get();
deviceService.getAvailableDevices().forEach(device -> {
Set<MacAddress> macAddresses = getMacAddresses(config);
Set<MacAddress> prevMacAddresses = getMacAddresses(prevConfig);
Set<MacAddress> macAddresses = new HashSet<>(getMacAddresses(config));
Set<MacAddress> prevMacAddresses = new HashSet<>(getMacAddresses(prevConfig));
// Avoid removing and re-adding unchanged MAC addresses since
// FlowObjective does not guarantee the execution order.
Set<MacAddress> sameMacAddresses = new HashSet<>(macAddresses);
......
......@@ -33,6 +33,7 @@ import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.event.Event;
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
......@@ -60,23 +61,27 @@ 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.link.LinkEvent;
import org.onosproject.net.link.LinkListener;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.mcast.McastEvent;
import org.onosproject.net.mcast.McastListener;
import org.onosproject.net.mcast.MulticastRouteService;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceConfiguration;
import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
import org.onosproject.segmentrouting.grouphandler.NeighborSet;
import org.onosproject.segmentrouting.grouphandler.NeighborSetNextObjectiveStoreKey;
import org.onosproject.segmentrouting.grouphandler.PortNextObjectiveStoreKey;
import org.onosproject.net.host.HostService;
import org.onosproject.net.link.LinkEvent;
import org.onosproject.net.link.LinkListener;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.segmentrouting.grouphandler.SubnetNextObjectiveStoreKey;
import org.onosproject.segmentrouting.grouphandler.XConnectNextObjectiveStoreKey;
import org.onosproject.store.serializers.KryoNamespaces;
......@@ -143,6 +148,12 @@ public class SegmentRoutingManager implements SegmentRoutingService {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService compCfgService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MulticastRouteService multicastRouteService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
protected ArpHandler arpHandler = null;
protected IcmpHandler icmpHandler = null;
protected IpHandler ipHandler = null;
......@@ -157,8 +168,11 @@ public class SegmentRoutingManager implements SegmentRoutingService {
private InternalLinkListener linkListener = null;
private InternalDeviceListener deviceListener = null;
private NetworkConfigEventHandler netcfgHandler = null;
private McastEventHandler mcastEventHandler = null;
private InternalEventHandler eventHandler = new InternalEventHandler();
private final InternalHostListener hostListener = new InternalHostListener();
private final InternalConfigListener cfgListener = new InternalConfigListener(this);
private final InternalMcastListener mcastListener = new InternalMcastListener();
private ScheduledExecutorService executorService = Executors
.newScheduledThreadPool(1);
......@@ -196,29 +210,32 @@ public class SegmentRoutingManager implements SegmentRoutingService {
private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
private EventuallyConsistentMap<String, Policy> policyStore = null;
private final InternalConfigListener cfgListener =
new InternalConfigListener(this);
private final ConfigFactory<DeviceId, SegmentRoutingDeviceConfig> cfgDeviceFactory =
private final ConfigFactory<DeviceId, SegmentRoutingDeviceConfig> deviceConfigFactory =
new ConfigFactory<DeviceId, SegmentRoutingDeviceConfig>(SubjectFactories.DEVICE_SUBJECT_FACTORY,
SegmentRoutingDeviceConfig.class,
"segmentrouting") {
SegmentRoutingDeviceConfig.class, "segmentrouting") {
@Override
public SegmentRoutingDeviceConfig createConfig() {
return new SegmentRoutingDeviceConfig();
}
};
private final ConfigFactory<ApplicationId, SegmentRoutingAppConfig> cfgAppFactory =
private final ConfigFactory<ApplicationId, SegmentRoutingAppConfig> appConfigFactory =
new ConfigFactory<ApplicationId, SegmentRoutingAppConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
SegmentRoutingAppConfig.class,
"segmentrouting") {
SegmentRoutingAppConfig.class, "segmentrouting") {
@Override
public SegmentRoutingAppConfig createConfig() {
return new SegmentRoutingAppConfig();
}
};
private ConfigFactory<ApplicationId, McastConfig> mcastConfigFactory =
new ConfigFactory<ApplicationId, McastConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
McastConfig.class, "multicast") {
@Override
public McastConfig createConfig() {
return new McastConfig();
}
};
private Object threadSchedulerLock = new Object();
private static int numOfEventsQueued = 0;
private static int numOfEventsExecuted = 0;
......@@ -312,14 +329,17 @@ public class SegmentRoutingManager implements SegmentRoutingService {
linkListener = new InternalLinkListener();
deviceListener = new InternalDeviceListener();
netcfgHandler = new NetworkConfigEventHandler(this);
mcastEventHandler = new McastEventHandler(this);
cfgService.addListener(cfgListener);
cfgService.registerConfigFactory(cfgDeviceFactory);
cfgService.registerConfigFactory(cfgAppFactory);
cfgService.registerConfigFactory(deviceConfigFactory);
cfgService.registerConfigFactory(appConfigFactory);
cfgService.registerConfigFactory(mcastConfigFactory);
hostService.addListener(hostListener);
packetService.addProcessor(processor, PacketProcessor.director(2));
linkService.addListener(linkListener);
deviceService.addListener(deviceListener);
multicastRouteService.addListener(mcastListener);
// Request ARP packet-in
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
......@@ -351,8 +371,9 @@ public class SegmentRoutingManager implements SegmentRoutingService {
@Deactivate
protected void deactivate() {
cfgService.removeListener(cfgListener);
cfgService.unregisterConfigFactory(cfgDeviceFactory);
cfgService.unregisterConfigFactory(cfgAppFactory);
cfgService.unregisterConfigFactory(deviceConfigFactory);
cfgService.unregisterConfigFactory(appConfigFactory);
cfgService.unregisterConfigFactory(mcastConfigFactory);
// Withdraw ARP packet-in
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
......@@ -362,12 +383,20 @@ public class SegmentRoutingManager implements SegmentRoutingService {
packetService.removeProcessor(processor);
linkService.removeListener(linkListener);
deviceService.removeListener(deviceListener);
multicastRouteService.removeListener(mcastListener);
processor = null;
linkListener = null;
deviceService = null;
deviceListener = null;
groupHandlerMap.clear();
nsNextObjStore.destroy();
subnetNextObjStore.destroy();
portNextObjStore.destroy();
xConnectNextObjStore.destroy();
tunnelStore.destroy();
policyStore.destroy();
subnetVidStore.destroy();
log.info("Stopped");
}
......@@ -1186,6 +1215,27 @@ public class SegmentRoutingManager implements SegmentRoutingService {
}
}
private class InternalMcastListener implements McastListener {
@Override
public void event(McastEvent event) {
switch (event.type()) {
case SOURCE_ADDED:
mcastEventHandler.processSourceAdded(event);
break;
case SINK_ADDED:
mcastEventHandler.processSinkAdded(event);
break;
case SINK_REMOVED:
mcastEventHandler.processSinkRemoved(event);
break;
case ROUTE_ADDED:
case ROUTE_REMOVED:
default:
break;
}
}
}
private static class BridgingTableObjectiveContext implements ObjectiveContext {
final MacAddress mac;
final VlanId vlanId;
......
......@@ -496,12 +496,22 @@ public class DeviceConfiguration implements DeviceProperties {
return srinfo != null && srinfo.adjacencySids.containsKey(sid);
}
/**
* Gets connect points for which segment routing does not install subnet rules.
*
* @return set of connect points
*/
public Set<ConnectPoint> suppressSubnet() {
SegmentRoutingAppConfig appConfig =
cfgService.getConfig(appId, SegmentRoutingAppConfig.class);
return (appConfig != null) ? appConfig.suppressSubnet() : ImmutableSet.of();
}
/**
* Gets connect points for which segment routing does not install host rules.
*
* @return set of connect points
*/
public Set<ConnectPoint> suppressHost() {
SegmentRoutingAppConfig appConfig =
cfgService.getConfig(appId, SegmentRoutingAppConfig.class);
......
/*
* Copyright 2015-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.segmentrouting.grouphandler;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
/**
* Key of multicast next objective store.
*/
public class McastNextObjectiveStoreKey {
private final IpAddress mcastIp;
private final DeviceId deviceId;
/**
* Constructs the key of multicast next objective store.
*
* @param mcastIp multicast group IP address
* @param deviceId device ID
*/
public McastNextObjectiveStoreKey(IpAddress mcastIp, DeviceId deviceId) {
checkNotNull(mcastIp, "mcastIp cannot be null");
checkNotNull(deviceId, "deviceId cannot be null");
checkArgument(mcastIp.isMulticast(), "mcastIp must be a multicast address");
this.mcastIp = mcastIp;
this.deviceId = deviceId;
}
/**
* Returns the multicast IP address of this key.
*
* @return multicast IP
*/
public IpAddress mcastIp() {
return this.mcastIp;
}
/**
* Returns the device ID of this key.
*
* @return device ID
*/
public DeviceId deviceId() {
return this.deviceId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof McastNextObjectiveStoreKey)) {
return false;
}
McastNextObjectiveStoreKey that =
(McastNextObjectiveStoreKey) o;
return (Objects.equals(this.mcastIp, that.mcastIp) &&
Objects.equals(this.deviceId, that.deviceId));
}
@Override
public int hashCode() {
return Objects.hash(mcastIp, deviceId);
}
@Override
public String toString() {
return toStringHelper(getClass())
.add("mcastIp", mcastIp)
.add("deviceId", deviceId)
.toString();
}
}
......@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
......@@ -102,4 +103,13 @@ public final class McastRouteInfo {
return sinks;
}
@Override
public String toString() {
return toStringHelper(this)
.add("route", route())
.add("sink", sink())
.add("source", source())
.add("sinks", sinks())
.toString();
}
}
......
......@@ -22,8 +22,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
......@@ -279,17 +277,6 @@ public class CpqdOfdpa2Pipeline extends Ofdpa2Pipeline {
}
for (PortNumber pnum : portnums) {
// update storage
groupHandler.port2Vlan.put(pnum, storeVlan);
Set<PortNumber> vlanPorts = groupHandler.vlan2Port.get(storeVlan);
if (vlanPorts == null) {
vlanPorts = Collections.newSetFromMap(
new ConcurrentHashMap<PortNumber, Boolean>());
vlanPorts.add(pnum);
groupHandler.vlan2Port.put(storeVlan, vlanPorts);
} else {
vlanPorts.add(pnum);
}
// create rest of flowrule
selector.matchInPort(pnum);
FlowRule rule = DefaultFlowRule.builder()
......
......@@ -65,7 +65,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
......@@ -123,10 +122,6 @@ public class Ofdpa2GroupHandler {
// index number for group creation
private AtomicCounter nextIndex;
// 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<>();
......
......@@ -313,31 +313,22 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline
}
VlanId assignedVlan = null;
// For VLAN cross-connect packets, use the configured VLAN
if (vidCriterion != null) {
if (vidCriterion.vlanId() != VlanId.NONE) {
// Use the VLAN in metadata whenever a metadata is provided
if (filt.meta() != null) {
assignedVlan = readVlanFromTreatment(filt.meta());
// Use the VLAN in criterion if metadata is not present and the traffic is tagged
} else if (!vidCriterion.vlanId().equals(VlanId.NONE)) {
assignedVlan = vidCriterion.vlanId();
}
// For untagged packets, assign a VLAN ID
} else {
if (filt.meta() == null) {
log.error("Missing metadata in filtering objective required " +
"for vlan assignment in dev {}", deviceId);
fail(filt, ObjectiveError.BADPARAMS);
return;
}
for (Instruction i : filt.meta().allInstructions()) {
if (i instanceof ModVlanIdInstruction) {
assignedVlan = ((ModVlanIdInstruction) i).vlanId();
}
}
if (assignedVlan == null) {
log.error("Driver requires an assigned vlan-id to tag incoming "
+ "untagged packets. Not processing vlan filters on "
+ "device {}", deviceId);
fail(filt, ObjectiveError.BADPARAMS);
return;
}
if (assignedVlan == null) {
log.error("Driver fails to extract VLAN information. "
+ "Not proccessing VLAN filters on device {}.", deviceId);
log.debug("VLAN ID in criterion={}, metadata={}",
readVlanFromTreatment(filt.meta()), vidCriterion.vlanId());
fail(filt, ObjectiveError.BADPARAMS);
return;
}
}
......@@ -457,22 +448,14 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline
TrafficSelector.Builder preSelector = null;
TrafficTreatment.Builder preTreatment = null;
treatment.transition(TMAC_TABLE);
VlanId storeVlan = null;
if (vidCriterion.vlanId() == VlanId.NONE) {
// untagged packets are assigned vlans
OfdpaMatchVlanVid ofdpaMatchVlanVid = new OfdpaMatchVlanVid(VlanId.NONE);
selector.extension(ofdpaMatchVlanVid, deviceId);
OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(assignedVlan);
treatment.extension(ofdpaSetVlanVid, deviceId);
// ofdpa requires an additional vlan match rule for the assigned vlan
// and it does not require the push when setting the assigned vlan.
// It also requires the extra rule to be sent to the switch before we
// send the untagged match rule.
// None of this in compliance with OF standard.
storeVlan = assignedVlan;
preSelector = DefaultTrafficSelector.builder();
OfdpaMatchVlanVid preOfdpaMatchVlanVid = new OfdpaMatchVlanVid(assignedVlan);
......@@ -482,7 +465,11 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline
} else {
OfdpaMatchVlanVid ofdpaMatchVlanVid = new OfdpaMatchVlanVid(vidCriterion.vlanId());
selector.extension(ofdpaMatchVlanVid, deviceId);
storeVlan = vidCriterion.vlanId();
if (!assignedVlan.equals(vidCriterion.vlanId())) {
OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(assignedVlan);
treatment.extension(ofdpaSetVlanVid, deviceId);
}
}
// ofdpa cannot match on ALL portnumber, so we need to use separate
......@@ -499,17 +486,6 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline
}
for (PortNumber pnum : portnums) {
// update storage
groupHandler.port2Vlan.put(pnum, storeVlan);
Set<PortNumber> vlanPorts = groupHandler.vlan2Port.get(storeVlan);
if (vlanPorts == null) {
vlanPorts = Collections.newSetFromMap(
new ConcurrentHashMap<PortNumber, Boolean>());
vlanPorts.add(pnum);
groupHandler.vlan2Port.put(storeVlan, vlanPorts);
} else {
vlanPorts.add(pnum);
}
// create rest of flowrule
selector.matchInPort(pnum);
FlowRule rule = DefaultFlowRule.builder()
......@@ -1112,4 +1088,13 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline
Criterion criterion = selector.getCriterion(Criterion.Type.IPV4_DST);
return (criterion == null) ? null : ((IPCriterion) criterion).ip();
}
private static VlanId readVlanFromTreatment(TrafficTreatment treatment) {
for (Instruction i : treatment.allInstructions()) {
if (i instanceof ModVlanIdInstruction) {
return ((ModVlanIdInstruction) i).vlanId();
}
}
return null;
}
}
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.config.basics;
import com.google.common.annotations.Beta;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.Config;
/**
* Configuration for multicast.
*/
@Beta
public class McastConfig extends Config<ApplicationId> {
private static final String INGRESS_VLAN = "ingressVlan";
private static final String EGRESS_VLAN = "egressVlan";
@Override
public boolean isValid() {
return hasOnlyFields(INGRESS_VLAN, EGRESS_VLAN) &&
ingressVlan() != null && egressVlan() != null;
}
/**
* Gets ingress VLAN of multicast traffic.
*
* @return Ingress VLAN ID
*/
public VlanId ingressVlan() {
if (!object.has(INGRESS_VLAN)) {
return VlanId.NONE;
}
try {
return VlanId.vlanId(object.path(INGRESS_VLAN).asText());
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Sets ingress VLAN of multicast traffic.
*
* @param vlanId Ingress VLAN ID
* @return this {@link McastConfig}
*/
public McastConfig setIngressVlan(VlanId vlanId) {
if (vlanId == null) {
object.remove(INGRESS_VLAN);
} else {
object.put(INGRESS_VLAN, vlanId.toString());
}
return this;
}
/**
* Gets egress VLAN of multicast traffic.
*
* @return Egress VLAN ID
*/
public VlanId egressVlan() {
if (!object.has(EGRESS_VLAN)) {
return VlanId.NONE;
}
try {
return VlanId.vlanId(object.path(EGRESS_VLAN).asText());
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Sets egress VLAN of multicast traffic.
*
* @param vlanId Egress VLAN ID
* @return this {@link McastConfig}
*/
public McastConfig setEgressVlan(VlanId vlanId) {
if (vlanId == null) {
object.remove(EGRESS_VLAN);
} else {
object.put(EGRESS_VLAN, vlanId.toString());
}
return this;
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.config.basics;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.Beta;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.ConfigApplyDelegate;
import java.io.InputStream;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
/**
* Tests for class {@link McastConfig}.
*/
@Beta
public class McastConfigTest {
private static final TestApplicationId APP_ID =
new TestApplicationId(CoreService.CORE_APP_NAME);
private McastConfig config;
private McastConfig invalidConfig;
private static final VlanId INGRESS_VLAN_1 = VlanId.NONE;
private static final VlanId EGRESS_VLAN_1 = VlanId.NONE;
private static final VlanId INGRESS_VLAN_2 = VlanId.vlanId((short) 100);
private static final VlanId EGRESS_VLAN_2 = VlanId.vlanId((short) 100);
/**
* Initialize test related variables.
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
InputStream jsonStream = McastConfigTest.class
.getResourceAsStream("/mcast-config.json");
InputStream invalidJsonStream = McastConfigTest.class
.getResourceAsStream("/mcast-config-invalid.json");
ApplicationId subject = APP_ID;
String key = CoreService.CORE_APP_NAME;
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonStream);
JsonNode invalidJsonNode = mapper.readTree(invalidJsonStream);
ConfigApplyDelegate delegate = new MockDelegate();
config = new McastConfig();
config.init(subject, key, jsonNode, mapper, delegate);
invalidConfig = new McastConfig();
invalidConfig.init(subject, key, invalidJsonNode, mapper, delegate);
}
/**
* Tests config validity.
*
* @throws Exception
*/
@Test
public void isValid() throws Exception {
assertTrue(config.isValid());
assertFalse(invalidConfig.isValid());
}
/**
* Tests ingress VLAN getter.
*
* @throws Exception
*/
@Test
public void ingressVlan() throws Exception {
VlanId ingressVlan = config.ingressVlan();
assertNotNull("ingressVlan should not be null", ingressVlan);
assertThat(ingressVlan, is(INGRESS_VLAN_1));
}
/**
* Tests ingress VLAN setter.
*
* @throws Exception
*/
@Test
public void setIngressVlan() throws Exception {
config.setIngressVlan(INGRESS_VLAN_2);
VlanId ingressVlan = config.ingressVlan();
assertNotNull("ingressVlan should not be null", ingressVlan);
assertThat(ingressVlan, is(INGRESS_VLAN_2));
}
/**
* Tests egress VLAN getter.
*
* @throws Exception
*/
@Test
public void egressVlan() throws Exception {
VlanId egressVlan = config.egressVlan();
assertNotNull("egressVlan should not be null", egressVlan);
assertThat(egressVlan, is(EGRESS_VLAN_1));
}
/**
* Tests egress VLAN setter.
*
* @throws Exception
*/
@Test
public void setEgressVlan() throws Exception {
config.setEgressVlan(EGRESS_VLAN_2);
VlanId egressVlan = config.egressVlan();
assertNotNull("egressVlan should not be null", egressVlan);
assertThat(egressVlan, is(EGRESS_VLAN_2));
}
private class MockDelegate implements ConfigApplyDelegate {
@Override
public void onApply(Config config) {
}
}
}
\ No newline at end of file
{
"ingressVlan" : "None",
"egressVlan" : "5000"
}
\ No newline at end of file
{
"ingressVlan" : "None",
"egressVlan" : "None"
}
\ No newline at end of file