[ONOS-5607] Revise LISP ctrl impl class to track msgs and routers

Change-Id: I4a51a8ef9162e3feee543f40fa92a0435186d1c9
This commit is contained in:
Jian Li 2016-12-13 19:43:02 +09:00
parent 7ccc3a862f
commit 834ff723ce
10 changed files with 633 additions and 18 deletions

View File

@ -28,6 +28,13 @@ public interface LispController {
*/
Iterable<LispRouter> getRouters();
/**
* Obtains all subscribed LISP routers known to this LISP controllers.
*
* @return Iterable of LISP router elements
*/
Iterable<LispRouter> getSubscribedRouters();
/**
* Obtains the actual router for the given LispRouterId.
*

View File

@ -80,4 +80,47 @@ public interface LispRouter {
* @return whether the router is connected
*/
boolean isConnected();
/**
* Sets whether the router is connected.
*
* @param connected whether the router is connected
*/
void setConnected(boolean connected);
/**
* Checks if the router is subscribed.
* As long as a router sends Map-Request message,
* we treat the router is subscribed.
*
* @return whether the router is subscribed
*/
boolean isSubscribed();
/**
* Sets whether the router is subscribed.
*
* @param subscribed whether the router is subscribed
*/
void setSubscribed(boolean subscribed);
/**
* Sets the LISP agent to be used. This method can only be invoked once.
*
* @param agent the agent to set
*/
void setAgent(LispRouterAgent agent);
/**
* Announces to the LISP agent that this router has connected.
*
* @return true if successful, false if duplicate router
*/
boolean connectRouter();
/**
* Disconnects the router by closing UDP connection.
* Results in a call to the channel handler's close method for cleanup.
*/
void disconnectRouter();
}

View File

