Jonathan Hart
Committed by Gerrit Code Review

Generalize IntentSynchronizer and separate reactive routing code

 * IntentSynchronizer can now handle any intent rather than having use
   case specific APIs
 * IntentSynchronizer does not generate or store intents anymore, it only
   perform synchronization
 * SdnIpFib generates and manages the procative route-based intents
 * ReactiveRoutingFib generates and manages the reactive intents
 * Unit tests have been tightned up to only test single components, rather
   than multiple components together
 * PeerConnectivityManager uses meaningful keys when creating intents

Change-Id: I4bb036ec8d056f43ece46f7dfc71d5e5a136b77d
Showing 21 changed files with 1785 additions and 1521 deletions
/*
* Copyright 2015 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.reactive.routing;
/**
* Specifies the type of an IP address or an IP prefix location.
*/
enum LocationType {
/**
* The location of an IP address or an IP prefix is in local SDN network.
*/
LOCAL,
/**
* The location of an IP address or an IP prefix is outside local SDN network.
*/
INTERNET,
/**
* There is no route for this IP address or IP prefix.
*/
NO_ROUTE
}
/*
* Copyright 2015 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.reactive.routing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
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.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
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.host.HostService;
import org.onosproject.net.intent.Constraint;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.constraint.PartialFailureConstraint;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* FIB component for reactive routing intents.
*/
public class ReactiveRoutingFib implements IntentRequestListener {
private static final int PRIORITY_OFFSET = 100;
private static final int PRIORITY_MULTIPLIER = 5;
protected static final ImmutableList<Constraint> CONSTRAINTS
= ImmutableList.of(new PartialFailureConstraint());
private final Logger log = LoggerFactory.getLogger(getClass());
private final ApplicationId appId;
private final HostService hostService;
private final RoutingConfigurationService configService;
private final InterfaceService interfaceService;
private final IntentSynchronizationService intentSynchronizer;
private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
/**
* Class constructor.
*
* @param appId application ID to use to generate intents
* @param hostService host service
* @param configService routing configuration service
* @param interfaceService interface service
* @param intentSynchronizer intent synchronization service
*/
public ReactiveRoutingFib(ApplicationId appId, HostService hostService,
RoutingConfigurationService configService,
InterfaceService interfaceService,
IntentSynchronizationService intentSynchronizer) {
this.appId = appId;
this.hostService = hostService;
this.configService = configService;
this.interfaceService = interfaceService;
this.intentSynchronizer = intentSynchronizer;
routeIntents = Maps.newConcurrentMap();
}
@Override
public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) {
checkNotNull(hostIpAddress);
Set<ConnectPoint> ingressPoints =
configService.getBgpPeerConnectPoints();
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (hostIpAddress.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
}
// Match the destination IP prefix at the first hop
IpPrefix ipPrefix = hostIpAddress.toIpPrefix();
selector.matchIPDst(ipPrefix);
// Rewrite the destination MAC address
MacAddress hostMac = null;
ConnectPoint egressPoint = null;
for (Host host : hostService.getHostsByIp(hostIpAddress)) {
if (host.mac() != null) {
hostMac = host.mac();
egressPoint = host.location();
break;
}
}
if (hostMac == null) {
hostService.startMonitoringIp(hostIpAddress);
return;
}
TrafficTreatment.Builder treatment =
DefaultTrafficTreatment.builder().setEthDst(hostMac);
Key key = Key.of(ipPrefix.toString(), appId);
int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ PRIORITY_OFFSET;
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPoints)
.egressPoint(egressPoint)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
log.trace("Generates ConnectivityInternetToHost intent {}", intent);
submitReactiveIntent(ipPrefix, intent);
}
@Override
public void setUpConnectivityHostToInternet(IpAddress hostIp, IpPrefix prefix,
IpAddress nextHopIpAddress) {
// Find the attachment point (egress interface) of the next hop
Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress);
if (egressInterface == null) {
log.warn("No outgoing interface found for {}",
nextHopIpAddress);
return;
}
Set<Host> hosts = hostService.getHostsByIp(nextHopIpAddress);
if (hosts.isEmpty()) {
log.warn("No host found for next hop IP address");
return;
}
MacAddress nextHopMacAddress = null;
for (Host host : hosts) {
nextHopMacAddress = host.mac();
break;
}
hosts = hostService.getHostsByIp(hostIp);
if (hosts.isEmpty()) {
log.warn("No host found for host IP address");
return;
}
Host host = hosts.stream().findFirst().get();
ConnectPoint ingressPoint = host.location();
// Generate the intent itself
ConnectPoint egressPort = egressInterface.connectPoint();
log.debug("Generating intent for prefix {}, next hop mac {}",
prefix, nextHopMacAddress);
// Match the destination IP prefix at the first hop
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (prefix.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPDst(prefix);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchIPv6Dst(prefix);
}
// Rewrite the destination MAC address
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
.setEthDst(nextHopMacAddress);
if (!egressInterface.vlan().equals(VlanId.NONE)) {
treatment.setVlanId(egressInterface.vlan());
// If we set VLAN ID, we have to make sure a VLAN tag exists.
// TODO support no VLAN -> VLAN routing
selector.matchVlanId(VlanId.ANY);
}
int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
Key key = Key.of(prefix.toString() + "-reactive", appId);
MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(Collections.singleton(ingressPoint))
.egressPoint(egressPort)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
submitReactiveIntent(prefix, intent);
}
@Override
public void setUpConnectivityHostToHost(IpAddress dstIpAddress,
IpAddress srcIpAddress,
MacAddress srcMacAddress,
ConnectPoint srcConnectPoint) {
checkNotNull(dstIpAddress);
checkNotNull(srcIpAddress);
checkNotNull(srcMacAddress);
checkNotNull(srcConnectPoint);
IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix();
IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
ConnectPoint dstConnectPoint = null;
MacAddress dstMacAddress = null;
for (Host host : hostService.getHostsByIp(dstIpAddress)) {
if (host.mac() != null) {
dstMacAddress = host.mac();
dstConnectPoint = host.location();
break;
}
}
if (dstMacAddress == null) {
hostService.startMonitoringIp(dstIpAddress);
return;
}
//
// Handle intent from source host to destination host
//
MultiPointToSinglePointIntent srcToDstIntent =
hostToHostIntentGenerator(dstIpAddress, dstConnectPoint,
dstMacAddress, srcConnectPoint);
submitReactiveIntent(dstIpPrefix, srcToDstIntent);
//
// Handle intent from destination host to source host
//
// Since we proactively handle the intent from destination host to
// source host, we should check whether there is an exiting intent
// first.
if (mp2pIntentExists(srcIpPrefix)) {
updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint);
return;
} else {
// There is no existing intent, create a new one.
MultiPointToSinglePointIntent dstToSrcIntent =
hostToHostIntentGenerator(srcIpAddress, srcConnectPoint,
srcMacAddress, dstConnectPoint);
submitReactiveIntent(srcIpPrefix, dstToSrcIntent);
}
}
/**
* Generates MultiPointToSinglePointIntent for both source host and
* destination host located in local SDN network.
*
* @param dstIpAddress the destination IP address
* @param dstConnectPoint the destination host connect point
* @param dstMacAddress the MAC address of destination host
* @param srcConnectPoint the connect point where packet-in from
* @return the generated MultiPointToSinglePointIntent
*/
private MultiPointToSinglePointIntent hostToHostIntentGenerator(
IpAddress dstIpAddress,
ConnectPoint dstConnectPoint,
MacAddress dstMacAddress,
ConnectPoint srcConnectPoint) {
checkNotNull(dstIpAddress);
checkNotNull(dstConnectPoint);
checkNotNull(dstMacAddress);
checkNotNull(srcConnectPoint);
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(srcConnectPoint);
IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (dstIpAddress.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPDst(dstIpPrefix);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchIPv6Dst(dstIpPrefix);
}
// Rewrite the destination MAC address
TrafficTreatment.Builder treatment =
DefaultTrafficTreatment.builder().setEthDst(dstMacAddress);
Key key = Key.of(dstIpPrefix.toString(), appId);
int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ PRIORITY_OFFSET;
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPoints)
.egressPoint(dstConnectPoint)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
log.trace("Generates ConnectivityHostToHost = {} ", intent);
return intent;
}
@Override
public void updateExistingMp2pIntent(IpPrefix ipPrefix,
ConnectPoint ingressConnectPoint) {
checkNotNull(ipPrefix);
checkNotNull(ingressConnectPoint);
MultiPointToSinglePointIntent existingIntent =
getExistingMp2pIntent(ipPrefix);
if (existingIntent != null) {
Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints();
// Add host connect point into ingressPoints of the existing intent
if (ingressPoints.add(ingressConnectPoint)) {
MultiPointToSinglePointIntent updatedMp2pIntent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(existingIntent.key())
.selector(existingIntent.selector())
.treatment(existingIntent.treatment())
.ingressPoints(ingressPoints)
.egressPoint(existingIntent.egressPoint())
.priority(existingIntent.priority())
.constraints(CONSTRAINTS)
.build();
log.trace("Update an existing MultiPointToSinglePointIntent "
+ "to new intent = {} ", updatedMp2pIntent);
submitReactiveIntent(ipPrefix, updatedMp2pIntent);
}
// If adding ingressConnectPoint to ingressPoints failed, it
// because between the time interval from checking existing intent
// to generating new intent, onos updated this intent due to other
// packet-in and the new intent also includes the
// ingressConnectPoint. This will not affect reactive routing.
}
}
/**
* Submits a reactive intent to the intent synchronizer.
*
* @param ipPrefix IP prefix of the intent
* @param intent intent to submit
*/
void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
routeIntents.put(ipPrefix, intent);
intentSynchronizer.submit(intent);
}
/**
* Gets the existing MultiPointToSinglePointIntent from memory for a given
* IP prefix.
*
* @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent
* @return the MultiPointToSinglePointIntent if found, otherwise null
*/
private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix ipPrefix) {
checkNotNull(ipPrefix);
return routeIntents.get(ipPrefix);
}
@Override
public boolean mp2pIntentExists(IpPrefix ipPrefix) {
checkNotNull(ipPrefix);
return routeIntents.get(ipPrefix) != null;
}
}
......@@ -25,27 +25,39 @@ import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
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.host.HostService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.RouteEntry;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.SdnIpService;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.Ethernet.TYPE_ARP;
import static org.onlab.packet.Ethernet.TYPE_IPV4;
import static org.onosproject.net.packet.PacketPriority.REACTIVE;
......@@ -74,16 +86,32 @@ public class SdnIpReactiveRouting {
protected RoutingService routingService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected SdnIpService sdnIpService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RoutingConfigurationService config;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
private ApplicationId appId;
private IntentRequestListener intentRequestListener;
private ReactiveRoutingProcessor processor =
new ReactiveRoutingProcessor();
@Activate
public void activate() {
appId = coreService.registerApplication(APP_NAME);
intentRequestListener = new ReactiveRoutingFib(appId, hostService,
config, interfaceService,
sdnIpService.getIntentSynchronizationService());
packetService.addProcessor(processor, PacketProcessor.director(2));
requestIntercepts();
log.info("SDN-IP Reactive Routing Started");
......@@ -168,12 +196,11 @@ public class SdnIpReactiveRouting {
IpAddress srcIp =
IpAddress.valueOf(ipv4Packet.getSourceAddress());
MacAddress srcMac = ethPkt.getSourceMAC();
routingService.packetReactiveProcessor(dstIp, srcIp,
srcConnectPoint, srcMac);
packetReactiveProcessor(dstIp, srcIp, srcConnectPoint, srcMac);
// TODO emit packet first or packetReactiveProcessor first
ConnectPoint egressConnectPoint = null;
egressConnectPoint = routingService.getEgressConnectPoint(dstIp);
egressConnectPoint = getEgressConnectPoint(dstIp);
if (egressConnectPoint != null) {
forwardPacketToDst(context, egressConnectPoint);
}
......@@ -185,6 +212,170 @@ public class SdnIpReactiveRouting {
}
/**
* Routes packet reactively.
*
* @param dstIpAddress the destination IP address of a packet
* @param srcIpAddress the source IP address of a packet
* @param srcConnectPoint the connect point where a packet comes from
* @param srcMacAddress the source MAC address of a packet
*/
private void packetReactiveProcessor(IpAddress dstIpAddress,
IpAddress srcIpAddress,
ConnectPoint srcConnectPoint,
MacAddress srcMacAddress) {
checkNotNull(dstIpAddress);
checkNotNull(srcIpAddress);
checkNotNull(srcConnectPoint);
checkNotNull(srcMacAddress);
//
// Step1: Try to update the existing intent first if it exists.
//
IpPrefix ipPrefix = null;
RouteEntry routeEntry = null;
if (config.isIpAddressLocal(dstIpAddress)) {
if (dstIpAddress.isIp4()) {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip4Address.BIT_LENGTH);
} else {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip6Address.BIT_LENGTH);
}
} else {
// Get IP prefix from BGP route table
routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
ipPrefix = routeEntry.prefix();
}
}
if (ipPrefix != null
&& intentRequestListener.mp2pIntentExists(ipPrefix)) {
intentRequestListener.updateExistingMp2pIntent(ipPrefix,
srcConnectPoint);
return;
}
//
// Step2: There is no existing intent for the destination IP address.
// Check whether it is necessary to create a new one. If necessary then
// create a new one.
//
TrafficType trafficType =
trafficTypeClassifier(srcConnectPoint, dstIpAddress);
switch (trafficType) {
case HOST_TO_INTERNET:
// If the destination IP address is outside the local SDN network.
// The Step 1 has already handled it. We do not need to do anything here.
intentRequestListener.setUpConnectivityHostToInternet(srcIpAddress,
ipPrefix, routeEntry.nextHop());
break;
case INTERNET_TO_HOST:
intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
break;
case HOST_TO_HOST:
intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
srcIpAddress, srcMacAddress, srcConnectPoint);
break;
case INTERNET_TO_INTERNET:
log.trace("This is transit traffic, "
+ "the intent should be preinstalled already");
break;
case DROP:
// TODO here should setUpDropPacketIntent(...);
// We need a new type of intent here.
break;
case UNKNOWN:
log.trace("This is unknown traffic, so we do nothing");
break;
default:
break;
}
}
/**
* Classifies the traffic and return the traffic type.
*
* @param srcConnectPoint the connect point where the packet comes from
* @param dstIp the destination IP address in packet
* @return the traffic type which this packet belongs to
*/
private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
IpAddress dstIp) {
LocationType dstIpLocationType = getLocationType(dstIp);
Optional<Interface> srcInterface =
interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
switch (dstIpLocationType) {
case INTERNET:
if (!srcInterface.isPresent()) {
return TrafficType.HOST_TO_INTERNET;
} else {
return TrafficType.INTERNET_TO_INTERNET;
}
case LOCAL:
if (!srcInterface.isPresent()) {
return TrafficType.HOST_TO_HOST;
} else {
// TODO Currently we only consider local public prefixes.
// In the future, we will consider the local private prefixes.
// If dstIpLocationType is a local private, we should return
// TrafficType.DROP.
return TrafficType.INTERNET_TO_HOST;
}
case NO_ROUTE:
return TrafficType.DROP;
default:
return TrafficType.UNKNOWN;
}
}
/**
* Evaluates the location of an IP address and returns the location type.
*
* @param ipAddress the IP address to evaluate
* @return the IP address location type
*/
private LocationType getLocationType(IpAddress ipAddress) {
if (config.isIpAddressLocal(ipAddress)) {
return LocationType.LOCAL;
} else if (routingService.getLongestMatchableRouteEntry(ipAddress) != null) {
return LocationType.INTERNET;
} else {
return LocationType.NO_ROUTE;
}
}
public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
LocationType type = getLocationType(dstIpAddress);
if (type == LocationType.LOCAL) {
Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
if (!hosts.isEmpty()) {
return hosts.iterator().next().location();
} else {
hostService.startMonitoringIp(dstIpAddress);
return null;
}
} else if (type == LocationType.INTERNET) {
IpAddress nextHopIpAddress = null;
RouteEntry routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
nextHopIpAddress = routeEntry.nextHop();
Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
if (it != null) {
return it.connectPoint();
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
}
/**
* Emits the specified packet onto the network.
*
* @param context the packet context
......
/*
* Copyright 2015 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.reactive.routing;
/**
* Specifies the type of traffic.
* <p>
* We classify traffic by the first packet of each traffic.
* </p>
*/
enum TrafficType {
/**
* Traffic from a host located in local SDN network wants to
* communicate with destination host located in Internet (outside
* local SDN network).
*/
HOST_TO_INTERNET,
/**
* Traffic from Internet wants to communicate with a host located
* in local SDN network.
*/
INTERNET_TO_HOST,
/**
* Both the source host and destination host of a traffic are in
* local SDN network.
*/
HOST_TO_HOST,
/**
* Traffic from Internet wants to traverse local SDN network.
*/
INTERNET_TO_INTERNET,
/**
* Any traffic wants to communicate with a destination which has
* no route, or traffic from Internet wants to access a local private
* IP address.
*/
DROP,
/**
* Traffic does not belong to the types above.
*/
UNKNOWN
}
......@@ -47,6 +47,16 @@ public interface IntentRequestListener {
ConnectPoint srcConnectPoint);
/**
* Sets up connectivity for packet from a local host to the Internet.
*
* @param hostIp IP address of the local host
* @param prefix external IP prefix that the host is talking to
* @param nextHopIpAddress IP address of the next hop router for the prefix
*/
void setUpConnectivityHostToInternet(IpAddress hostIp, IpPrefix prefix,
IpAddress nextHopIpAddress);
/**
* Adds one new ingress connect point into ingress points of an existing
* intent and resubmits the new intent.
* <p>
......
/*
* Copyright 2015 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.routing;
import org.onosproject.net.intent.Intent;
/**
* Submits and withdraws intents to the IntentService from a single point in
* the cluster at any one time. The provided intents will be synchronized with
* the IntentService on leadership change.
*/
public interface IntentSynchronizationService {
/**
* Submits and intent to the synchronizer.
* <p>
* The intent will be submitted directly to the IntentService if this node
* is the leader, otherwise it will be stored in the synchronizer for
* synchronization if this node becomes the leader.
* </p>
*
* @param intent intent to submit
*/
void submit(Intent intent);
/**
* Withdraws an intent from the synchronizer.
* <p>
* The intent will be withdrawn directly from the IntentService if this node
* is the leader. The intent will be removed from the synchronizer's
* in-memory storage.
* </p>
*
* @param intent intent to withdraw
*/
void withdraw(Intent intent);
}
......@@ -16,8 +16,6 @@
package org.onosproject.routing;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.routing.config.BgpConfig;
import java.util.Collection;
......@@ -32,63 +30,6 @@ public interface RoutingService {
Class<BgpConfig> CONFIG_CLASS = BgpConfig.class;
/**
* Specifies the type of an IP address or an IP prefix location.
*/
enum LocationType {
/**
* The location of an IP address or an IP prefix is in local SDN network.
*/
LOCAL,
/**
* The location of an IP address or an IP prefix is outside local SDN network.
*/
INTERNET,
/**
* There is no route for this IP address or IP prefix.
*/
NO_ROUTE
}
/**
* Specifies the type of traffic.
* <p>
* We classify traffic by the first packet of each traffic.
* </p>
*/
enum TrafficType {
/**
* Traffic from a host located in local SDN network wants to
* communicate with destination host located in Internet (outside
* local SDN network).
*/
HOST_TO_INTERNET,
/**
* Traffic from Internet wants to communicate with a host located
* in local SDN network.
*/
INTERNET_TO_HOST,
/**
* Both the source host and destination host of a traffic are in
* local SDN network.
*/
HOST_TO_HOST,
/**
* Traffic from Internet wants to traverse local SDN network.
*/
INTERNET_TO_INTERNET,
/**
* Any traffic wants to communicate with a destination which has
* no route, or traffic from Internet wants to access a local private
* IP address.
*/
DROP,
/**
* Traffic does not belong to the types above.
*/
UNKNOWN
}
/**
* Starts the routing service.
*/
void start();
......@@ -101,15 +42,6 @@ public interface RoutingService {
void addFibListener(FibListener fibListener);
/**
* Adds intent creation and submission listener.
*
* @param intentRequestListener listener to send intent creation and
* submission request to
*/
void addIntentRequestListener(IntentRequestListener
intentRequestListener);
/**
* Stops the routing service.
*/
void stop();
......@@ -129,14 +61,6 @@ public interface RoutingService {
Collection<RouteEntry> getRoutes6();
/**
* Evaluates the location of an IP address and returns the location type.
*
* @param ipAddress the IP address to evaluate
* @return the IP address location type
*/
LocationType getLocationType(IpAddress ipAddress);
/**
* Finds out the route entry which has the longest matchable IP prefix.
*
* @param ipAddress IP address used to find out longest matchable IP prefix
......@@ -145,25 +69,4 @@ public interface RoutingService {
*/
RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress);
/**
* Finds out the egress connect point where to emit the first packet
* based on destination IP address.
*
* @param dstIpAddress the destination IP address
* @return the egress connect point if found, otherwise null
*/
ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress);
/**
* Routes packet reactively.
*
* @param dstIpAddress the destination IP address of a packet
* @param srcIpAddress the source IP address of a packet
* @param srcConnectPoint the connect point where a packet comes from
* @param srcMacAddress the source MAC address of a packet
*/
void packetReactiveProcessor(IpAddress dstIpAddress,
IpAddress srcIpAddress,
ConnectPoint srcConnectPoint,
MacAddress srcMacAddress);
}
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.sdnip;
package org.onosproject.routing;
/**
* Service interface exported by SDN-IP.
......@@ -28,4 +28,12 @@ public interface SdnIpService {
*/
void modifyPrimary(boolean isPrimary);
/**
* Gets the intent synchronization service.
*
* @return intent synchronization service
*/
// TODO fix service resolution in SDN-IP
IntentSynchronizationService getIntentSynchronizationService();
}
......
......@@ -35,9 +35,7 @@ import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
......@@ -46,7 +44,6 @@ import org.onosproject.routing.BgpService;
import org.onosproject.routing.FibEntry;
import org.onosproject.routing.FibListener;
import org.onosproject.routing.FibUpdate;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.RouteEntry;
import org.onosproject.routing.RouteListener;
import org.onosproject.routing.RouteUpdate;
......@@ -61,7 +58,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
......@@ -100,7 +96,6 @@ public class Router implements RoutingService {
private final Map<IpAddress, MacAddress> ip2Mac = new ConcurrentHashMap<>();
private FibListener fibComponent;
private IntentRequestListener intentRequestListener;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
......@@ -145,12 +140,6 @@ public class Router implements RoutingService {
@Override
public void addFibListener(FibListener fibListener) {
this.fibComponent = checkNotNull(fibListener);
}
@Override
public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
this.intentRequestListener = checkNotNull(intentRequestListener);
}
@Override
......@@ -287,12 +276,10 @@ public class Router implements RoutingService {
void addRibRoute(RouteEntry routeEntry) {
if (routeEntry.isIp4()) {
// IPv4
ribTable4.put(createBinaryString(routeEntry.prefix()),
routeEntry);
ribTable4.put(createBinaryString(routeEntry.prefix()), routeEntry);
} else {
// IPv6
ribTable6.put(createBinaryString(routeEntry.prefix()),
routeEntry);
ribTable6.put(createBinaryString(routeEntry.prefix()), routeEntry);
}
}
......@@ -553,17 +540,6 @@ public class Router implements RoutingService {
}
@Override
public LocationType getLocationType(IpAddress ipAddress) {
if (routingConfigurationService.isIpAddressLocal(ipAddress)) {
return LocationType.LOCAL;
} else if (getLongestMatchableRouteEntry(ipAddress) != null) {
return LocationType.INTERNET;
} else {
return LocationType.NO_ROUTE;
}
}
@Override
public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
RouteEntry routeEntry = null;
Iterable<RouteEntry> routeEntries;
......@@ -587,142 +563,4 @@ public class Router implements RoutingService {
return routeEntry;
}
@Override
public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
LocationType type = getLocationType(dstIpAddress);
if (type == LocationType.LOCAL) {
Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
if (!hosts.isEmpty()) {
return hosts.iterator().next().location();
} else {
hostService.startMonitoringIp(dstIpAddress);
return null;
}
} else if (type == LocationType.INTERNET) {
IpAddress nextHopIpAddress = null;
RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
nextHopIpAddress = routeEntry.nextHop();
Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
if (it != null) {
return it.connectPoint();
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
}
@Override
public void packetReactiveProcessor(IpAddress dstIpAddress,
IpAddress srcIpAddress,
ConnectPoint srcConnectPoint,
MacAddress srcMacAddress) {
checkNotNull(dstIpAddress);
checkNotNull(srcIpAddress);
checkNotNull(srcConnectPoint);
checkNotNull(srcMacAddress);
//
// Step1: Try to update the existing intent first if it exists.
//
IpPrefix ipPrefix = null;
if (routingConfigurationService.isIpAddressLocal(dstIpAddress)) {
if (dstIpAddress.isIp4()) {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip4Address.BIT_LENGTH);
} else {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip6Address.BIT_LENGTH);
}
} else {
// Get IP prefix from BGP route table
RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
ipPrefix = routeEntry.prefix();
}
}
if (ipPrefix != null
&& intentRequestListener.mp2pIntentExists(ipPrefix)) {
intentRequestListener.updateExistingMp2pIntent(ipPrefix,
srcConnectPoint);
return;
}
//
// Step2: There is no existing intent for the destination IP address.
// Check whether it is necessary to create a new one. If necessary then
// create a new one.
//
TrafficType trafficType =
trafficTypeClassifier(srcConnectPoint, dstIpAddress);
switch (trafficType) {
case HOST_TO_INTERNET:
// If the destination IP address is outside the local SDN network.
// The Step 1 has already handled it. We do not need to do anything here.
break;
case INTERNET_TO_HOST:
intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
break;
case HOST_TO_HOST:
intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
srcIpAddress, srcMacAddress, srcConnectPoint);
break;
case INTERNET_TO_INTERNET:
log.trace("This is transit traffic, "
+ "the intent should be preinstalled already");
break;
case DROP:
// TODO here should setUpDropPaccketIntent(...);
// We need a new type of intent here.
break;
case UNKNOWN:
log.trace("This is unknown traffic, so we do nothing");
break;
default:
break;
}
}
/**
* Classifies the traffic and return the traffic type.
*
* @param srcConnectPoint the connect point where the packet comes from
* @param dstIp the destination IP address in packet
* @return the traffic type which this packet belongs to
*/
private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
IpAddress dstIp) {
LocationType dstIpLocationType = getLocationType(dstIp);
Optional<Interface> srcInterface =
interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
switch (dstIpLocationType) {
case INTERNET:
if (!srcInterface.isPresent()) {
return TrafficType.HOST_TO_INTERNET;
} else {
return TrafficType.INTERNET_TO_INTERNET;
}
case LOCAL:
if (!srcInterface.isPresent()) {
return TrafficType.HOST_TO_HOST;
} else {
// TODO Currently we only consider local public prefixes.
// In the future, we will consider the local private prefixes.
// If dstIpLocationType is a local private, we should return
// TrafficType.DROP.
return TrafficType.INTERNET_TO_HOST;
}
case NO_ROUTE:
return TrafficType.DROP;
default:
return TrafficType.UNKNOWN;
}
}
}
......
......@@ -18,10 +18,7 @@ package org.onosproject.routing.impl;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.routing.FibListener;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.RouteEntry;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.StaticRoutingService;
......@@ -49,11 +46,6 @@ public class StaticRouter implements RoutingService, StaticRoutingService {
}
@Override
public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
}
@Override
public void stop() {
}
......@@ -69,27 +61,11 @@ public class StaticRouter implements RoutingService, StaticRoutingService {
}
@Override
public LocationType getLocationType(IpAddress ipAddress) {
return null;
}
@Override
public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
return null;
}
@Override
public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
return null;
}
@Override
public void packetReactiveProcessor(IpAddress dstIpAddress, IpAddress srcIpAddress,
ConnectPoint srcConnectPoint, MacAddress srcMacAddress) {
}
@Override
public FibListener getFibListener() {
return fibListener;
}
......
......@@ -15,118 +15,79 @@
*/
package org.onosproject.sdnip;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
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.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
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.Criterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.Constraint;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.net.intent.constraint.PartialFailureConstraint;
import org.onosproject.routing.FibListener;
import org.onosproject.routing.FibUpdate;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.onosproject.routing.IntentSynchronizationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
/**
* Synchronizes intents between the in-memory intent store and the
* IntentService.
*/
public class IntentSynchronizer implements FibListener, IntentRequestListener {
private static final int PRIORITY_OFFSET = 100;
private static final int PRIORITY_MULTIPLIER = 5;
protected static final ImmutableList<Constraint> CONSTRAINTS
= ImmutableList.of(new PartialFailureConstraint());
public class IntentSynchronizer implements IntentSynchronizationService {
private static final Logger log =
LoggerFactory.getLogger(IntentSynchronizer.class);
private final ApplicationId appId;
private final IntentService intentService;
private final HostService hostService;
private final InterfaceService interfaceService;
private final Map<IntentKey, PointToPointIntent> peerIntents;
private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
private final Map<Key, Intent> intents;
//
// State to deal with SDN-IP Leader election and pushing Intents
//
private final ExecutorService bgpIntentsSynchronizerExecutor;
private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
private volatile boolean isElectedLeader = false;
private volatile boolean isActivatedLeader = false;
private final RoutingConfigurationService configService;
/**
* Class constructor.
*
* @param appId the Application ID
* @param intentService the intent service
*/
IntentSynchronizer(ApplicationId appId, IntentService intentService) {
this(appId, intentService,
newSingleThreadExecutor(groupedThreads("onos/sdnip", "sync")));
}
/**
* Class constructor.
*
* @param appId the Application ID
* @param intentService the intent service
* @param hostService the host service
* @param configService the SDN-IP configuration service
* @param interfaceService the interface service
* @param executorService executor service for synchronization thread
*/
IntentSynchronizer(ApplicationId appId, IntentService intentService,
HostService hostService,
RoutingConfigurationService configService,
InterfaceService interfaceService) {
ExecutorService executorService) {
this.appId = appId;
this.intentService = intentService;
this.hostService = hostService;
this.interfaceService = interfaceService;
peerIntents = new ConcurrentHashMap<>();
routeIntents = new ConcurrentHashMap<>();
this.configService = configService;
intents = new ConcurrentHashMap<>();
bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("sdnip-intents-synchronizer-%d").build());
bgpIntentsSynchronizerExecutor = executorService;
}
/**
* Starts the synchronizer.
*/
public void start() {
bgpIntentsSynchronizerExecutor.execute(this::doIntentSynchronizationThread);
}
/**
......@@ -187,794 +148,118 @@ public class IntentSynchronizer implements FibListener, IntentRequestListener {
}
}
/**
* Signals the synchronizer that the SDN-IP leadership has changed.
*
* @param isLeader true if this instance is now the leader, otherwise false
*/
public void leaderChanged(boolean isLeader) {
log.debug("SDN-IP Leader changed: {}", isLeader);
if (!isLeader) {
this.isElectedLeader = false;
this.isActivatedLeader = false;
return; // Nothing to do
}
this.isActivatedLeader = false;
this.isElectedLeader = true;
//
// Tell the Intents Synchronizer thread to start the synchronization
//
intentsSynchronizerSemaphore.release();
}
/**
* Gets the route intents.
*
* @return the route intents
*/
public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
List<MultiPointToSinglePointIntent> result = new LinkedList<>();
for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
routeIntents.entrySet()) {
result.add(entry.getValue());
}
return result;
}
/**
* Thread for Intent Synchronization.
*/
private void doIntentSynchronizationThread() {
boolean interrupted = false;
try {
while (!interrupted) {
try {
intentsSynchronizerSemaphore.acquire();
//
// Drain all permits, because a single synchronization is
// sufficient.
//
intentsSynchronizerSemaphore.drainPermits();
} catch (InterruptedException e) {
interrupted = true;
break;
}
synchronizeIntents();
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/**
* Submits a collection of point-to-point intents.
*
* @param intents the intents to submit
*/
void submitPeerIntents(Collection<PointToPointIntent> intents) {
@Override
public void submit(Intent intent) {
synchronized (this) {
// Store the intents in memory
for (PointToPointIntent intent : intents) {
peerIntents.put(new IntentKey(intent), intent);
}
// Push the intents
intents.put(intent.key(), intent);
if (isElectedLeader && isActivatedLeader) {
log.debug("SDN-IP Submitting all Peer Intents...");
for (Intent intent : intents) {
log.trace("SDN-IP Submitting intents: {}", intent);
intentService.submit(intent);
}
log.trace("SDN-IP Submitting intent: {}", intent);
intentService.submit(intent);
}
}
}
/**
* Submits a MultiPointToSinglePointIntent for reactive routing.
*
* @param ipPrefix the IP prefix to match in a MultiPointToSinglePointIntent
* @param intent the intent to submit
*/
void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
@Override
public void withdraw(Intent intent) {
synchronized (this) {
// Store the intent in memory
routeIntents.put(ipPrefix, intent);
// Push the intent
intents.remove(intent.key(), intent);
if (isElectedLeader && isActivatedLeader) {
log.trace("SDN-IP submitting reactive routing intent: {}", intent);
intentService.submit(intent);
log.trace("SDN-IP Withdrawing intent: {}", intent);
intentService.withdraw(intent);
}
}
}
/**
* Generates a route intent for a prefix, the next hop IP address, and
* the next hop MAC address.
* <p/>
* This method will find the egress interface for the intent.
* Intent will match dst IP prefix and rewrite dst MAC address at all other
* border switches, then forward packets according to dst MAC address.
* Signals the synchronizer that the SDN-IP leadership has changed.
*
* @param prefix IP prefix of the route to add
* @param nextHopIpAddress IP address of the next hop
* @param nextHopMacAddress MAC address of the next hop
* @return the generated intent, or null if no intent should be submitted
* @param isLeader true if this instance is now the leader, otherwise false
*/
private MultiPointToSinglePointIntent generateRouteIntent(
IpPrefix prefix,
IpAddress nextHopIpAddress,
MacAddress nextHopMacAddress) {
// Find the attachment point (egress interface) of the next hop
Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress);
if (egressInterface == null) {
log.warn("No outgoing interface found for {}",
nextHopIpAddress);
return null;
}
//
// Generate the intent itself
//
Set<ConnectPoint> ingressPorts = new HashSet<>();
ConnectPoint egressPort = egressInterface.connectPoint();
log.debug("Generating intent for prefix {}, next hop mac {}",
prefix, nextHopMacAddress);
for (Interface intf : interfaceService.getInterfaces()) {
// TODO this should be only peering interfaces
if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
ConnectPoint srcPort = intf.connectPoint();
ingressPorts.add(srcPort);
}
}
// Match the destination IP prefix at the first hop
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (prefix.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPDst(prefix);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchIPv6Dst(prefix);
}
// Rewrite the destination MAC address
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
.setEthDst(nextHopMacAddress);
if (!egressInterface.vlan().equals(VlanId.NONE)) {
treatment.setVlanId(egressInterface.vlan());
// If we set VLAN ID, we have to make sure a VLAN tag exists.
// TODO support no VLAN -> VLAN routing
selector.matchVlanId(VlanId.ANY);
}
int priority =
prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
Key key = Key.of(prefix.toString(), appId);
return MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPorts)
.egressPoint(egressPort)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
}
@Override
public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) {
checkNotNull(hostIpAddress);
Set<ConnectPoint> ingressPoints =
configService.getBgpPeerConnectPoints();
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (hostIpAddress.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
}
public void leaderChanged(boolean isLeader) {
log.debug("SDN-IP Leader changed: {}", isLeader);
// Match the destination IP prefix at the first hop
IpPrefix ipPrefix = hostIpAddress.toIpPrefix();
selector.matchIPDst(ipPrefix);
// Rewrite the destination MAC address
MacAddress hostMac = null;
ConnectPoint egressPoint = null;
for (Host host : hostService.getHostsByIp(hostIpAddress)) {
if (host.mac() != null) {
hostMac = host.mac();
egressPoint = host.location();
break;
}
}
if (hostMac == null) {
hostService.startMonitoringIp(hostIpAddress);
return;
if (!isLeader) {
this.isElectedLeader = false;
this.isActivatedLeader = false;
return; // Nothing to do
}
this.isActivatedLeader = false;
this.isElectedLeader = true;
TrafficTreatment.Builder treatment =
DefaultTrafficTreatment.builder().setEthDst(hostMac);
Key key = Key.of(ipPrefix.toString(), appId);
int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ PRIORITY_OFFSET;
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPoints)
.egressPoint(egressPoint)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
log.trace("Generates ConnectivityInternetToHost intent {}", intent);
submitReactiveIntent(ipPrefix, intent);
}
@Override
public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
//
// NOTE: Semantically, we MUST withdraw existing intents before
// submitting new intents.
//
synchronized (this) {
MultiPointToSinglePointIntent intent;
log.debug("SDN-IP submitting intents = {} withdrawing = {}",
updates.size(), withdraws.size());
//
// Prepare the Intent batch operations for the intents to withdraw
//
for (FibUpdate withdraw : withdraws) {
checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
"FibUpdate with wrong type in withdraws list");
IpPrefix prefix = withdraw.entry().prefix();
intent = routeIntents.remove(prefix);
if (intent == null) {
log.trace("SDN-IP No intent in routeIntents to delete " +
"for prefix: {}", prefix);
continue;
}
if (isElectedLeader && isActivatedLeader) {
log.trace("SDN-IP Withdrawing intent: {}", intent);
intentService.withdraw(intent);
}
}
//
// Prepare the Intent batch operations for the intents to submit
//
for (FibUpdate update : updates) {
checkArgument(update.type() == FibUpdate.Type.UPDATE,
"FibUpdate with wrong type in updates list");
IpPrefix prefix = update.entry().prefix();
intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
update.entry().nextHopMac());
if (intent == null) {
// This preserves the old semantics - if an intent can't be
// generated, we don't do anything with that prefix. But
// perhaps we should withdraw the old intent anyway?
continue;
}
MultiPointToSinglePointIntent oldIntent =
routeIntents.put(prefix, intent);
if (isElectedLeader && isActivatedLeader) {
if (oldIntent != null) {
log.trace("SDN-IP Withdrawing old intent: {}",
oldIntent);
intentService.withdraw(oldIntent);
}
log.trace("SDN-IP Submitting intent: {}", intent);
intentService.submit(intent);
}
}
}
// Run the synchronization method off-thread
bgpIntentsSynchronizerExecutor.execute(this::synchronizeIntents);
}
/**
* Synchronize the in-memory Intents with the Intents in the Intent
* framework.
*/
void synchronizeIntents() {
synchronized (this) {
Map<IntentKey, Intent> localIntents = new HashMap<>();
Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
Collection<Intent> storeInMemoryIntents = new LinkedList<>();
Collection<Intent> addIntents = new LinkedList<>();
Collection<Intent> deleteIntents = new LinkedList<>();
if (!isElectedLeader) {
return; // Nothing to do: not the leader anymore
}
log.debug("SDN-IP synchronizing all intents...");
// Prepare the local intents
for (Intent intent : routeIntents.values()) {
localIntents.put(new IntentKey(intent), intent);
}
for (Intent intent : peerIntents.values()) {
localIntents.put(new IntentKey(intent), intent);
private void synchronizeIntents() {
Map<Key, Intent> serviceIntents = new HashMap<>();
intentService.getIntents().forEach(i -> {
if (i.appId().equals(appId)) {
serviceIntents.put(i.key(), i);
}
});
// Fetch all intents for this application
for (Intent intent : intentService.getIntents()) {
if (!intent.appId().equals(appId)) {
continue;
}
fetchedIntents.put(new IntentKey(intent), intent);
}
if (log.isDebugEnabled()) {
for (Intent intent: fetchedIntents.values()) {
log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
intent);
}
}
computeIntentsDelta(localIntents, fetchedIntents,
storeInMemoryIntents, addIntents,
deleteIntents);
List<Intent> intentsToAdd = new LinkedList<>();
List<Intent> intentsToRemove = new LinkedList<>();
//
// Perform the actions:
// 1. Store in memory fetched intents that are same. Can be done
// even if we are not the leader anymore
// 2. Delete intents: check if the leader before the operation
// 3. Add intents: check if the leader before the operation
//
for (Intent intent : storeInMemoryIntents) {
// Store the intent in memory based on its type
if (intent instanceof MultiPointToSinglePointIntent) {
MultiPointToSinglePointIntent mp2pIntent =
(MultiPointToSinglePointIntent) intent;
// Find the IP prefix
Criterion c =
mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
if (c == null) {
// Try IPv6
c =
mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
}
if (c != null && c instanceof IPCriterion) {
IPCriterion ipCriterion = (IPCriterion) c;
IpPrefix ipPrefix = ipCriterion.ip();
if (ipPrefix == null) {
continue;
}
log.trace("SDN-IP Intent Synchronizer: updating " +
"in-memory Route Intent for prefix {}",
ipPrefix);
routeIntents.put(ipPrefix, mp2pIntent);
} else {
log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
mp2pIntent.id());
}
continue;
}
if (intent instanceof PointToPointIntent) {
PointToPointIntent p2pIntent = (PointToPointIntent) intent;
log.trace("SDN-IP Intent Synchronizer: updating " +
"in-memory Peer Intent {}", p2pIntent);
peerIntents.put(new IntentKey(intent), p2pIntent);
continue;
}
}
// Withdraw Intents
for (Intent intent : deleteIntents) {
intentService.withdraw(intent);
log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
intent);
}
if (!isElectedLeader) {
log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
"not elected leader anymore");
isActivatedLeader = false;
return;
}
// Add Intents
for (Intent intent : addIntents) {
intentService.submit(intent);
log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
intent);
}
if (!isElectedLeader) {
log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
"not elected leader anymore");
isActivatedLeader = false;
return;
}
if (isElectedLeader) {
isActivatedLeader = true; // Allow push of Intents
for (Intent localIntent : intents.values()) {
Intent serviceIntent = serviceIntents.remove(localIntent.key());
if (serviceIntent == null) {
intentsToAdd.add(localIntent);
} else {
isActivatedLeader = false;
}
log.debug("SDN-IP intent synchronization completed");
}
}
/**
* Computes the delta in two sets of Intents: local in-memory Intents,
* and intents fetched from the Intent framework.
*
* @param localIntents the local in-memory Intents
* @param fetchedIntents the Intents fetched from the Intent framework
* @param storeInMemoryIntents the Intents that should be stored in memory.
* Note: This Collection must be allocated by the caller, and it will
* be populated by this method.
* @param addIntents the Intents that should be added to the Intent
* framework. Note: This Collection must be allocated by the caller, and
* it will be populated by this method.
* @param deleteIntents the Intents that should be deleted from the Intent
* framework. Note: This Collection must be allocated by the caller, and
* it will be populated by this method.
*/
private void computeIntentsDelta(
final Map<IntentKey, Intent> localIntents,
final Map<IntentKey, Intent> fetchedIntents,
Collection<Intent> storeInMemoryIntents,
Collection<Intent> addIntents,
Collection<Intent> deleteIntents) {
//
// Compute the deltas between the LOCAL in-memory Intents and the
// FETCHED Intents:
// - If an Intent is in both the LOCAL and FETCHED sets:
// If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
// the LOCAL Intent should be added/installed; otherwise the
// FETCHED intent should be stored in the local memory
// (i.e., override the LOCAL Intent) to preserve the original
// Intent ID.
// - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
// Intent should be added/installed.
// - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
// Intent should be deleted/withdrawn.
//
for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
IntentKey intentKey = entry.getKey();
Intent localIntent = entry.getValue();
Intent fetchedIntent = fetchedIntents.get(intentKey);
if (fetchedIntent == null) {
//
// No FETCHED Intent found: push the LOCAL Intent.
//
addIntents.add(localIntent);
continue;
}
IntentState state =
intentService.getIntentState(fetchedIntent.key());
if (state == null ||
state == IntentState.WITHDRAWING ||
state == IntentState.WITHDRAWN) {
// The intent has been withdrawn but according to our route
// table it should be installed. We'll reinstall it.
addIntents.add(localIntent);
continue;
IntentState state = intentService.getIntentState(serviceIntent.key());
if (!IntentUtils.equals(serviceIntent, localIntent) || state == null ||
state == IntentState.WITHDRAW_REQ ||
state == IntentState.WITHDRAWING ||
state == IntentState.WITHDRAWN) {
intentsToAdd.add(localIntent);
}
}
storeInMemoryIntents.add(fetchedIntent);
}
for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
IntentKey intentKey = entry.getKey();
Intent fetchedIntent = entry.getValue();
Intent localIntent = localIntents.get(intentKey);
if (localIntent != null) {
continue;
for (Intent serviceIntent : serviceIntents.values()) {
IntentState state = intentService.getIntentState(serviceIntent.key());
if (state != null && state != IntentState.WITHDRAW_REQ
&& state != IntentState.WITHDRAWING
&& state != IntentState.WITHDRAWN) {
intentsToRemove.add(serviceIntent);
}
IntentState state =
intentService.getIntentState(fetchedIntent.key());
if (state == null ||
state == IntentState.WITHDRAWING ||
state == IntentState.WITHDRAWN) {
// Nothing to do. The intent has been already withdrawn.
continue;
}
//
// No LOCAL Intent found: delete/withdraw the FETCHED Intent.
//
deleteIntents.add(fetchedIntent);
}
}
/**
* Helper class that can be used to compute the key for an Intent by
* by excluding the Intent ID.
*/
static final class IntentKey {
private final Intent intent;
/**
* Constructor.
*
* @param intent the intent to use
*/
IntentKey(Intent intent) {
checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
(intent instanceof PointToPointIntent),
"Intent type not recognized", intent);
this.intent = intent;
}
/**
* Compares two Multi-Point to Single-Point Intents whether they
* represent same logical intention.
*
* @param intent1 the first Intent to compare
* @param intent2 the second Intent to compare
* @return true if both Intents represent same logical intention,
* otherwise false
*/
static boolean equalIntents(MultiPointToSinglePointIntent intent1,
MultiPointToSinglePointIntent intent2) {
return Objects.equals(intent1.appId(), intent2.appId()) &&
Objects.equals(intent1.selector(), intent2.selector()) &&
Objects.equals(intent1.treatment(), intent2.treatment()) &&
Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
Objects.equals(intent1.egressPoint(), intent2.egressPoint());
}
log.debug("SDN-IP Intent Synchronizer: submitting {}, withdrawing {}",
intentsToAdd.size(), intentsToRemove.size());
/**
* Compares two Point-to-Point Intents whether they represent
* same logical intention.
*
* @param intent1 the first Intent to compare
* @param intent2 the second Intent to compare
* @return true if both Intents represent same logical intention,
* otherwise false
*/
static boolean equalIntents(PointToPointIntent intent1,
PointToPointIntent intent2) {
return Objects.equals(intent1.appId(), intent2.appId()) &&
Objects.equals(intent1.selector(), intent2.selector()) &&
Objects.equals(intent1.treatment(), intent2.treatment()) &&
Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
Objects.equals(intent1.egressPoint(), intent2.egressPoint());
// Withdraw Intents
for (Intent intent : intentsToRemove) {
intentService.withdraw(intent);
log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
intent);
}
@Override
public int hashCode() {
if (intent instanceof PointToPointIntent) {
PointToPointIntent p2pIntent = (PointToPointIntent) intent;
return Objects.hash(p2pIntent.appId(),
p2pIntent.resources(),
p2pIntent.selector(),
p2pIntent.treatment(),
p2pIntent.constraints(),
p2pIntent.ingressPoint(),
p2pIntent.egressPoint());
}
if (intent instanceof MultiPointToSinglePointIntent) {
MultiPointToSinglePointIntent m2pIntent =
(MultiPointToSinglePointIntent) intent;
return Objects.hash(m2pIntent.appId(),
m2pIntent.resources(),
m2pIntent.selector(),
m2pIntent.treatment(),
m2pIntent.constraints(),
m2pIntent.ingressPoints(),
m2pIntent.egressPoint());
}
checkArgument(false, "Intent type not recognized", intent);
return 0;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (!(obj instanceof IntentKey))) {
return false;
}
IntentKey other = (IntentKey) obj;
if (this.intent instanceof PointToPointIntent) {
if (!(other.intent instanceof PointToPointIntent)) {
return false;
}
return equalIntents((PointToPointIntent) this.intent,
(PointToPointIntent) other.intent);
}
if (this.intent instanceof MultiPointToSinglePointIntent) {
if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
return false;
}
return equalIntents(
(MultiPointToSinglePointIntent) this.intent,
(MultiPointToSinglePointIntent) other.intent);
}
checkArgument(false, "Intent type not recognized", intent);
return false;
if (!isElectedLeader) {
log.debug("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
"not elected leader anymore");
isActivatedLeader = false;
return;
}
}
@Override
public void setUpConnectivityHostToHost(IpAddress dstIpAddress,
IpAddress srcIpAddress,
MacAddress srcMacAddress,
ConnectPoint srcConnectPoint) {
checkNotNull(dstIpAddress);
checkNotNull(srcIpAddress);
checkNotNull(srcMacAddress);
checkNotNull(srcConnectPoint);
IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix();
IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
ConnectPoint dstConnectPoint = null;
MacAddress dstMacAddress = null;
for (Host host : hostService.getHostsByIp(dstIpAddress)) {
if (host.mac() != null) {
dstMacAddress = host.mac();
dstConnectPoint = host.location();
break;
}
// Add Intents
for (Intent intent : intentsToAdd) {
intentService.submit(intent);
log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
intent);
}
if (dstMacAddress == null) {
hostService.startMonitoringIp(dstIpAddress);
if (!isElectedLeader) {
log.debug("SDN-IP Intent Synchronizer: cannot submit intents: " +
"not elected leader anymore");
isActivatedLeader = false;
return;
}
//
// Handle intent from source host to destination host
//
MultiPointToSinglePointIntent srcToDstIntent =
hostToHostIntentGenerator(dstIpAddress, dstConnectPoint,
dstMacAddress, srcConnectPoint);
submitReactiveIntent(dstIpPrefix, srcToDstIntent);
//
// Handle intent from destination host to source host
//
// Since we proactively handle the intent from destination host to
// source host, we should check whether there is an exiting intent
// first.
if (mp2pIntentExists(srcIpPrefix)) {
updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint);
return;
if (isElectedLeader) {
isActivatedLeader = true; // Allow push of Intents
} else {
// There is no existing intent, create a new one.
MultiPointToSinglePointIntent dstToSrcIntent =
hostToHostIntentGenerator(srcIpAddress, srcConnectPoint,
srcMacAddress, dstConnectPoint);
submitReactiveIntent(srcIpPrefix, dstToSrcIntent);
isActivatedLeader = false;
}
log.debug("SDN-IP intent synchronization completed");
}
/**
* Generates MultiPointToSinglePointIntent for both source host and
* destination host located in local SDN network.
*
* @param dstIpAddress the destination IP address
* @param dstConnectPoint the destination host connect point
* @param dstMacAddress the MAC address of destination host
* @param srcConnectPoint the connect point where packet-in from
* @return the generated MultiPointToSinglePointIntent
*/
private MultiPointToSinglePointIntent hostToHostIntentGenerator(
IpAddress dstIpAddress,
ConnectPoint dstConnectPoint,
MacAddress dstMacAddress,
ConnectPoint srcConnectPoint) {
checkNotNull(dstIpAddress);
checkNotNull(dstConnectPoint);
checkNotNull(dstMacAddress);
checkNotNull(srcConnectPoint);
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(srcConnectPoint);
IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (dstIpAddress.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPDst(dstIpPrefix);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchIPv6Dst(dstIpPrefix);
}
// Rewrite the destination MAC address
TrafficTreatment.Builder treatment =
DefaultTrafficTreatment.builder().setEthDst(dstMacAddress);
Key key = Key.of(dstIpPrefix.toString(), appId);
int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ PRIORITY_OFFSET;
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPoints)
.egressPoint(dstConnectPoint)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
log.trace("Generates ConnectivityHostToHost = {} ", intent);
return intent;
}
@Override
public void updateExistingMp2pIntent(IpPrefix ipPrefix,
ConnectPoint ingressConnectPoint) {
checkNotNull(ipPrefix);
checkNotNull(ingressConnectPoint);
MultiPointToSinglePointIntent existingIntent =
getExistingMp2pIntent(ipPrefix);
if (existingIntent != null) {
Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints();
// Add host connect point into ingressPoints of the existing intent
if (ingressPoints.add(ingressConnectPoint)) {
MultiPointToSinglePointIntent updatedMp2pIntent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(existingIntent.key())
.selector(existingIntent.selector())
.treatment(existingIntent.treatment())
.ingressPoints(ingressPoints)
.egressPoint(existingIntent.egressPoint())
.priority(existingIntent.priority())
.constraints(CONSTRAINTS)
.build();
log.trace("Update an existing MultiPointToSinglePointIntent "
+ "to new intent = {} ", updatedMp2pIntent);
submitReactiveIntent(ipPrefix, updatedMp2pIntent);
}
// If adding ingressConnectPoint to ingressPoints failed, it
// because between the time interval from checking existing intent
// to generating new intent, onos updated this intent due to other
// packet-in and the new intent also includes the
// ingressConnectPoint. This will not affect reactive routing.
}
}
@Override
public boolean mp2pIntentExists(IpPrefix ipPrefix) {
checkNotNull(ipPrefix);
return routeIntents.get(ipPrefix) != null;
}
/**
* Gets the existing MultiPointToSinglePointIntent from memory for a given
* IP prefix.
*
* @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent
* @return the MultiPointToSinglePointIntent if found, otherwise null
*/
private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix
ipPrefix) {
checkNotNull(ipPrefix);
return routeIntents.get(ipPrefix);
}
}
......
/*
* Copyright 2015 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.sdnip;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.PointToPointIntent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Utilities for dealing with intents.
*/
public final class IntentUtils {
private static final Logger log = LoggerFactory.getLogger(IntentUtils.class);
private IntentUtils() {
}
/**
* Checks if two intents represent the same value.
*
* <p>({@link Intent#equals(Object)} only checks ID equality)</p>
*
* <p>Both intents must be of the same type.</p>
*
* @param one first intent
* @param two second intent
* @return true if the two intents represent the same value, otherwise false
*/
public static boolean equals(Intent one, Intent two) {
checkArgument(one.getClass() == two.getClass(),
"Intents are not the same type");
if (!(Objects.equals(one.appId(), two.appId()) &&
Objects.equals(one.key(), two.key()))) {
return false;
}
if (one instanceof MultiPointToSinglePointIntent) {
MultiPointToSinglePointIntent intent1 = (MultiPointToSinglePointIntent) one;
MultiPointToSinglePointIntent intent2 = (MultiPointToSinglePointIntent) two;
return Objects.equals(intent1.selector(), intent2.selector()) &&
Objects.equals(intent1.treatment(), intent2.treatment()) &&
Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
Objects.equals(intent1.egressPoint(), intent2.egressPoint());
} else if (one instanceof PointToPointIntent) {
PointToPointIntent intent1 = (PointToPointIntent) one;
PointToPointIntent intent2 = (PointToPointIntent) two;
return Objects.equals(intent1.selector(), intent2.selector()) &&
Objects.equals(intent1.treatment(), intent2.treatment()) &&
Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
Objects.equals(intent1.egressPoint(), intent2.egressPoint());
} else {
log.error("Unimplemented intent type");
return false;
}
}
}
......@@ -15,6 +15,8 @@
*/
package org.onosproject.sdnip;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.IPv6;
......@@ -22,16 +24,18 @@ import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.config.NetworkConfigService;
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.host.InterfaceIpAddress;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.config.BgpConfig;
import org.slf4j.Logger;
......@@ -49,18 +53,26 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class PeerConnectivityManager {
private static final int PRIORITY_OFFSET = 1000;
private static final String SUFFIX_DST = "dst";
private static final String SUFFIX_SRC = "src";
private static final String SUFFIX_ICMP = "icmp";
private static final Logger log = LoggerFactory.getLogger(
PeerConnectivityManager.class);
private static final short BGP_PORT = 179;
private final IntentSynchronizer intentSynchronizer;
private final IntentSynchronizationService intentSynchronizer;
private final NetworkConfigService configService;
private final InterfaceService interfaceService;
private final ApplicationId appId;
private final ApplicationId routerAppId;
// Just putting something random here for now. Figure out exactly what
// indexes we need when we start making use of them.
private final Multimap<BgpConfig.BgpSpeakerConfig, PointToPointIntent> peerIntents;
/**
* Creates a new PeerConnectivityManager.
*
......@@ -71,7 +83,7 @@ public class PeerConnectivityManager {
* @param routerAppId application ID
*/
public PeerConnectivityManager(ApplicationId appId,
IntentSynchronizer intentSynchronizer,
IntentSynchronizationService intentSynchronizer,
NetworkConfigService configService,
ApplicationId routerAppId,
InterfaceService interfaceService) {
......@@ -80,6 +92,8 @@ public class PeerConnectivityManager {
this.configService = configService;
this.routerAppId = routerAppId;
this.interfaceService = interfaceService;
peerIntents = HashMultimap.create();
}
/**
......@@ -100,8 +114,6 @@ public class PeerConnectivityManager {
* BGP speakers and external BGP peers.
*/
private void setUpConnectivity() {
List<PointToPointIntent> intents = new ArrayList<>();
BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
if (config == null) {
......@@ -113,11 +125,12 @@ public class PeerConnectivityManager {
log.debug("Start to set up BGP paths for BGP speaker: {}",
bgpSpeaker);
intents.addAll(buildSpeakerIntents(bgpSpeaker));
}
buildSpeakerIntents(bgpSpeaker).forEach(i -> {
peerIntents.put(bgpSpeaker, i);
intentSynchronizer.submit(i);
});
// Submit all the intents.
intentSynchronizer.submitPeerIntents(intents);
}
}
private Collection<PointToPointIntent> buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker) {
......@@ -167,8 +180,8 @@ public class PeerConnectivityManager {
List<PointToPointIntent> intents = new ArrayList<>();
TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
TrafficSelector selector;
Key key;
byte tcpProtocol;
byte icmpProtocol;
......@@ -188,8 +201,11 @@ public class PeerConnectivityManager {
null,
BGP_PORT);
key = buildKey(ipOne, ipTwo, SUFFIX_DST);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portOne)
......@@ -204,8 +220,11 @@ public class PeerConnectivityManager {
BGP_PORT,
null);
key = buildKey(ipOne, ipTwo, SUFFIX_SRC);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portOne)
......@@ -220,8 +239,11 @@ public class PeerConnectivityManager {
null,
BGP_PORT);
key = buildKey(ipTwo, ipOne, SUFFIX_DST);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portTwo)
......@@ -236,8 +258,11 @@ public class PeerConnectivityManager {
BGP_PORT,
null);
key = buildKey(ipTwo, ipOne, SUFFIX_SRC);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portTwo)
......@@ -252,8 +277,11 @@ public class PeerConnectivityManager {
null,
null);
key = buildKey(ipOne, ipTwo, SUFFIX_ICMP);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portOne)
......@@ -268,8 +296,11 @@ public class PeerConnectivityManager {
null,
null);
key = buildKey(ipTwo, ipOne, SUFFIX_ICMP);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.ingressPoint(portTwo)
......@@ -316,4 +347,27 @@ public class PeerConnectivityManager {
return builder.build();
}
/**
* Builds an intent Key for a point-to-point intent based off the source
* and destination IP address, as well as a suffix String to distinguish
* between different types of intents between the same source and
* destination.
*
* @param srcIp source IP address
* @param dstIp destination IP address
* @param suffix suffix string
* @return
*/
private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) {
String keyString = new StringBuilder()
.append(srcIp.toString())
.append("-")
.append(dstIp.toString())
.append("-")
.append(suffix)
.toString();
return Key.of(keyString, appId);
}
}
......
......@@ -32,7 +32,9 @@ import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.IntentService;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.SdnIpService;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.slf4j.Logger;
......@@ -79,6 +81,7 @@ public class SdnIp implements SdnIpService {
private IntentSynchronizer intentSynchronizer;
private PeerConnectivityManager peerConnectivity;
private SdnIpFib fib;
private LeadershipEventListener leadershipEventListener =
new InnerLeadershipEventListener();
......@@ -93,10 +96,7 @@ public class SdnIp implements SdnIpService {
localControllerNode = clusterService.getLocalNode();
intentSynchronizer = new IntentSynchronizer(appId, intentService,
hostService,
config,
interfaceService);
intentSynchronizer = new IntentSynchronizer(appId, intentService);
intentSynchronizer.start();
peerConnectivity = new PeerConnectivityManager(appId,
......@@ -106,8 +106,9 @@ public class SdnIp implements SdnIpService {
interfaceService);
peerConnectivity.start();
routingService.addFibListener(intentSynchronizer);
routingService.addIntentRequestListener(intentSynchronizer);
fib = new SdnIpFib(appId, interfaceService, intentSynchronizer);
routingService.addFibListener(fib);
routingService.start();
leadershipService.addListener(leadershipEventListener);
......@@ -131,6 +132,11 @@ public class SdnIp implements SdnIpService {
intentSynchronizer.leaderChanged(isPrimary);
}
@Override
public IntentSynchronizationService getIntentSynchronizationService() {
return intentSynchronizer;
}
/**
* Converts DPIDs of the form xx:xx:xx:xx:xx:xx:xx to OpenFlow provider
* device URIs.
......
/*
* Copyright 2015 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.sdnip;
import com.google.common.collect.ImmutableList;
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.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
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.intent.Constraint;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.constraint.PartialFailureConstraint;
import org.onosproject.routing.FibListener;
import org.onosproject.routing.FibUpdate;
import org.onosproject.routing.IntentSynchronizationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.google.common.base.Preconditions.checkArgument;
/**
* FIB component of SDN-IP.
*/
public class SdnIpFib implements FibListener {
private Logger log = LoggerFactory.getLogger(getClass());
private static final int PRIORITY_OFFSET = 100;
private static final int PRIORITY_MULTIPLIER = 5;
protected static final ImmutableList<Constraint> CONSTRAINTS
= ImmutableList.of(new PartialFailureConstraint());
private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
private final ApplicationId appId;
private final InterfaceService interfaceService;
private final IntentSynchronizationService intentSynchronizer;
/**
* Class constructor.
*
* @param appId application ID to use when generating intents
* @param interfaceService interface service
* @param intentSynchronizer intent synchronizer
*/
public SdnIpFib(ApplicationId appId, InterfaceService interfaceService,
IntentSynchronizationService intentSynchronizer) {
routeIntents = new ConcurrentHashMap<>();
this.appId = appId;
this.interfaceService = interfaceService;
this.intentSynchronizer = intentSynchronizer;
}
@Override
public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
int submitCount = 0, withdrawCount = 0;
//
// NOTE: Semantically, we MUST withdraw existing intents before
// submitting new intents.
//
synchronized (this) {
MultiPointToSinglePointIntent intent;
//
// Prepare the Intent batch operations for the intents to withdraw
//
for (FibUpdate withdraw : withdraws) {
checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
"FibUpdate with wrong type in withdraws list");
IpPrefix prefix = withdraw.entry().prefix();
intent = routeIntents.remove(prefix);
if (intent == null) {
log.trace("SDN-IP No intent in routeIntents to delete " +
"for prefix: {}", prefix);
continue;
}
intentSynchronizer.withdraw(intent);
withdrawCount++;
}
//
// Prepare the Intent batch operations for the intents to submit
//
for (FibUpdate update : updates) {
checkArgument(update.type() == FibUpdate.Type.UPDATE,
"FibUpdate with wrong type in updates list");
IpPrefix prefix = update.entry().prefix();
intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
update.entry().nextHopMac());
if (intent == null) {
// This preserves the old semantics - if an intent can't be
// generated, we don't do anything with that prefix. But
// perhaps we should withdraw the old intent anyway?
continue;
}
routeIntents.put(prefix, intent);
intentSynchronizer.submit(intent);
submitCount++;
}
log.debug("SDN-IP submitted {}/{}, withdrew = {}/{}", submitCount,
updates.size(), withdrawCount, withdraws.size());
}
}
/**
* Generates a route intent for a prefix, the next hop IP address, and
* the next hop MAC address.
* <p/>
* This method will find the egress interface for the intent.
* Intent will match dst IP prefix and rewrite dst MAC address at all other
* border switches, then forward packets according to dst MAC address.
*
* @param prefix IP prefix of the route to add
* @param nextHopIpAddress IP address of the next hop
* @param nextHopMacAddress MAC address of the next hop
* @return the generated intent, or null if no intent should be submitted
*/
private MultiPointToSinglePointIntent generateRouteIntent(
IpPrefix prefix,
IpAddress nextHopIpAddress,
MacAddress nextHopMacAddress) {
// Find the attachment point (egress interface) of the next hop
Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress);
if (egressInterface == null) {
log.warn("No outgoing interface found for {}",
nextHopIpAddress);
return null;
}
// Generate the intent itself
Set<ConnectPoint> ingressPorts = new HashSet<>();
ConnectPoint egressPort = egressInterface.connectPoint();
log.debug("Generating intent for prefix {}, next hop mac {}",
prefix, nextHopMacAddress);
for (Interface intf : interfaceService.getInterfaces()) {
// TODO this should be only peering interfaces
if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
ConnectPoint srcPort = intf.connectPoint();
ingressPorts.add(srcPort);
}
}
// Match the destination IP prefix at the first hop
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
if (prefix.isIp4()) {
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPDst(prefix);
} else {
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchIPv6Dst(prefix);
}
// Rewrite the destination MAC address
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
.setEthDst(nextHopMacAddress);
if (!egressInterface.vlan().equals(VlanId.NONE)) {
treatment.setVlanId(egressInterface.vlan());
// If we set VLAN ID, we have to make sure a VLAN tag exists.
// TODO support no VLAN -> VLAN routing
selector.matchVlanId(VlanId.ANY);
}
int priority =
prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
Key key = Key.of(prefix.toString(), appId);
return MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment.build())
.ingressPoints(ingressPorts)
.egressPoint(egressPort)
.priority(priority)
.constraints(CONSTRAINTS)
.build();
}
}
......@@ -18,7 +18,7 @@ package org.onosproject.sdnip.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.sdnip.SdnIpService;
import org.onosproject.routing.SdnIpService;
/**
* Command to change whether this SDNIP instance is primary or not.
......
......@@ -16,6 +16,7 @@
package org.onosproject.sdnip;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Test;
import org.onlab.junit.TestUtils;
......@@ -27,10 +28,9 @@ import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
......@@ -43,20 +43,13 @@ import org.onosproject.net.intent.AbstractIntentTest;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.routing.FibEntry;
import org.onosproject.routing.FibUpdate;
import org.onosproject.routing.RouteEntry;
import org.onosproject.routing.config.BgpPeer;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
......@@ -64,11 +57,8 @@ import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
/**
* This class tests the intent synchronization function in the
......@@ -76,10 +66,7 @@ import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
*/
public class IntentSyncTest extends AbstractIntentTest {
private RoutingConfigurationService routingConfig;
private InterfaceService interfaceService;
private IntentService intentService;
private NetworkConfigService configService;
private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000001"),
......@@ -100,65 +87,18 @@ public class IntentSyncTest extends AbstractIntentTest {
private IntentSynchronizer intentSynchronizer;
private final Set<Interface> interfaces = Sets.newHashSet();
private static final ApplicationId APPID = new ApplicationId() {
@Override
public short id() {
return 1;
}
@Override
public String name() {
return "SDNIP";
}
};
private static final ApplicationId APPID = TestApplicationId.create("SDNIP");
@Before
public void setUp() throws Exception {
super.setUp();
routingConfig = createMock(RoutingConfigurationService.class);
interfaceService = createMock(InterfaceService.class);
configService = createMock(NetworkConfigService.class);
// These will set expectations on routingConfig
setUpInterfaceService();
setUpBgpPeers();
replay(routingConfig);
replay(interfaceService);
intentService = createMock(IntentService.class);
intentSynchronizer = new IntentSynchronizer(APPID, intentService,
null, routingConfig,
interfaceService);
}
/**
* Sets up BGP peers in external networks.
*/
private void setUpBgpPeers() {
Map<IpAddress, BgpPeer> peers = new HashMap<>();
String peerSw1Eth1 = "192.168.10.1";
peers.put(IpAddress.valueOf(peerSw1Eth1),
new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
// Two BGP peers are connected to switch 2 port 1.
String peer1Sw2Eth1 = "192.168.20.1";
peers.put(IpAddress.valueOf(peer1Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
String peer2Sw2Eth1 = "192.168.20.2";
peers.put(IpAddress.valueOf(peer2Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
String peer1Sw4Eth1 = "192.168.40.1";
peers.put(IpAddress.valueOf(peer1Sw4Eth1),
new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
MoreExecutors.newDirectExecutorService());
}
/**
......@@ -200,267 +140,13 @@ public class IntentSyncTest extends AbstractIntentTest {
MacAddress.valueOf("00:00:00:00:00:04"),
VlanId.vlanId((short) 1));
expect(interfaceService.getInterfacesByPort(SW4_ETH1)).andReturn(
Collections.singleton(sw4Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.40.1")))
.andReturn(sw4Eth1).anyTimes();
interfaces.add(sw4Eth1);
expect(interfaceService.getInterfacesByPort(SW1_ETH1)).andReturn(
Collections.singleton(sw1Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.10.1")))
.andReturn(sw1Eth1).anyTimes();
expect(interfaceService.getInterfacesByPort(SW2_ETH1)).andReturn(
Collections.singleton(sw2Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.20.1")))
.andReturn(sw2Eth1).anyTimes();
expect(interfaceService.getInterfacesByPort(SW3_ETH1)).andReturn(
Collections.singleton(sw3Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.30.1")))
.andReturn(sw3Eth1).anyTimes();
expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
}
/**
* Tests adding a FIB entry to the IntentSynchronizer.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*
* @throws TestUtilsException
*/
@Test
public void testFibAdd() throws TestUtilsException {
FibEntry fibEntry = new FibEntry(
Ip4Prefix.valueOf("1.1.1.0/24"),
Ip4Address.valueOf("192.168.10.1"),
MacAddress.valueOf("00:00:00:00:00:01"));
// Construct a MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
fibEntry.prefix());
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(SW2_ETH1);
ingressPoints.add(SW3_ETH1);
ingressPoints.add(SW4_ETH1);
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(SW1_ETH1)
.constraints(IntentSynchronizer.CONSTRAINTS)
.build();
// Setup the expected intents
intentService.submit(eqExceptId(intent));
replay(intentService);
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
fibEntry);
intentSynchronizer.update(Collections.singleton(fibUpdate),
Collections.emptyList());
assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
Intent firstIntent =
intentSynchronizer.getRouteIntents().iterator().next();
IntentKey firstIntentKey = new IntentKey(firstIntent);
IntentKey intentKey = new IntentKey(intent);
assertTrue(firstIntentKey.equals(intentKey));
verify(intentService);
}
/**
* Tests adding a FIB entry with to a next hop in a VLAN.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*
* @throws TestUtilsException
*/
@Test
public void testFibAddWithVlan() throws TestUtilsException {
FibEntry fibEntry = new FibEntry(
Ip4Prefix.valueOf("3.3.3.0/24"),
Ip4Address.valueOf("192.168.40.1"),
MacAddress.valueOf("00:00:00:00:00:04"));
// Construct a MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(fibEntry.prefix())
.matchVlanId(VlanId.ANY);
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:04"))
.setVlanId(VlanId.vlanId((short) 1));
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(SW1_ETH1);
ingressPoints.add(SW2_ETH1);
ingressPoints.add(SW3_ETH1);
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(SW4_ETH1)
.constraints(IntentSynchronizer.CONSTRAINTS)
.build();
// Setup the expected intents
intentService.submit(eqExceptId(intent));
replay(intentService);
// Run the test
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
intentSynchronizer.update(Collections.singleton(fibUpdate),
Collections.emptyList());
// Verify
assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
Intent firstIntent =
intentSynchronizer.getRouteIntents().iterator().next();
IntentKey firstIntentKey = new IntentKey(firstIntent);
IntentKey intentKey = new IntentKey(intent);
assertTrue(firstIntentKey.equals(intentKey));
verify(intentService);
}
/**
* Tests updating a FIB entry.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*
* @throws TestUtilsException
*/
@Test
public void testFibUpdate() throws TestUtilsException {
// Firstly add a route
testFibAdd();
Intent addedIntent =
intentSynchronizer.getRouteIntents().iterator().next();
// Start to construct a new route entry and new intent
FibEntry fibEntryUpdate = new FibEntry(
Ip4Prefix.valueOf("1.1.1.0/24"),
Ip4Address.valueOf("192.168.20.1"),
MacAddress.valueOf("00:00:00:00:00:02"));
// Construct a new MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilderNew =
DefaultTrafficSelector.builder();
selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
fibEntryUpdate.prefix());
TrafficTreatment.Builder treatmentBuilderNew =
DefaultTrafficTreatment.builder();
treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
Set<ConnectPoint> ingressPointsNew = new HashSet<>();
ingressPointsNew.add(SW1_ETH1);
ingressPointsNew.add(SW3_ETH1);
ingressPointsNew.add(SW4_ETH1);
MultiPointToSinglePointIntent intentNew =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.selector(selectorBuilderNew.build())
.treatment(treatmentBuilderNew.build())
.ingressPoints(ingressPointsNew)
.egressPoint(SW2_ETH1)
.constraints(IntentSynchronizer.CONSTRAINTS)
.build();
// Set up test expectation
reset(intentService);
// Setup the expected intents
intentService.withdraw(eqExceptId(addedIntent));
intentService.submit(eqExceptId(intentNew));
replay(intentService);
// Call the update() method in IntentSynchronizer class
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
fibEntryUpdate);
intentSynchronizer.update(Collections.singletonList(fibUpdate),
Collections.emptyList());
// Verify
assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
Intent firstIntent =
intentSynchronizer.getRouteIntents().iterator().next();
IntentKey firstIntentKey = new IntentKey(firstIntent);
IntentKey intentNewKey = new IntentKey(intentNew);
assertTrue(firstIntentKey.equals(intentNewKey));
verify(intentService);
}
/**
* Tests deleting a FIB entry.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is withdrawn from the IntentService.
*
* @throws TestUtilsException
*/
@Test
public void testFibDelete() throws TestUtilsException {
// Firstly add a route
testFibAdd();
Intent addedIntent =
intentSynchronizer.getRouteIntents().iterator().next();
// Construct the existing route entry
FibEntry fibEntry = new FibEntry(
Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
// Set up expectation
reset(intentService);
// Setup the expected intents
intentService.withdraw(eqExceptId(addedIntent));
replay(intentService);
// Call the update() method in IntentSynchronizer class
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
intentSynchronizer.update(Collections.emptyList(),
Collections.singletonList(fibUpdate));
// Verify
assertEquals(intentSynchronizer.getRouteIntents().size(), 0);
verify(intentService);
}
/**
* This method tests the behavior of intent Synchronizer.
* Tests the synchronization behavior of intent synchronizer. We set up
* a discrepancy between the intent service state and the intent
* synchronizer's state and ensure that this is reconciled correctly.
*
* @throws TestUtilsException
*/
......@@ -529,27 +215,13 @@ public class IntentSyncTest extends AbstractIntentTest {
// Compose a intent, which is equal to intent5 but the id is different.
MultiPointToSinglePointIntent intent5New =
staticIntentBuilder(intent5, routeEntry5, "00:00:00:00:00:01");
assertThat(IntentSynchronizer.IntentKey.equalIntents(
intent5, intent5New),
is(true));
assertThat(IntentUtils.equals(intent5, intent5New), is(true));
assertFalse(intent5.equals(intent5New));
MultiPointToSinglePointIntent intent6 = intentBuilder(
routeEntry6.prefix(), "00:00:00:00:00:01", SW1_ETH1);
// Set up the routeIntents field in IntentSynchronizer class
ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent>
routeIntents = new ConcurrentHashMap<>();
routeIntents.put(routeEntry1.prefix(), intent1);
routeIntents.put(routeEntry3.prefix(), intent3);
routeIntents.put(routeEntry4Update.prefix(), intent4Update);
routeIntents.put(routeEntry5.prefix(), intent5New);
routeIntents.put(routeEntry6.prefix(), intent6);
routeIntents.put(routeEntry7.prefix(), intent7);
TestUtils.setField(intentSynchronizer, "routeIntents", routeIntents);
// Set up expectation
reset(intentService);
Set<Intent> intents = new HashSet<>();
intents.add(intent1);
expect(intentService.getIntentState(intent1.key()))
......@@ -568,9 +240,9 @@ public class IntentSyncTest extends AbstractIntentTest {
.andReturn(IntentState.WITHDRAWING).anyTimes();
expect(intentService.getIntents()).andReturn(intents).anyTimes();
// These are the operations that should be done to the intentService
// during synchronization
intentService.withdraw(intent2);
intentService.withdraw(intent4);
intentService.submit(intent3);
intentService.submit(intent4Update);
intentService.submit(intent6);
......@@ -578,16 +250,101 @@ public class IntentSyncTest extends AbstractIntentTest {
replay(intentService);
// Start the test
// Simulate some input from the clients. The intent synchronizer has not
// gained the global leadership yet, but it will remember this input for
// when it does.
intentSynchronizer.submit(intent1);
intentSynchronizer.submit(intent2);
intentSynchronizer.withdraw(intent2);
intentSynchronizer.submit(intent3);
intentSynchronizer.submit(intent4);
intentSynchronizer.submit(intent4Update);
intentSynchronizer.submit(intent5);
intentSynchronizer.submit(intent6);
intentSynchronizer.submit(intent7);
// Give the leadership to the intent synchronizer. It will now attempt
// to synchronize the intents in the store with the intents it has
// recorded based on the earlier user input.
intentSynchronizer.leaderChanged(true);
verify(intentService);
}
/**
* Tests the behavior of the submit API, both when the synchronizer has
* leadership and when it does not.
*/
@Test
public void testSubmit() {
IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1);
// Set up expectations
intentService.submit(intent);
expect(intentService.getIntents()).andReturn(Collections.emptyList())
.anyTimes();
replay(intentService);
// Give the intent synchronizer leadership so it will submit intents
// to the intent service
intentSynchronizer.leaderChanged(true);
// Test the submit
intentSynchronizer.submit(intent);
verify(intentService);
// Now we'll remove leadership from the intent synchronizer and verify
// that it does not submit any intents to the intent service when we
// call the submit API
reset(intentService);
replay(intentService);
intentSynchronizer.leaderChanged(false);
intentSynchronizer.submit(intent);
verify(intentService);
}
/**
* Tests the behavior of the withdraw API, both when the synchronizer has
* leadership and when it does not.
*/
@Test
public void testWithdraw() {
IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1);
// Submit an intent first so we can withdraw it later
intentService.submit(intent);
intentService.withdraw(intent);
expect(intentService.getIntents()).andReturn(Collections.emptyList())
.anyTimes();
replay(intentService);
// Give the intent synchronizer leadership so it will submit intents
// to the intent service
intentSynchronizer.leaderChanged(true);
intentSynchronizer.synchronizeIntents();
// Verify
assertEquals(intentSynchronizer.getRouteIntents().size(), 6);
assertTrue(intentSynchronizer.getRouteIntents().contains(intent1));
assertTrue(intentSynchronizer.getRouteIntents().contains(intent3));
assertTrue(intentSynchronizer.getRouteIntents().contains(intent4Update));
assertTrue(intentSynchronizer.getRouteIntents().contains(intent5));
assertTrue(intentSynchronizer.getRouteIntents().contains(intent6));
// Test the submit then withdraw
intentSynchronizer.submit(intent);
intentSynchronizer.withdraw(intent);
verify(intentService);
// Now we'll remove leadership from the intent synchronizer and verify
// that it does not withdraw any intents to the intent service when we
// call the withdraw API
reset(intentService);
replay(intentService);
intentSynchronizer.leaderChanged(false);
intentSynchronizer.submit(intent);
intentSynchronizer.withdraw(intent);
verify(intentService);
}
......@@ -607,10 +364,10 @@ public class IntentSyncTest extends AbstractIntentTest {
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
if (ipPrefix.isIp4()) {
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4); // IPv4
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4);
selectorBuilder.matchIPDst(ipPrefix);
} else {
selectorBuilder.matchEthType(Ethernet.TYPE_IPV6); // IPv6
selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);
selectorBuilder.matchIPv6Dst(ipPrefix);
}
......@@ -628,11 +385,12 @@ public class IntentSyncTest extends AbstractIntentTest {
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.key(Key.of(ipPrefix.toString(), APPID))
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(egressPoint)
.constraints(IntentSynchronizer.CONSTRAINTS)
.constraints(SdnIpFib.CONSTRAINTS)
.build();
return intent;
}
......@@ -646,7 +404,7 @@ public class IntentSyncTest extends AbstractIntentTest {
* @return the newly constructed MultiPointToSinglePointIntent
* @throws TestUtilsException
*/
private MultiPointToSinglePointIntent staticIntentBuilder(
private MultiPointToSinglePointIntent staticIntentBuilder(
MultiPointToSinglePointIntent intent, RouteEntry routeEntry,
String nextHopMacAddress) throws TestUtilsException {
......
......@@ -19,7 +19,6 @@ import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.junit.TestUtils;
import org.onlab.junit.TestUtils.TestUtilsException;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
......@@ -28,13 +27,14 @@ import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
......@@ -42,8 +42,9 @@ import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.intent.AbstractIntentTest;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.config.BgpConfig;
import org.onosproject.routing.config.BgpPeer;
import org.onosproject.routing.config.BgpSpeaker;
......@@ -71,26 +72,15 @@ import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
*/
public class PeerConnectivityManagerTest extends AbstractIntentTest {
private static final ApplicationId APPID = new ApplicationId() {
@Override
public short id() {
return 0;
}
@Override
public String name() {
return "foo";
}
};
private static final ApplicationId APPID = TestApplicationId.create("foo");
private static final ApplicationId CONFIG_APP_ID = APPID;
private PeerConnectivityManager peerConnectivityManager;
private IntentSynchronizer intentSynchronizer;
private IntentSynchronizationService intentSynchronizer;
private RoutingConfigurationService routingConfig;
private InterfaceService interfaceService;
private NetworkConfigService networkConfigService;
private IntentService intentService;
private Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers;
private Map<String, Interface> interfaces;
......@@ -98,8 +88,6 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
private BgpConfig bgpConfig;
private Map<String, Interface> configuredInterfaces;
private Map<IpAddress, BgpPeer> configuredPeers;
private List<PointToPointIntent> intentList;
private final String dpid1 = "00:00:00:00:00:00:00:01";
......@@ -136,7 +124,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
// These will set expectations on routingConfig and interfaceService
bgpSpeakers = setUpBgpSpeakers();
interfaces = Collections.unmodifiableMap(setUpInterfaces());
peers = Collections.unmodifiableMap(setUpPeers());
peers = setUpPeers();
initPeerConnectivity();
intentList = setUpIntentList();
......@@ -169,11 +157,11 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
* Sets up logical interfaces, which emulate the configured interfaces
* in SDN-IP application.
*
* @return configured interfaces as a MAP from Interface name to Interface
* @return configured interfaces as a map from interface name to Interface
*/
private Map<String, Interface> setUpInterfaces() {
configuredInterfaces = new HashMap<>();
Map<String, Interface> configuredInterfaces = new HashMap<>();
String interfaceSw1Eth1 = "s1-eth1";
InterfaceIpAddress ia1 =
......@@ -242,7 +230,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
*/
private Map<IpAddress, BgpPeer> setUpPeers() {
configuredPeers = new HashMap<>();
Map<IpAddress, BgpPeer> configuredPeers = new HashMap<>();
String peerSw1Eth1 = "192.168.10.1";
configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
......@@ -266,14 +254,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
* @return point to point intent list
*/
private List<PointToPointIntent> setUpIntentList() {
intentList = new ArrayList<>();
setUpBgpIntents();
setUpIcmpIntents();
return intentList;
}
/**
......@@ -306,8 +292,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
}
Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0]
+ "-" + ((srcTcpPort == null) ? "dst" : "src"), APPID);
PointToPointIntent intent = PointToPointIntent.builder()
.appId(APPID)
.key(key)
.selector(builder.build())
.treatment(noTreatment)
.ingressPoint(srcConnectPoint)
......@@ -392,8 +382,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
.matchIPDst(IpPrefix.valueOf(dstPrefix))
.build();
Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0]
+ "-" + "icmp", APPID);
PointToPointIntent intent = PointToPointIntent.builder()
.appId(APPID)
.key(key)
.selector(selector)
.treatment(noTreatment)
.ingressPoint(srcConnectPoint)
......@@ -434,19 +428,14 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
expect(bgpConfig.bgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
replay(bgpConfig);
expect(networkConfigService.getConfig(APPID, BgpConfig.class)).andReturn(bgpConfig).anyTimes();
expect(networkConfigService.getConfig(APPID, BgpConfig.class))
.andReturn(bgpConfig).anyTimes();
replay(networkConfigService);
replay(routingConfig);
replay(interfaceService);
intentService = createMock(IntentService.class);
replay(intentService);
intentSynchronizer = new IntentSynchronizer(APPID, intentService,
null, routingConfig,
interfaceService);
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
intentSynchronizer = createMock(IntentSynchronizationService.class);
replay(intentSynchronizer);
peerConnectivityManager =
new PeerConnectivityManager(APPID, intentSynchronizer,
......@@ -464,20 +453,18 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
*/
@Test
public void testConnectionSetup() {
reset(intentService);
reset(intentSynchronizer);
// Setup the expected intents
for (Intent intent : intentList) {
intentService.submit(eqExceptId(intent));
intentSynchronizer.submit(eqExceptId(intent));
}
replay(intentService);
replay(intentSynchronizer);
// Running the interface to be tested.
peerConnectivityManager.start();
verify(intentService);
verify(intentSynchronizer);
}
/**
......@@ -488,7 +475,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
reset(interfaceService);
expect(interfaceService.getInterfaces()).andReturn(
Sets.<Interface>newHashSet()).anyTimes();
Sets.newHashSet()).anyTimes();
expect(interfaceService.getInterfacesByPort(s2Eth1))
.andReturn(Collections.emptySet()).anyTimes();
expect(interfaceService.getInterfacesByPort(s1Eth1))
......@@ -508,10 +495,10 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
replay(interfaceService);
reset(intentService);
replay(intentService);
reset(intentSynchronizer);
replay(intentSynchronizer);
peerConnectivityManager.start();
verify(intentService);
verify(intentSynchronizer);
}
/**
......@@ -527,10 +514,10 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
replay(routingConfig);
reset(intentService);
replay(intentService);
reset(intentSynchronizer);
replay(intentSynchronizer);
peerConnectivityManager.start();
verify(intentService);
verify(intentSynchronizer);
}
/**
......@@ -540,7 +527,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest {
@Test
public void testNoPeerInterface() {
String peerSw100Eth1 = "192.168.200.1";
configuredPeers.put(IpAddress.valueOf(peerSw100Eth1),
peers.put(IpAddress.valueOf(peerSw100Eth1),
new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1));
testConnectionSetup();
}
......
/*
* Copyright 2015 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.sdnip;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
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.host.InterfaceIpAddress;
import org.onosproject.net.intent.AbstractIntentTest;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.routing.FibEntry;
import org.onosproject.routing.FibUpdate;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.config.BgpPeer;
import org.onosproject.routing.config.RoutingConfigurationService;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
/**
* Unit tests for SdnIpFib.
*/
public class SdnIpFibTest extends AbstractIntentTest {
private RoutingConfigurationService routingConfig;
private InterfaceService interfaceService;
private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000001"),
PortNumber.portNumber(1));
private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000002"),
PortNumber.portNumber(1));
private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000003"),
PortNumber.portNumber(1));
private static final ConnectPoint SW4_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000004"),
PortNumber.portNumber(1));
private SdnIpFib sdnipFib;
private IntentSynchronizationService intentSynchronizer;
private final Set<Interface> interfaces = Sets.newHashSet();
private static final ApplicationId APPID = TestApplicationId.create("SDNIP");
@Before
public void setUp() throws Exception {
super.setUp();
routingConfig = createMock(RoutingConfigurationService.class);
interfaceService = createMock(InterfaceService.class);
// These will set expectations on routingConfig and interfaceService
setUpInterfaceService();
setUpBgpPeers();
replay(routingConfig);
replay(interfaceService);
intentSynchronizer = createMock(IntentSynchronizationService.class);
sdnipFib = new SdnIpFib(APPID, interfaceService, intentSynchronizer);
}
/**
* Sets up BGP peers in external networks.
*/
private void setUpBgpPeers() {
Map<IpAddress, BgpPeer> peers = new HashMap<>();
String peerSw1Eth1 = "192.168.10.1";
peers.put(IpAddress.valueOf(peerSw1Eth1),
new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
// Two BGP peers are connected to switch 2 port 1.
String peer1Sw2Eth1 = "192.168.20.1";
peers.put(IpAddress.valueOf(peer1Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
String peer2Sw2Eth1 = "192.168.20.2";
peers.put(IpAddress.valueOf(peer2Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
String peer1Sw4Eth1 = "192.168.40.1";
peers.put(IpAddress.valueOf(peer1Sw4Eth1),
new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
}
/**
* Sets up InterfaceService.
*/
private void setUpInterfaceService() {
Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
interfaceIpAddresses1.add(new InterfaceIpAddress(
IpAddress.valueOf("192.168.10.101"),
IpPrefix.valueOf("192.168.10.0/24")));
Interface sw1Eth1 = new Interface(SW1_ETH1,
interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
VlanId.NONE);
interfaces.add(sw1Eth1);
Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
interfaceIpAddresses2.add(
new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
IpPrefix.valueOf("192.168.20.0/24")));
Interface sw2Eth1 = new Interface(SW2_ETH1,
interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
VlanId.NONE);
interfaces.add(sw2Eth1);
Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
interfaceIpAddresses3.add(
new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
IpPrefix.valueOf("192.168.30.0/24")));
Interface sw3Eth1 = new Interface(SW3_ETH1,
interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
VlanId.NONE);
interfaces.add(sw3Eth1);
InterfaceIpAddress interfaceIpAddress4 =
new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
IpPrefix.valueOf("192.168.40.0/24"));
Interface sw4Eth1 = new Interface(SW4_ETH1,
Sets.newHashSet(interfaceIpAddress4),
MacAddress.valueOf("00:00:00:00:00:04"),
VlanId.vlanId((short) 1));
expect(interfaceService.getInterfacesByPort(SW4_ETH1)).andReturn(
Collections.singleton(sw4Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.40.1")))
.andReturn(sw4Eth1).anyTimes();
interfaces.add(sw4Eth1);
expect(interfaceService.getInterfacesByPort(SW1_ETH1)).andReturn(
Collections.singleton(sw1Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.10.1")))
.andReturn(sw1Eth1).anyTimes();
expect(interfaceService.getInterfacesByPort(SW2_ETH1)).andReturn(
Collections.singleton(sw2Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.20.1")))
.andReturn(sw2Eth1).anyTimes();
expect(interfaceService.getInterfacesByPort(SW3_ETH1)).andReturn(
Collections.singleton(sw3Eth1)).anyTimes();
expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.30.1")))
.andReturn(sw3Eth1).anyTimes();
expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
}
/**
* Tests adding a FIB entry to the IntentSynchronizer.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*/
@Test
public void testFibAdd() {
IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
FibEntry fibEntry = new FibEntry(prefix,
Ip4Address.valueOf("192.168.10.1"),
MacAddress.valueOf("00:00:00:00:00:01"));
// Construct a MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
fibEntry.prefix());
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(SW2_ETH1);
ingressPoints.add(SW3_ETH1);
ingressPoints.add(SW4_ETH1);
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.key(Key.of(prefix.toString(), APPID))
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(SW1_ETH1)
.constraints(SdnIpFib.CONSTRAINTS)
.build();
// Setup the expected intents
intentSynchronizer.submit(eqExceptId(intent));
replay(intentSynchronizer);
// Send in the UPDATE FibUpdate
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList());
verify(intentSynchronizer);
}
/**
* Tests adding a FIB entry with to a next hop in a VLAN.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*/
@Test
public void testFibAddWithVlan() {
IpPrefix prefix = Ip4Prefix.valueOf("3.3.3.0/24");
FibEntry fibEntry = new FibEntry(prefix,
Ip4Address.valueOf("192.168.40.1"),
MacAddress.valueOf("00:00:00:00:00:04"));
// Construct a MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(fibEntry.prefix())
.matchVlanId(VlanId.ANY);
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:04"))
.setVlanId(VlanId.vlanId((short) 1));
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(SW1_ETH1);
ingressPoints.add(SW2_ETH1);
ingressPoints.add(SW3_ETH1);
MultiPointToSinglePointIntent intent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.key(Key.of(prefix.toString(), APPID))
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(SW4_ETH1)
.constraints(SdnIpFib.CONSTRAINTS)
.build();
// Setup the expected intents
intentSynchronizer.submit(eqExceptId(intent));
replay(intentSynchronizer);
// Send in the UPDATE FibUpdate
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList());
verify(intentSynchronizer);
}
/**
* Tests updating a FIB entry.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is submitted to the IntentService.
*/
@Test
public void testFibUpdate() {
// Firstly add a route
testFibAdd();
IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
// Start to construct a new route entry and new intent
FibEntry fibEntryUpdate = new FibEntry(prefix,
Ip4Address.valueOf("192.168.20.1"),
MacAddress.valueOf("00:00:00:00:00:02"));
// Construct a new MultiPointToSinglePointIntent intent
TrafficSelector.Builder selectorBuilderNew =
DefaultTrafficSelector.builder();
selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
fibEntryUpdate.prefix());
TrafficTreatment.Builder treatmentBuilderNew =
DefaultTrafficTreatment.builder();
treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
Set<ConnectPoint> ingressPointsNew = new HashSet<>();
ingressPointsNew.add(SW1_ETH1);
ingressPointsNew.add(SW3_ETH1);
ingressPointsNew.add(SW4_ETH1);
MultiPointToSinglePointIntent intentNew =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.key(Key.of(prefix.toString(), APPID))
.selector(selectorBuilderNew.build())
.treatment(treatmentBuilderNew.build())
.ingressPoints(ingressPointsNew)
.egressPoint(SW2_ETH1)
.constraints(SdnIpFib.CONSTRAINTS)
.build();
// Set up test expectation
reset(intentSynchronizer);
// Setup the expected intents
intentSynchronizer.submit(eqExceptId(intentNew));
replay(intentSynchronizer);
// Send in the UPDATE FibUpdate
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
fibEntryUpdate);
sdnipFib.update(Collections.singletonList(fibUpdate),
Collections.emptyList());
verify(intentSynchronizer);
}
/**
* Tests deleting a FIB entry.
*
* We verify that the synchronizer records the correct state and that the
* correct intent is withdrawn from the IntentService.
*/
@Test
public void testFibDelete() {
// Firstly add a route
testFibAdd();
IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
// Construct the existing route entry
FibEntry fibEntry = new FibEntry(prefix, null, null);
// Construct the existing MultiPointToSinglePoint intent
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
fibEntry.prefix());
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
Set<ConnectPoint> ingressPoints = new HashSet<>();
ingressPoints.add(SW2_ETH1);
ingressPoints.add(SW3_ETH1);
ingressPoints.add(SW4_ETH1);
MultiPointToSinglePointIntent addedIntent =
MultiPointToSinglePointIntent.builder()
.appId(APPID)
.key(Key.of(prefix.toString(), APPID))
.selector(selectorBuilder.build())
.treatment(treatmentBuilder.build())
.ingressPoints(ingressPoints)
.egressPoint(SW1_ETH1)
.constraints(SdnIpFib.CONSTRAINTS)
.build();
// Set up expectation
reset(intentSynchronizer);
// Setup the expected intents
intentSynchronizer.withdraw(eqExceptId(addedIntent));
replay(intentSynchronizer);
// Send in the DELETE FibUpdate
FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
sdnipFib.update(Collections.emptyList(), Collections.singletonList(fibUpdate));
verify(intentSynchronizer);
}
}
......@@ -17,7 +17,6 @@ package org.onosproject.sdnip;
import org.easymock.IArgumentMatcher;
import org.onosproject.net.intent.Intent;
import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
import static org.easymock.EasyMock.reportMatcher;
......@@ -53,8 +52,6 @@ public final class TestIntentServiceHelper {
* the solution is to use an EasyMock matcher that verifies that all the
* value properties of the provided intent match the expected values, but
* ignores the intent ID when testing equality.
*
* FIXME this currently does not take key into account
*/
private static final class IdAgnosticIntentMatcher implements
IArgumentMatcher {
......@@ -86,9 +83,7 @@ public final class TestIntentServiceHelper {
Intent providedIntent = (Intent) object;
providedString = providedIntent.toString();
IntentKey thisIntentKey = new IntentKey(intent);
IntentKey providedIntentKey = new IntentKey(providedIntent);
return thisIntentKey.equals(providedIntentKey);
return IntentUtils.equals(intent, providedIntent);
}
}
......
......@@ -521,7 +521,7 @@ public class EventuallyConsistentMapImpl<K, V>
return;
}
peers.forEach(node ->
senderPending.computeIfAbsent(node, unusedKey -> new EventAccumulator(node)).add(event)
senderPending.computeIfAbsent(node, unusedKey -> new EventAccumulator(node)).add(event)
);
}
......@@ -574,8 +574,10 @@ public class EventuallyConsistentMapImpl<K, V>
return;
}
try {
log.debug("Received anti-entropy advertisement from {} for {} with {} entries in it",
mapName, ad.sender(), ad.digest().size());
if (log.isTraceEnabled()) {
log.trace("Received anti-entropy advertisement from {} for {} with {} entries in it",
mapName, ad.sender(), ad.digest().size());
}
antiEntropyCheckLocalItems(ad).forEach(this::notifyListeners);
if (!lightweightAntiEntropy) {
......