blob: 0078650b440421eaa3feadaca9f85087778cd93b [file] [log] [blame]
package org.onlab.onos.sdnip;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.onlab.junit.IntegrationTest;
import org.onlab.junit.TestUtils;
import org.onlab.junit.TestUtils.TestUtilsException;
import org.onlab.onos.core.ApplicationId;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.host.InterfaceIpAddress;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
import org.onlab.onos.net.intent.AbstractIntentTest;
import org.onlab.onos.sdnip.config.BgpPeer;
import org.onlab.onos.sdnip.config.Interface;
import org.onlab.onos.sdnip.config.SdnIpConfigService;
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 com.google.common.collect.Sets;
/**
* Integration tests for the SDN-IP application.
* <p/>
* The tests are very coarse-grained. They feed route updates in to
* {@link Router} (simulating routes learnt from iBGP module inside SDN-IP
* application), then they check that the correct intents are created and
* submitted to the intent service. The entire route processing logic of
* Router class is tested.
*/
@Category(IntegrationTest.class)
public class SdnIpTest extends AbstractIntentTest {
private static final int MAC_ADDRESS_LENGTH = 6;
private static final int MIN_PREFIX_LENGTH = 1;
private static final int MAX_PREFIX_LENGTH = 32;
private IntentSynchronizer intentSynchronizer;
static Router router;
private SdnIpConfigService sdnIpConfigService;
private InterfaceService interfaceService;
private HostService hostService;
private IntentService intentService;
private Map<IpAddress, BgpPeer> bgpPeers;
private Random random;
static final ConnectPoint SW1_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000001"),
PortNumber.portNumber(1));
static final ConnectPoint SW2_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000002"),
PortNumber.portNumber(1));
static final ConnectPoint SW3_ETH1 = new ConnectPoint(
DeviceId.deviceId("of:0000000000000003"),
PortNumber.portNumber(1));
private static final ApplicationId APPID = new ApplicationId() {
@Override
public short id() {
return 1;
}
@Override
public String name() {
return "SDNIP";
}
};
@Before
public void setUp() throws Exception {
super.setUp();
setUpInterfaceService();
setUpSdnIpConfigService();
hostService = new TestHostService();
intentService = createMock(IntentService.class);
random = new Random();
intentSynchronizer = new IntentSynchronizer(APPID, intentService);
router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
interfaceService, hostService);
}
/**
* Sets up InterfaceService and virtual {@link Interface}s.
*/
private void setUpInterfaceService() {
interfaceService = createMock(InterfaceService.class);
Set<Interface> interfaces = Sets.newHashSet();
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"));
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"));
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"));
interfaces.add(sw3Eth1);
expect(interfaceService.getInterface(SW1_ETH1)).andReturn(
sw1Eth1).anyTimes();
expect(interfaceService.getInterface(SW2_ETH1)).andReturn(
sw2Eth1).anyTimes();
expect(interfaceService.getInterface(SW3_ETH1)).andReturn(
sw3Eth1).anyTimes();
expect(interfaceService.getInterfaces()).andReturn(
interfaces).anyTimes();
replay(interfaceService);
}
/**
* Sets up SdnIpConfigService and BGP peers in external networks.
*/
private void setUpSdnIpConfigService() {
sdnIpConfigService = createMock(SdnIpConfigService.class);
bgpPeers = new HashMap<>();
String peerSw1Eth1 = "192.168.10.1";
bgpPeers.put(IpAddress.valueOf(peerSw1Eth1),
new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
String peer1Sw2Eth1 = "192.168.20.1";
bgpPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
String peer2Sw2Eth1 = "192.168.30.1";
bgpPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
new BgpPeer("00:00:00:00:00:00:00:03", 1, peer2Sw2Eth1));
expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
replay(sdnIpConfigService);
}
/**
* Tests adding a set of routes into {@link Router}.
* <p/>
* Random routes are generated and fed in to the route processing
* logic (via processRouteAdd in Router class). We check that the correct
* intents are generated and submitted to our mock intent service.
*
* @throws InterruptedException if interrupted while waiting on a latch
* @throws TestUtilsException if exceptions when using TestUtils
*/
@Test @Ignore("needs fix from intents")
public void testAddRoutes() throws InterruptedException, TestUtilsException {
int numRoutes = 100;
final CountDownLatch latch = new CountDownLatch(numRoutes);
List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
// Set up expectation
reset(intentService);
for (RouteUpdate update : routeUpdates) {
Ip4Address nextHopAddress = update.routeEntry().nextHop();
// Find out the egress ConnectPoint
ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
generateMacAddress(nextHopAddress),
egressConnectPoint);
intentService.submit(intent);
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
latch.countDown();
return null;
}
}).once();
}
replay(intentService);
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
// Add route updates
for (RouteUpdate update : routeUpdates) {
router.processRouteAdd(update.routeEntry());
}
latch.await(5000, TimeUnit.MILLISECONDS);
assertEquals(router.getRoutes().size(), numRoutes);
assertEquals(intentSynchronizer.getRouteIntents().size(),
numRoutes);
verify(intentService);
}
/**
* Tests adding then deleting a set of routes from {@link Router}.
* <p/>
* Random routes are generated and fed in to the route processing
* logic (via processRouteAdd in Router class), and we check that the
* correct intents are generated. We then delete the entire set of routes
* (by feeding updates to processRouteDelete), and check that the correct
* intents are withdrawn from the intent service.
*
* @throws InterruptedException if interrupted while waiting on a latch
* @throws TestUtilsException exceptions when using TestUtils
*/
@Test @Ignore("needs fix from intents")
public void testDeleteRoutes() throws InterruptedException, TestUtilsException {
int numRoutes = 100;
List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
final CountDownLatch installCount = new CountDownLatch(numRoutes);
final CountDownLatch deleteCount = new CountDownLatch(numRoutes);
// Set up expectation
reset(intentService);
for (RouteUpdate update : routeUpdates) {
Ip4Address nextHopAddress = update.routeEntry().nextHop();
// Find out the egress ConnectPoint
ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
generateMacAddress(nextHopAddress),
egressConnectPoint);
intentService.submit(intent);
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
installCount.countDown();
return null;
}
}).once();
intentService.withdraw(intent);
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
deleteCount.countDown();
return null;
}
}).once();
}
replay(intentService);
intentSynchronizer.leaderChanged(true);
TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
// Send the add updates first
for (RouteUpdate update : routeUpdates) {
router.processRouteAdd(update.routeEntry());
}
// Give some time to let the intents be submitted
installCount.await(5000, TimeUnit.MILLISECONDS);
// Send the DELETE updates
for (RouteUpdate update : routeUpdates) {
router.processRouteDelete(update.routeEntry());
}
deleteCount.await(5000, TimeUnit.MILLISECONDS);
assertEquals(0, router.getRoutes().size());
assertEquals(0, intentSynchronizer.getRouteIntents().size());
verify(intentService);
}
/**
* This methods generates random route updates.
*
* @param numRoutes the number of route updates to generate
* @return a list of route update
*/
private List<RouteUpdate> generateRouteUpdates(int numRoutes) {
List<RouteUpdate> routeUpdates = new ArrayList<>(numRoutes);
Set<Ip4Prefix> prefixes = new HashSet<>();
for (int i = 0; i < numRoutes; i++) {
Ip4Prefix prefix;
do {
// Generate a random prefix length between MIN_PREFIX_LENGTH
// and MAX_PREFIX_LENGTH
int prefixLength = random.nextInt(
(MAX_PREFIX_LENGTH - MIN_PREFIX_LENGTH) + 1)
+ MIN_PREFIX_LENGTH;
prefix =
Ip4Prefix.valueOf(Ip4Address.valueOf(random.nextInt()),
prefixLength);
// We have to ensure we don't generate the same prefix twice
// (this is quite easy to happen with small prefix lengths).
} while (prefixes.contains(prefix));
prefixes.add(prefix);
// Randomly select a peer to use as the next hop
BgpPeer nextHop = null;
int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers()
.size());
int j = 0;
for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) {
if (j++ == peerNumber) {
nextHop = peer;
break;
}
}
assertNotNull(nextHop);
RouteUpdate update =
new RouteUpdate(RouteUpdate.Type.UPDATE,
new RouteEntry(prefix,
nextHop.ipAddress().getIp4Address()));
routeUpdates.add(update);
}
return routeUpdates;
}
/**
* Generates the MultiPointToSinglePointIntent that should be
* submitted/withdrawn for a particular RouteUpdate.
*
* @param update the RouteUpdate to generate an intent for
* @param nextHopMac a MAC address to use as the dst-mac for the intent
* @param egressConnectPoint the outgoing ConnectPoint for the intent
* @return the generated intent
*/
private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update,
MacAddress nextHopMac, ConnectPoint egressConnectPoint) {
IpPrefix ip4Prefix = update.routeEntry().prefix();
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix);
TrafficTreatment.Builder treatmentBuilder =
DefaultTrafficTreatment.builder();
treatmentBuilder.setEthDst(nextHopMac);
Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
for (Interface intf : interfaceService.getInterfaces()) {
if (!intf.connectPoint().equals(egressConnectPoint)) {
ConnectPoint srcPort = intf.connectPoint();
ingressPoints.add(srcPort);
}
}
MultiPointToSinglePointIntent intent =
new MultiPointToSinglePointIntent(APPID,
selectorBuilder.build(), treatmentBuilder.build(),
ingressPoints, egressConnectPoint);
return intent;
}
/**
* Generates a MAC address based on an IP address.
* For the test we need MAC addresses but the actual values don't have any
* meaning, so we'll just generate them based on the IP address. This means
* we have a deterministic mapping from IP address to MAC address.
*
* @param ipAddress IP address used to generate a MAC address
* @return generated MAC address
*/
static MacAddress generateMacAddress(IpAddress ipAddress) {
byte[] macAddress = new byte[MAC_ADDRESS_LENGTH];
ByteBuffer bb = ByteBuffer.wrap(macAddress);
// Put the IP address bytes into the lower four bytes of the MAC
// address. Leave the first two bytes set to 0.
bb.position(2);
bb.put(ipAddress.toOctets());
return MacAddress.valueOf(bb.array());
}
/**
* Finds out the ConnectPoint for a BGP peer address.
*
* @param bgpPeerAddress the BGP peer address.
*/
private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) {
ConnectPoint connectPoint = null;
for (BgpPeer bgpPeer: bgpPeers.values()) {
if (bgpPeer.ipAddress().equals(bgpPeerAddress)) {
connectPoint = bgpPeer.connectPoint();
break;
}
}
return connectPoint;
}
}