@ -24,6 +24,11 @@ public class LispControllerAdapter implements LispController {
return null;
}
@Override
public Iterable<LispRouter> getSubscribedRouters() {
return null;
}
@Override
public LispRouter getRouter(LispRouterId routerId) {
return null;

View File

@ -63,4 +63,34 @@ public class LispRouterAdapter implements LispRouter {
public boolean isConnected() {
return false;
}
@Override
public void setConnected(boolean connected) {
}
@Override
public boolean isSubscribed() {
return false;
}
@Override
public void setSubscribed(boolean subscribed) {
}
@Override
public void setAgent(LispRouterAgent agent) {
}
@Override
public boolean connectRouter() {
return false;
}
@Override
public void disconnectRouter() {
}
}

View File

@ -13,6 +13,7 @@ TEST_DEPS = [
'//lib:TEST_ADAPTERS',
'//utils/osgi:onlab-osgi-tests',
'//core/api:onos-api-tests',
'//protocols/lisp/api:onos-protocols-lisp-api-tests',
]
osgi_jar_with_tests (

View File

@ -61,6 +61,14 @@
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-lisp-api</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -48,6 +48,7 @@ public class LispControllerBootstrap {
private EventLoopGroup eventLoopGroup;
private Class<? extends AbstractChannel> channelClass;
private List<ChannelFuture> channelFutures = Lists.newArrayList();
/**
* Stitches all channel handlers into server bootstrap.
@ -59,8 +60,6 @@ public class LispControllerBootstrap {
configBootstrapOptions(bootstrap);
List<ChannelFuture> channelFutures = Lists.newArrayList();
lispPorts.forEach(p -> {
InetSocketAddress sa = new InetSocketAddress(p);
channelFutures.add(bootstrap.bind(sa));
@ -152,6 +151,7 @@ public class LispControllerBootstrap {
try {
// try to shutdown all open event groups
eventLoopGroup.shutdownGracefully().sync();
closeChannels(channelFutures);
} catch (InterruptedException e) {
log.warn("Failed to stop LISP controller. Reasons: {}.", e.getMessage());
}

View File

@ -15,6 +15,7 @@
*/
package org.onosproject.lisp.ctl.impl;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@ -29,18 +30,29 @@ import org.onosproject.core.CoreService;
import org.onosproject.lisp.ctl.LispController;
import org.onosproject.lisp.ctl.LispMessageListener;
import org.onosproject.lisp.ctl.LispRouter;
import org.onosproject.lisp.ctl.LispRouterAgent;
import org.onosproject.lisp.ctl.LispRouterId;
import org.onosproject.lisp.ctl.LispRouterListener;
import org.onosproject.lisp.msg.authentication.LispAuthenticationConfig;
import org.onosproject.net.device.DeviceService;
import org.onosproject.lisp.msg.protocols.LispInfoReply;
import org.onosproject.lisp.msg.protocols.LispInfoRequest;
import org.onosproject.lisp.msg.protocols.LispMessage;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Dictionary;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.stream.Collectors.toConcurrentMap;
import static org.onlab.util.Tools.get;
import static org.onlab.util.Tools.getIntegerProperty;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* LISP controller initiation class.
@ -51,8 +63,7 @@ public class LispControllerImpl implements LispController {
private static final String APP_ID = "org.onosproject.lisp-base";
private static final Logger log =
LoggerFactory.getLogger(LispControllerImpl.class);
private static final Logger log = getLogger(LispControllerImpl.class);
private static final String DEFAULT_LISP_AUTH_KEY = "onos";
private static final short DEFAULT_LISP_AUTH_KEY_ID = 1;
@ -60,46 +71,65 @@ public class LispControllerImpl implements LispController {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService cfgService;
@Property(name = "lispAuthKey", value = DEFAULT_LISP_AUTH_KEY,
label = "Authentication key which is used to calculate authentication " +
"data for LISP control message; default value is onos")
protected String lispAuthKey = DEFAULT_LISP_AUTH_KEY;
private String lispAuthKey = DEFAULT_LISP_AUTH_KEY;
@Property(name = "lispAuthKeyId", intValue = DEFAULT_LISP_AUTH_KEY_ID,
label = "Authentication key id which denotes the authentication method " +
"that ONOS uses to calculate the authentication data; " +
"1 denotes HMAC SHA1 encryption, 2 denotes HMAC SHA256 encryption; " +
"default value is 1")
protected int lispAuthKeyId = DEFAULT_LISP_AUTH_KEY_ID;
private int lispAuthKeyId = DEFAULT_LISP_AUTH_KEY_ID;
ExecutorService executorMessages =
newFixedThreadPool(4, groupedThreads("onos/lisp", "event-stats-%d", log));
protected LispRouterAgent agent = new DefaultLispRouterAgent();
ConcurrentMap<LispRouterId, LispRouter> connectedRouters = Maps.newConcurrentMap();
private Set<LispRouterListener> lispRouterListeners = new CopyOnWriteArraySet<>();
private Set<LispMessageListener> lispMessageListeners = new CopyOnWriteArraySet<>();
private final LispControllerBootstrap bootstrap = new LispControllerBootstrap();
private final LispAuthenticationConfig authConfig = LispAuthenticationConfig.getInstance();
@Activate
public void activate(ComponentContext context) {
coreService.registerApplication(APP_ID, this::cleanup);
cfgService.registerProperties(getClass());
coreService.registerApplication(APP_ID);
initAuthConfig(context.getProperties());
bootstrap.start();
log.info("Started");
}
/**
* Shutdowns all listening channel and all LISP channels.
* Clean information about routers before deactivating.
*/
private void cleanup() {
bootstrap.stop();
connectedRouters.values().forEach(LispRouter::disconnectRouter);
connectedRouters.clear();
}
@Deactivate
public void deactivate() {
cleanup();
cfgService.unregisterProperties(getClass(), false);
bootstrap.stop();
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
readComponentConfiguration(context);
bootstrap.stop();
bootstrap.start();
}
/**
@ -138,31 +168,189 @@ public class LispControllerImpl implements LispController {
@Override
public Iterable<LispRouter> getRouters() {
return null;
return connectedRouters.values();
}
@Override
public Iterable<LispRouter> getSubscribedRouters() {
return connectedRouters.entrySet()
.stream()
.filter(e -> e.getValue().isSubscribed())
.collect(toConcurrentMap(Map.Entry::getKey,
Map.Entry::getValue)).values();
}
@Override
public LispRouter getRouter(LispRouterId routerId) {
return null;
return connectedRouters.get(routerId);
}
@Override
public void addRouterListener(LispRouterListener listener) {
if (!lispRouterListeners.contains(listener)) {
lispRouterListeners.add(listener);
}
}
@Override
public void removeRouterListener(LispRouterListener listener) {
lispRouterListeners.remove(listener);
}
@Override
public void addMessageListener(LispMessageListener listener) {
if (!lispMessageListeners.contains(listener)) {
lispMessageListeners.add(listener);
}
}
@Override
public void removeMessageListener(LispMessageListener listener) {
lispMessageListeners.remove(listener);
}
/**
* Implementation of a LISP agent which is responsible for keeping track of
* connected LISP routers and the state in which they are in.
*/
public final class DefaultLispRouterAgent implements LispRouterAgent {
private final Logger log = getLogger(DefaultLispRouterAgent.class);
/**
* Prevents object instantiation from external class.
*/
private DefaultLispRouterAgent() {
}
@Override
public boolean addConnectedRouter(LispRouterId routerId, LispRouter router) {
if (connectedRouters.get(routerId) != null) {
log.error("Trying to add connectedRouter but found a previous " +
"value for routerId: {}", routerId);
return false;
} else {
log.info("Added router {}", routerId);
connectedRouters.put(routerId, router);
for (LispRouterListener listener : lispRouterListeners) {
listener.routerAdded(routerId);
}
return true;
}
}
@Override
public void removeConnectedRouter(LispRouterId routerId) {
if (connectedRouters.get(routerId) == null) {
log.error("Trying to remove router {} from connectedRouter " +
"list but no element was found", routerId);
} else {
log.info("Removed router {}", routerId);
connectedRouters.remove(routerId);
for (LispRouterListener listener : lispRouterListeners) {
listener.routerRemoved(routerId);
}
}
}
@Override
public void processUpstreamMessage(LispRouterId routerId, LispMessage message) {
switch (message.getType()) {
case LISP_MAP_REGISTER:
case LISP_MAP_REQUEST:
executorMessages.execute(
new LispIncomingMessageHandler(routerId, message));
break;
case LISP_INFO:
if (message instanceof LispInfoRequest) {
executorMessages.execute(
new LispIncomingMessageHandler(routerId, message));
} else {
log.warn("Not incoming LISP control message");
}
break;
default:
log.warn("Not incoming LISP control message");
break;
}
}
@Override
public void processDownstreamMessage(LispRouterId routerId, LispMessage message) {
switch (message.getType()) {
case LISP_MAP_NOTIFY:
case LISP_MAP_REPLY:
executorMessages.execute(
new LispOutgoingMessageHandler(routerId, message));
break;
case LISP_INFO:
if (message instanceof LispInfoReply) {
executorMessages.execute(
new LispOutgoingMessageHandler(routerId, message));
} else {
log.warn("Not outgoing LISP control message");
}
break;
default:
log.warn("Not outgoing LISP control message");
break;
}
}
}
/**
* LISP message handler.
*/
protected class LispMessageHandler implements Runnable {
private final LispRouterId routerId;
private final LispMessage message;
private final boolean isIncoming;
LispMessageHandler(LispRouterId routerId,
LispMessage message, boolean isIncoming) {
this.routerId = routerId;
this.message = message;
this.isIncoming = isIncoming;
}
@Override
public void run() {
for (LispMessageListener listener : lispMessageListeners) {
if (isIncoming) {
listener.handleIncomingMessage(routerId, message);
} else {
listener.handleOutgoingMessage(routerId, message);
}
}
}
}
/**
* LISP incoming message handler.
*/
protected final class LispIncomingMessageHandler
extends LispMessageHandler implements Runnable {
LispIncomingMessageHandler(LispRouterId routerId,
LispMessage message) {
super(routerId, message, true);
}
}
/**
* LISP outgoing message handler.
*/
protected final class LispOutgoingMessageHandler
extends LispMessageHandler implements Runnable {
LispOutgoingMessageHandler(LispRouterId routerId,
LispMessage message) {
super(routerId, message, false);
}
}
}

View File

@ -0,0 +1,329 @@
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.lisp.ctl.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.IpAddress;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.CoreService;
import org.onosproject.lisp.ctl.LispMessageListener;
import org.onosproject.lisp.ctl.LispRouter;
import org.onosproject.lisp.ctl.LispRouterAdapter;
import org.onosproject.lisp.ctl.LispRouterAgent;
import org.onosproject.lisp.ctl.LispRouterId;
import org.onosproject.lisp.ctl.LispRouterListener;
import org.onosproject.lisp.msg.protocols.DefaultLispMapNotify.DefaultNotifyBuilder;
import org.onosproject.lisp.msg.protocols.DefaultLispMapRecord.DefaultMapRecordBuilder;
import org.onosproject.lisp.msg.protocols.DefaultLispMapRegister.DefaultRegisterBuilder;
import org.onosproject.lisp.msg.protocols.LispMapNotify;
import org.onosproject.lisp.msg.protocols.LispMapNotify.NotifyBuilder;
import org.onosproject.lisp.msg.protocols.LispMapRecord;
import org.onosproject.lisp.msg.protocols.LispMapRecord.MapRecordBuilder;
import org.onosproject.lisp.msg.protocols.LispMapRegister;
import org.onosproject.lisp.msg.protocols.LispMapRegister.RegisterBuilder;
import org.onosproject.lisp.msg.protocols.LispMapReplyAction;
import org.onosproject.lisp.msg.protocols.LispMessage;
import org.onosproject.lisp.msg.types.LispIpv4Address;
import org.osgi.service.component.ComponentContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static junit.framework.TestCase.fail;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
/**
* Unit tests for the LISP controller implementation class.
*/
public class LispControllerImplTest {
private LispRouterId routerId1;
private LispRouterId routerId2;
private LispRouterId routerId3;
private LispRouter router1;
private LispRouter router2;
private LispRouter router3;
private LispControllerImpl controller;
private LispRouterAgent agent;
private TestRouterListener routerListener;
private TestMessageListener messageListener;
/**
* Tests harness for a router routerListener.
*/
static final class TestRouterListener implements LispRouterListener {
final List<LispRouterId> removedIds = Lists.newArrayList();
final List<LispRouterId> addedIds = Lists.newArrayList();
final List<LispRouterId> changedIds = Lists.newArrayList();
@Override
public void routerAdded(LispRouterId routerId) {
addedIds.add(routerId);
}
@Override
public void routerRemoved(LispRouterId routerId) {
removedIds.add(routerId);
}
@Override
public void routerChanged(LispRouterId routerId) {
changedIds.add(routerId);
}
}
/**
* Tests harness for a router messageListener.
*/
static final class TestMessageListener implements LispMessageListener {
final List<LispMessage> incomingMessages = Lists.newArrayList();
final List<LispMessage> outgoingMessages = Lists.newArrayList();
CountDownLatch incomingLatch = new CountDownLatch(1);
CountDownLatch outgoingLatch = new CountDownLatch(1);
@Override
public void handleIncomingMessage(LispRouterId routerId, LispMessage msg) {
synchronized (incomingMessages) {
incomingMessages.add(msg);
incomingLatch.countDown();
}
}
@Override
public void handleOutgoingMessage(LispRouterId routerId, LispMessage msg) {
synchronized (outgoingMessages) {
outgoingMessages.add(msg);
outgoingLatch.countDown();
}
}
public void waitUntilUpdateIsCalled() throws InterruptedException {
incomingLatch.await();
outgoingLatch.await();
}
}
/**
* Sets up routers to use as data, mocks and launches a controller instance.
*/
@Before
public void setUp() {
try {
router1 = new LispRouterAdapter();
routerId1 = LispRouterId.routerId(new URI("lisp:10.1.1.1"));
router2 = new LispRouterAdapter();
routerId2 = LispRouterId.routerId(new URI("lisp:10.1.1.2"));
router3 = new LispRouterAdapter();
routerId3 = LispRouterId.routerId(new URI("lisp:10.1.1.3"));
} catch (URISyntaxException e) {
// this will never happen...
fail();
}
controller = new LispControllerImpl();
agent = controller.agent;
routerListener = new TestRouterListener();
controller.addRouterListener(routerListener);
messageListener = new TestMessageListener();
controller.addMessageListener(messageListener);
controller.coreService = EasyMock.createMock(CoreService.class);
ComponentConfigService mockConfigService =
EasyMock.createMock(ComponentConfigService.class);
expect(mockConfigService.getProperties(anyObject())).andReturn(ImmutableSet.of());
mockConfigService.registerProperties(controller.getClass());
expectLastCall();
mockConfigService.unregisterProperties(controller.getClass(), false);
expectLastCall();
expect(mockConfigService.getProperties(anyObject())).andReturn(ImmutableSet.of());
controller.cfgService = mockConfigService;
replay(mockConfigService);
ComponentContext mockContext = EasyMock.createMock(ComponentContext.class);
Dictionary<String, Object> properties = new Hashtable<>();
properties.put("lispAuthKey", "onos");
properties.put("lispAuthKeyId", 1);
expect(mockContext.getProperties()).andReturn(properties);
replay(mockContext);
controller.activate(mockContext);
}
@After
public void tearDown() {
controller.removeRouterListener(routerListener);
controller.removeMessageListener(messageListener);
controller.deactivate();
}
/**
* Tests adding and removing connected routers.
*/
@Test
public void testAddRemoveConnectedRouter() {
// Test adding connected routers into agent
boolean addRouter1 = agent.addConnectedRouter(routerId1, router1);
assertThat(addRouter1, is(true));
boolean addRouter2 = agent.addConnectedRouter(routerId2, router2);
assertThat(addRouter2, is(true));
boolean addRouter3 = agent.addConnectedRouter(routerId3, router3);
assertThat(addRouter3, is(true));
// Test the callback methods that contained in router listener is fired
assertThat(routerListener.addedIds, hasSize(3));
assertThat(routerListener.addedIds, hasItems(routerId1, routerId2, routerId3));
// Test adding a router twice (duplicated router)
// this should return false to indicate that there is already a router
// has been added previously
boolean addBadRouter1 = agent.addConnectedRouter(routerId1, router1);
assertThat(addBadRouter1, is(false));
// Also make sure that the duplicated router will never increase the counter
assertThat(controller.connectedRouters.size(), is(3));
// Test querying the router list
Stream<LispRouter> queriedRouters = makeIntoStream(controller.getRouters());
long routerCount = queriedRouters.count();
assertThat(routerCount, is(3L));
// Test querying the individual router
LispRouter queriedRouter = controller.getRouter(routerId1);
assertThat(queriedRouter, is(router1));
// Test removing a router from connected router collection
agent.removeConnectedRouter(routerId2);
Stream<LispRouter> queriedRoutersAfterRemoval =
makeIntoStream(controller.getRouters());
long routerCountAfterRemoval = queriedRoutersAfterRemoval.count();
assertThat(routerCountAfterRemoval, is(2L));
// Test the callback methods that contained in router listener is fired
assertThat(routerListener.removedIds, hasSize(1));
assertThat(routerListener.removedIds, hasItems(routerId2));
// Test querying the removed switch
LispRouter queriedRouterAfterRemoval = controller.getRouter(routerId2);
assertThat(queriedRouterAfterRemoval, nullValue());
}
/**
* Tests adding and removing LISP messages.
*/
@Test
public void testLispMessagePopulate() throws InterruptedException {
RegisterBuilder registerBuilder = new DefaultRegisterBuilder();
List<LispMapRecord> records = ImmutableList.of(getMapRecord(), getMapRecord());
LispMapRegister register = registerBuilder
.withIsProxyMapReply(true)
.withIsWantMapNotify(false)
.withKeyId((short) 1)
.withAuthKey("onos")
.withNonce(1L)
.withMapRecords(records)
.build();
NotifyBuilder notifyBuilder = new DefaultNotifyBuilder();
LispMapNotify notify = notifyBuilder
.withKeyId((short) 1)
.withAuthKey("onos")
.withNonce(1L)
.withMapRecords(records)
.build();
// Test the callback methods that contained in message listener is fired
agent.processUpstreamMessage(routerId1, register);
// Following line will be ignored
agent.processUpstreamMessage(routerId1, notify);
agent.processDownstreamMessage(routerId1, notify);
// Following line will be ignored
agent.processDownstreamMessage(routerId1, register);
messageListener.waitUntilUpdateIsCalled();
assertThat(messageListener.incomingMessages, hasSize(1));
assertThat(messageListener.incomingMessages, hasItems(register));
assertThat(messageListener.outgoingMessages, hasSize(1));
assertThat(messageListener.outgoingMessages, hasItems(notify));
}
/**
* Generates and returns a map record.
*
* @return a map record
*/
private LispMapRecord getMapRecord() {
MapRecordBuilder builder1 = new DefaultMapRecordBuilder();
LispIpv4Address ipv4Locator1 = new LispIpv4Address(IpAddress.valueOf("192.168.1.1"));
return builder1
.withRecordTtl(100)
.withAuthoritative(true)
.withMapVersionNumber((short) 1)
.withMaskLength((byte) 0x01)
.withAction(LispMapReplyAction.NativelyForward)
.withEidPrefixAfi(ipv4Locator1)
.build();
}
/**
* Converts an Iterable of some type into a stream of that type.
*
* @param items Iterable of objects
* @param <T> type of the items in the iterable
* @return stream of objects of type T
*/
private <T> Stream<T> makeIntoStream(Iterable<T> items) {
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
items.iterator(), Spliterator.ORDERED), false);
}
}

View File

@ -57,6 +57,10 @@
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
</dependencies>
</project>