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

Change-Id: I4a51a8ef9162e3feee543f40fa92a0435186d1c9
diff --git a/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispController.java b/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispController.java
index 5c47109..1a3641d 100644
--- a/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispController.java
+++ b/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispController.java
@@ -29,6 +29,13 @@
     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.
      *
      * @param routerId the router to fetch
diff --git a/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispRouter.java b/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispRouter.java
index a6fa99f..5b32853 100644
--- a/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispRouter.java
+++ b/protocols/lisp/api/src/main/java/org/onosproject/lisp/ctl/LispRouter.java
@@ -80,4 +80,47 @@
      * @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();
 }
diff --git a/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispControllerAdapter.java b/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispControllerAdapter.java
index c096131..aeebeeb 100644
--- a/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispControllerAdapter.java
+++ b/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispControllerAdapter.java
@@ -25,6 +25,11 @@
     }
 
     @Override
+    public Iterable<LispRouter> getSubscribedRouters() {
+        return null;
+    }
+
+    @Override
     public LispRouter getRouter(LispRouterId routerId) {
         return null;
     }
diff --git a/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispRouterAdapter.java b/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispRouterAdapter.java
index 4dcc736..d6570e8 100644
--- a/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispRouterAdapter.java
+++ b/protocols/lisp/api/src/test/java/org/onosproject/lisp/ctl/LispRouterAdapter.java
@@ -63,4 +63,34 @@
     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() {
+
+    }
 }
diff --git a/protocols/lisp/ctl/BUCK b/protocols/lisp/ctl/BUCK
index 622dae5..447eeb4 100644
--- a/protocols/lisp/ctl/BUCK
+++ b/protocols/lisp/ctl/BUCK
@@ -13,6 +13,7 @@
     '//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 (
diff --git a/protocols/lisp/ctl/pom.xml b/protocols/lisp/ctl/pom.xml
index c8954bb..ea22bd3 100644
--- a/protocols/lisp/ctl/pom.xml
+++ b/protocols/lisp/ctl/pom.xml
@@ -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>
diff --git a/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerBootstrap.java b/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerBootstrap.java
index 29af596..449983a 100644
--- a/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerBootstrap.java
+++ b/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerBootstrap.java
@@ -48,6 +48,7 @@
 
     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 @@
 
             configBootstrapOptions(bootstrap);
 
-            List<ChannelFuture> channelFutures = Lists.newArrayList();
-
             lispPorts.forEach(p -> {
                 InetSocketAddress sa = new InetSocketAddress(p);
                 channelFutures.add(bootstrap.bind(sa));
@@ -152,6 +151,7 @@
         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());
         }
diff --git a/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerImpl.java b/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerImpl.java
index 0d1bdf0..5d82527 100644
--- a/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerImpl.java
+++ b/protocols/lisp/ctl/src/main/java/org/onosproject/lisp/ctl/impl/LispControllerImpl.java
@@ -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.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 @@
 
     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;
@@ -61,45 +72,64 @@
     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 @@
 
     @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);
+        }
     }
 }
diff --git a/protocols/lisp/ctl/src/test/java/org/onosproject/lisp/ctl/impl/LispControllerImplTest.java b/protocols/lisp/ctl/src/test/java/org/onosproject/lisp/ctl/impl/LispControllerImplTest.java
new file mode 100644
index 0000000..8335d64
--- /dev/null
+++ b/protocols/lisp/ctl/src/test/java/org/onosproject/lisp/ctl/impl/LispControllerImplTest.java
@@ -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);
+    }
+}
diff --git a/protocols/lisp/pom.xml b/protocols/lisp/pom.xml
index 45a3d3d..1103ed6 100644
--- a/protocols/lisp/pom.xml
+++ b/protocols/lisp/pom.xml
@@ -57,6 +57,10 @@
             <artifactId>easymock</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
     </dependencies>
 
 </project>