[ONOS-5298] New VPLS NeighbourHandler to support multiple VLANs

Change-Id: I5c809b9c108760defc666b9f5e9d2a17c64047eb
diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/VplsNeighbourHandler.java b/apps/vpls/src/main/java/org/onosproject/vpls/VplsNeighbourHandler.java
index 69b8e34..d706dca 100644
--- a/apps/vpls/src/main/java/org/onosproject/vpls/VplsNeighbourHandler.java
+++ b/apps/vpls/src/main/java/org/onosproject/vpls/VplsNeighbourHandler.java
@@ -16,8 +16,7 @@
 
 package org.onosproject.vpls;
 
-import com.google.common.collect.Maps;
-
+import com.google.common.collect.SetMultimap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -33,20 +32,28 @@
 import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
 import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
 import org.onosproject.net.Host;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.host.HostService;
+import org.onosproject.vpls.config.VplsConfigurationService;
 import org.slf4j.Logger;
 
-import java.util.Map;
+import java.util.Collection;
+import java.util.Set;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Handles neighbour messages for VPLS use case.
- * Handlers will be changed automatically by interface or network configuration
- * events.
+ * Handlers will be changed automatically by interface or network configuration events.
  */
 @Component(immediate = true)
 public class VplsNeighbourHandler {
+    private static final String UNKNOWN_CONTEXT = "Unknown context type: {}";
+
+    private static final String CAN_NOT_FIND_NETWORK =
+            "Cannot find VPLS for port {} with VLAN Id {}.";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -57,16 +64,22 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NeighbourResolutionService neighbourService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VplsConfigurationService vplsConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService configService;
+
     private VplsInterfaceListener interfaceListener
             = new VplsInterfaceListener();
 
-    private VplsNeighbourMessageHandler neighbourHandler =
+    protected VplsNeighbourMessageHandler neighbourHandler =
             new VplsNeighbourMessageHandler();
 
-    private final Logger log = getLogger(getClass());
+    protected VplsConfigListener configListener =
+            new VplsConfigListener();
 
-    private Map<Interface, NeighbourMessageHandler> neighbourHandlers =
-            Maps.newHashMap();
+    private final Logger log = getLogger(getClass());
 
     private ApplicationId appId;
 
@@ -75,42 +88,29 @@
     protected void activate() {
         appId = coreService.registerApplication(Vpls.VPLS_APP);
         interfaceService.addListener(interfaceListener);
-
-        interfaceService.getInterfaces().forEach(intf -> {
-            neighbourHandlers.put(intf, neighbourHandler);
-
-            neighbourService.registerNeighbourHandler(intf, neighbourHandler, appId);
-        });
-
-        log.debug("Activated");
+        configService.addListener(configListener);
+        configNeighbourHandler();
     }
 
     @Deactivate
     protected void deactivate() {
         interfaceService.removeListener(interfaceListener);
-        neighbourHandlers.entrySet().forEach(e -> {
-            neighbourService.unregisterNeighbourHandler(e.getKey(), e.getValue(), appId);
-        });
-        log.debug("Deactivated");
+        configService.removeListener(configListener);
+        neighbourService.unregisterNeighbourHandlers(appId);
     }
 
-    private void configNeighbourHandler(Interface intf,
-                                          NeighbourMessageHandler handler,
-                                          InterfaceEvent.Type eventType) {
-        switch (eventType) {
-            case INTERFACE_ADDED:
-                neighbourHandlers.put(intf, handler);
-                neighbourService.registerNeighbourHandler(intf, handler, appId);
-                break;
-            case INTERFACE_REMOVED:
-                neighbourHandlers.remove(intf, handler);
-                neighbourService.unregisterNeighbourHandler(intf, handler, appId);
-                break;
-            case INTERFACE_UPDATED:
-                break;
-            default:
-                break;
-        }
+    private void configNeighbourHandler() {
+        neighbourService.unregisterNeighbourHandlers(appId);
+        Set<Interface> interfaces = vplsConfigService.getAllInterfaces();
+
+        interfaceService.getInterfaces()
+                .stream()
+                .filter(interfaces::contains)
+                .forEach(intf -> {
+                    neighbourService.registerNeighbourHandler(intf,
+                                                              neighbourHandler,
+                                                              appId);
+                });
     }
 
     /**
@@ -124,34 +124,84 @@
 
             switch (context.type()) {
                 case REQUEST:
-                    interfaceService.getInterfacesByVlan(context.vlan())
-                            .stream()
-                            .filter(intf -> !context.inPort().equals(intf.connectPoint()))
-                            .forEach(context::forward);
+                    handleRequest(context);
                     break;
+
                 case REPLY:
-                    hostService.getHostsByMac(context.dstMac())
-                            .stream()
-                            .filter(host -> host.vlan().equals(context.vlan()))
-                            .map(Host::location)
-                            .forEach(context::forward);
+                    handleReply(context, hostService);
                     break;
 
                 default:
-                    log.warn("Unknown context type: {}", context.type());
+                    log.warn(UNKNOWN_CONTEXT, context.type());
                     break;
             }
         }
     }
 
     /**
+     * Handles request messages.
+     *
+     * @param context the message context
+     */
+    protected void handleRequest(NeighbourMessageContext context) {
+
+        SetMultimap<String, Interface> vplsNetwork =
+                vplsConfigService.getVplsNetwork(context.vlan(), context.inPort());
+
+        if (vplsNetwork != null) {
+            Collection<Interface> vplsInterfaces = vplsNetwork.values();
+            vplsInterfaces.stream()
+                    .filter(intf -> !context.inPort().equals(intf.connectPoint()))
+                    .forEach(context::forward);
+
+        } else {
+            log.debug(CAN_NOT_FIND_NETWORK, context.inPort(), context.vlan());
+        }
+    }
+
+    /**
+     * Handles reply messages between VLAN tagged interfaces.
+     *
+     * @param context the message context
+     * @param hostService the host service
+     */
+    protected void handleReply(NeighbourMessageContext context,
+                               HostService hostService) {
+
+        SetMultimap<String, Interface> vplsNetwork =
+                vplsConfigService.getVplsNetwork(context.vlan(), context.inPort());
+
+        Set<Host> hosts = hostService.getHostsByMac(context.dstMac());
+        if (vplsNetwork != null) {
+            Collection<Interface> vplsInterfaces = vplsNetwork.values();
+            hosts.forEach(host -> vplsInterfaces.stream()
+                    .filter(intf -> intf.connectPoint().equals(host.location()))
+                    .filter(intf -> intf.vlan().equals(host.vlan()))
+                    .forEach(context::forward));
+        } else {
+            log.debug(CAN_NOT_FIND_NETWORK, context.inPort(), context.vlan());
+        }
+    }
+
+    /**
      * Listener for interface configuration events.
      */
     private class VplsInterfaceListener implements InterfaceListener {
 
         @Override
         public void event(InterfaceEvent event) {
-            configNeighbourHandler(event.subject(), neighbourHandler, event.type());
+            configNeighbourHandler();
+        }
+    }
+
+    /**
+     * Listener for network configuration events.
+     */
+    private class VplsConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            configNeighbourHandler();
         }
     }
 
diff --git a/apps/vpls/src/test/java/org/onosproject/vpls/VplsNeighbourHandlerTest.java b/apps/vpls/src/test/java/org/onosproject/vpls/VplsNeighbourHandlerTest.java
new file mode 100644
index 0000000..90e3415
--- /dev/null
+++ b/apps/vpls/src/test/java/org/onosproject/vpls/VplsNeighbourHandlerTest.java
@@ -0,0 +1,699 @@
+/*
+ * 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.vpls;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+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.InterfaceListener;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.incubator.net.neighbour.NeighbourHandlerRegistration;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
+import org.onosproject.incubator.net.neighbour.NeighbourProtocol;
+import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.vpls.config.VplsConfigurationService;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Tests the the {@link VplsNeighbourHandler} class.
+ */
+public class VplsNeighbourHandlerTest {
+    private final ConnectPoint of1p1 =
+            new ConnectPoint(DeviceId.deviceId("of:1"),
+                             PortNumber.portNumber(1));
+    private final ConnectPoint of4p1 =
+            new ConnectPoint(DeviceId.deviceId("of:4"),
+                             PortNumber.portNumber(1));
+    private final ConnectPoint of3p1 =
+            new ConnectPoint(DeviceId.deviceId("of:3"),
+                             PortNumber.portNumber(1));
+    private final ConnectPoint of4p2 =
+            new ConnectPoint(DeviceId.deviceId("of:4"),
+                             PortNumber.portNumber(2));
+    private final ConnectPoint of2p1 =
+            new ConnectPoint(DeviceId.deviceId("of:2"),
+                             PortNumber.portNumber(1));
+
+    private final VlanId vlan100 = VlanId.vlanId("100");
+    private final VlanId vlan200 = VlanId.vlanId("200");
+    private final VlanId vlan300 = VlanId.vlanId("300");
+
+    private final Interface v100h1 =
+            new Interface("v100h1", of1p1, null, null, vlan100);
+    private final Interface v100h2 =
+            new Interface("v100h2", of4p1, null, null, vlan100);
+    private final Interface v200h1 =
+            new Interface("v200h1", of4p2, null, null, vlan200);
+    private final Interface v300h1 =
+            new Interface("v300h1", of3p1, null, null, vlan300);
+    private final Interface v200h2 =
+            new Interface("v200h2", of2p1, null, null, vlan200);
+
+    private final MacAddress mac1 = MacAddress.valueOf("00:00:00:00:00:01");
+    private final MacAddress mac2 = MacAddress.valueOf("00:00:00:00:00:02");
+    private final MacAddress mac3 = MacAddress.valueOf("00:00:00:00:00:03");
+    private final MacAddress mac4 = MacAddress.valueOf("00:00:00:00:00:04");
+    private final MacAddress mac5 = MacAddress.valueOf("00:00:00:00:00:05");
+
+    private final ProviderId pid = new ProviderId("of", "foo");
+
+    private final Host v100host1 = makeHost(mac1, vlan100, of1p1);
+    private final Host v100host2 = makeHost(mac2, vlan100, of4p1);
+    private final Host v200host1 = makeHost(mac3, vlan200, of4p2);
+    private final Host v300host1 = makeHost(mac4, vlan300, of3p1);
+    private final Host v200host2 = makeHost(mac5, vlan200, of2p1);
+
+    private final Set<Host> availableHosts = ImmutableSet.of(v100host1,
+                                                             v100host2,
+                                                             v200host1,
+                                                             v300host1,
+                                                             v200host2);
+
+    private final Set<Interface> avaliableInterfaces =
+            ImmutableSet.of(v100h1, v100h2, v200h1, v200h2, v300h1);
+
+    private VplsNeighbourHandler vplsNeighbourHandler;
+
+    private HostService hostService;
+
+    /**
+     * Sets up 2 VPLS which contain 2 networks and 5 hosts.
+     * net1 contains 3 hosts: v100h1, v200h1 and v300h1
+     * net2 contains 2 hosts: v100h2, v200h2
+     */
+    @Before
+    public void setUp() {
+        vplsNeighbourHandler = new VplsNeighbourHandler();
+        SetMultimap<String, Interface> vplsNetworks =
+                HashMultimap.create();
+        vplsNetworks.put("net1", v100h1);
+        vplsNetworks.put("net1", v200h1);
+        vplsNetworks.put("net1", v300h1);
+        vplsNetworks.put("net2", v100h2);
+        vplsNetworks.put("net2", v200h2);
+        vplsNeighbourHandler.vplsConfigService =
+                new TestVplsConfigService(vplsNetworks);
+        vplsNeighbourHandler.interfaceService =
+                new TestInterfaceService();
+        vplsNeighbourHandler.neighbourService =
+                new TestNeighbourService();
+
+        hostService = new TestHostService();
+    }
+
+    @After
+    public void tearDown() {
+
+    }
+
+    /**
+     * Sends request messages to all hosts in VPLS net1.
+     * Request messages should be received from other hosts in net1.
+     */
+    @Test
+    public void testNet1RequestMessage() {
+        // Request from v100h1 (net1)
+        // Should be received by v200h1 and v300h1
+        TestMessageContext requestMessage =
+                makeBroadcastRequestContext(v100host1);
+        Set<Interface> expectInterfaces = ImmutableSet.of(v200h1, v300h1);
+        vplsNeighbourHandler.handleRequest(requestMessage);
+        assertEquals(expectInterfaces, requestMessage.forwardResults);
+
+        // Request from v200h1 (net1)
+        // Should be received by v100h1 and v300h1
+        requestMessage = makeBroadcastRequestContext(v200host1);
+        expectInterfaces = ImmutableSet.of(v100h1, v300h1);
+        vplsNeighbourHandler.handleRequest(requestMessage);
+        assertEquals(expectInterfaces, requestMessage.forwardResults);
+
+        // Request from v300h1 (net1)
+        // Should be received by v100h1 and v200h1
+        requestMessage = makeBroadcastRequestContext(v300host1);
+        expectInterfaces = ImmutableSet.of(v100h1, v200h1);
+        vplsNeighbourHandler.handleRequest(requestMessage);
+        assertEquals(expectInterfaces, requestMessage.forwardResults);
+    }
+
+    /**
+     * Sends request messages to all hosts in VPLS net2.
+     * Request messages should be received from other hosts in net2.
+     */
+    @Test
+    public void testNet2RequestMessage() {
+        // Request from v100h2
+        // Should be received by v200h2
+        TestMessageContext requestMessage =
+                makeBroadcastRequestContext(v100host2);
+        Set<Interface> expectInterfaces = ImmutableSet.of(v200h2);
+        vplsNeighbourHandler.handleRequest(requestMessage);
+        assertEquals(expectInterfaces, requestMessage.forwardResults);
+
+        // Request from v200h2
+        // Should be received by v100h2
+        requestMessage = makeBroadcastRequestContext(v200host2);
+        expectInterfaces = ImmutableSet.of(v100h2);
+        vplsNeighbourHandler.handleRequest(requestMessage);
+        assertEquals(expectInterfaces, requestMessage.forwardResults);
+    }
+
+    /**
+     * Sends reply messages to hosts in VPLS net1.
+     * Reply messages should be received by the host with MAC address equal to
+     * the dstMac of the message context.
+     */
+    @Test
+    public void testNet1ReplyMessage() {
+        // Response from v100h1 (net1) to v200h1 (net1)
+        // Should be received by v200h1
+        TestMessageContext replyMessage =
+                makeReplyContext(v100host1, v200host1);
+        Set<Interface> expectInterfaces = ImmutableSet.of(v200h1);
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+
+        // Response from v200h1 (net1) to v300h1 (net1)
+        // Should be received by v300h1
+        replyMessage = makeReplyContext(v200host1, v300host1);
+        expectInterfaces = ImmutableSet.of(v300h1);
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+
+        // Response from v300h1 (net1) to v100h1 (net1)
+        // Should be received by v100h1
+        replyMessage = makeReplyContext(v300host1, v100host1);
+        expectInterfaces = ImmutableSet.of(v100h1);
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+    }
+
+    /**
+     * Sends reply messages to hosts in VPLS net2.
+     * Reply messages should be received by the host with MAC address equal to
+     * the dstMac of the message context.
+     */
+    @Test
+    public void testNet2ReplyMessage() {
+        // Response from v100h2 (net2) to v200h2 (net2)
+        // Should be received by v200h2
+        TestMessageContext replyMessage =
+                makeReplyContext(v100host2, v200host2);
+        Set<Interface> expectInterfaces = ImmutableSet.of(v200h2);
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+
+        // Response from v200h2 (net2) to v100h2 (net2)
+        // Should be received by v100h2
+        replyMessage = makeReplyContext(v200host2, v100host2);
+        expectInterfaces = ImmutableSet.of(v100h2);
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+    }
+
+    /**
+     * Sends wrong reply messages to hosts.
+     * The source and the destination MAC addresses are not set on any host of the VPLS.
+     * The reply messages won't be received by any hosts.
+     */
+    @Test
+    public void testWrongReplyMessage() {
+        // Response from v100h1 (net1) to v100h2 (net2)
+        // forward results should be empty
+        TestMessageContext replyMessage = makeReplyContext(v100host1, v100host2);
+        Set<Interface> expectInterfaces = ImmutableSet.of();
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+
+        // Response from v200h2 (net2) to v300h1 (net1)
+        // forward results should be empty
+        replyMessage = makeReplyContext(v200host2, v300host1);
+        expectInterfaces = ImmutableSet.of();
+        vplsNeighbourHandler.handleReply(replyMessage, hostService);
+        assertEquals(expectInterfaces, replyMessage.forwardResults);
+    }
+
+    private Host makeHost(MacAddress mac, VlanId vlan, ConnectPoint cp) {
+        return new DefaultHost(pid,
+                               HostId.hostId(mac, vlan),
+                               mac,
+                               vlan,
+                               new HostLocation(cp, 0),
+                               Sets.newHashSet());
+    }
+
+    private TestMessageContext makeBroadcastRequestContext(Host host) {
+        return new TestMessageContext(host.location(),
+                                      host.mac(),
+                                      MacAddress.BROADCAST,
+                                      host.vlan(),
+                                      NeighbourMessageType.REQUEST);
+    }
+
+    private TestMessageContext makeReplyContext(Host src, Host dst) {
+        return new TestMessageContext(src.location(),
+                                      src.mac(),
+                                      dst.mac(),
+                                      src.vlan(),
+                                      NeighbourMessageType.REPLY);
+    }
+
+    private class TestMessageContext implements NeighbourMessageContext {
+
+
+        private final NeighbourMessageType type;
+        private final MacAddress srcMac;
+        private final MacAddress dstMac;
+        private final ConnectPoint inPort;
+        private final VlanId vlanId;
+
+        public Set<Interface> forwardResults;
+
+        /**
+         * Creates new neighbour message context for test.
+         *
+         * @param inPort the input port
+         * @param srcMac the source Mac
+         * @param dstMac the destination Mac
+         * @param vlanId the VLAN Id
+         * @param type the message context type
+         */
+        public TestMessageContext(
+                ConnectPoint inPort,
+                MacAddress srcMac,
+                MacAddress dstMac,
+                VlanId vlanId,
+                NeighbourMessageType type) {
+
+            this.inPort = inPort;
+            this.srcMac = srcMac;
+            this.dstMac = dstMac;
+            this.vlanId = vlanId;
+            this.type = type;
+
+            this.forwardResults = Sets.newHashSet();
+
+        }
+        @Override
+        public ConnectPoint inPort() {
+            return inPort;
+        }
+
+        @Override
+        public NeighbourMessageType type() {
+            return type;
+        }
+
+        @Override
+        public VlanId vlan() {
+            return vlanId;
+        }
+
+        @Override
+        public MacAddress srcMac() {
+            return srcMac;
+        }
+
+        @Override
+        public MacAddress dstMac() {
+            return dstMac;
+        }
+
+        @Override
+        public IpAddress target() {
+            return null;
+        }
+
+        @Override
+        public IpAddress sender() {
+            return null;
+        }
+
+        @Override
+        public void forward(ConnectPoint outPort) {
+
+        }
+
+        @Override
+        public void forward(Interface outIntf) {
+            forwardResults.add(outIntf);
+        }
+
+        @Override
+        public void reply(MacAddress targetMac) {
+
+        }
+
+        @Override
+        public void flood() {
+
+        }
+
+        @Override
+        public void drop() {
+
+        }
+
+        @Override
+        public Ethernet packet() {
+            return null;
+        }
+
+        @Override
+        public NeighbourProtocol protocol() {
+            return null;
+        }
+    }
+
+    private class TestVplsConfigService implements VplsConfigurationService {
+
+        private final SetMultimap<String, Interface> vplsNetworks;
+
+        public TestVplsConfigService(SetMultimap<String, Interface> networks) {
+            this.vplsNetworks = networks;
+        }
+
+        @Override
+        public void addVpls(String name, Set<String> ifaceNames) {
+            if (!vplsNetworks.containsKey(name)) {
+                ifaceNames.forEach(ifaceName -> {
+                    avaliableInterfaces.forEach(intf -> {
+                        if (intf.name().equals(ifaceName)) {
+                            vplsNetworks.put(name, intf);
+                        }
+                    });
+                });
+            }
+        }
+
+        @Override
+        public void removeVpls(String name) {
+            if (vplsNetworks.containsKey(name)) {
+                vplsNetworks.removeAll(name);
+            }
+        }
+
+        @Override
+        public void addInterfaceToVpls(String name, String ifaceName) {
+            avaliableInterfaces.forEach(intf -> {
+                if (intf.name().equals(ifaceName)) {
+                    vplsNetworks.put(name, intf);
+                }
+            });
+        }
+
+        @Override
+        public void removeInterfaceFromVpls(String ifaceName) {
+            SetMultimap<String, Interface> toBeRemoved = HashMultimap.create();
+            vplsNetworks.entries().forEach(e -> {
+                if (e.getValue().name().equals(ifaceName)) {
+                    toBeRemoved.put(e.getKey(), e.getValue());
+                }
+            });
+
+            toBeRemoved.entries()
+                    .forEach(e -> vplsNetworks.remove(e.getKey(),
+                                                      e.getValue()));
+        }
+
+        @Override
+        public void cleanVpls() {
+            vplsNetworks.clear();
+        }
+
+        @Override
+        public Set<String> getVplsAffectedByApi() {
+            return null;
+        }
+
+        @Override
+        public Set<Interface> getAllInterfaces() {
+            return ImmutableSet.copyOf(vplsNetworks.values());
+        }
+
+        @Override
+        public Set<Interface> getVplsInterfaces(String name) {
+            return vplsNetworks.get(name)
+                    .stream()
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<String> getAllVpls() {
+            return vplsNetworks.keySet();
+        }
+
+        @Override
+        public Set<String> getOldVpls() {
+            return null;
+        }
+
+        @Override
+        public SetMultimap<String, Interface> getVplsNetworks() {
+            return ImmutableSetMultimap.copyOf(vplsNetworks);
+        }
+
+        @Override
+        public SetMultimap<String, Interface> getVplsNetwork(VlanId vlan,
+                                                             ConnectPoint connectPoint) {
+            String vplsNetworkName =
+                    vplsNetworks.entries().stream()
+                            .filter(e -> e.getValue().connectPoint().equals(connectPoint))
+                            .filter(e -> e.getValue().vlan().equals(vlan))
+                            .map(e -> e.getKey())
+                            .findFirst()
+                            .orElse(null);
+            SetMultimap<String, Interface> result = HashMultimap.create();
+            if (vplsNetworkName != null &&
+                    vplsNetworks.containsKey(vplsNetworkName)) {
+                vplsNetworks.get(vplsNetworkName)
+                        .forEach(intf -> result.put(vplsNetworkName, intf));
+                return result;
+            }
+            return null;
+        }
+    }
+
+    class TestHostService extends HostServiceAdapter {
+        @Override
+        public Set<Host> getHostsByMac(MacAddress mac) {
+            return availableHosts.stream()
+                    .filter(host -> host.mac().equals(mac))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Iterable<Host> getHosts() {
+            return availableHosts;
+        }
+
+        @Override
+        public Set<Host> getHostsByVlan(VlanId vlanId) {
+            return availableHosts.stream()
+                    .filter(host -> host.vlan().equals(vlanId))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public int getHostCount() {
+            return availableHosts.size();
+        }
+
+        @Override
+        public Host getHost(HostId hostId) {
+            return availableHosts.stream()
+                    .filter(host -> host.id().equals(hostId))
+                    .findFirst()
+                    .orElse(null);
+        }
+    }
+
+    private class TestNeighbourService implements NeighbourResolutionService {
+        private SetMultimap<ConnectPoint, NeighbourHandlerRegistration> handlerRegs;
+
+        public TestNeighbourService() {
+            handlerRegs = HashMultimap.create();
+        }
+
+        @Override
+        public void registerNeighbourHandler(ConnectPoint connectPoint,
+                                             NeighbourMessageHandler handler,
+                                             ApplicationId appId) {
+            Interface intf =
+                    new Interface(null, connectPoint, null, null, null);
+
+            NeighbourHandlerRegistration reg =
+                    new HandlerRegistration(handler, intf, appId);
+
+            handlerRegs.put(connectPoint, reg);
+        }
+
+        @Override
+        public void registerNeighbourHandler(Interface intf,
+                                             NeighbourMessageHandler handler,
+                                             ApplicationId appId) {
+            NeighbourHandlerRegistration reg =
+                    new HandlerRegistration(handler, intf, appId);
+            handlerRegs.put(intf.connectPoint(), reg);
+        }
+
+        @Override
+        public void unregisterNeighbourHandler(ConnectPoint connectPoint,
+                                               NeighbourMessageHandler handler,
+                                               ApplicationId appId) {
+            handlerRegs.removeAll(connectPoint);
+        }
+
+        @Override
+        public void unregisterNeighbourHandler(Interface intf,
+                                               NeighbourMessageHandler handler,
+                                               ApplicationId appId) {
+            handlerRegs.removeAll(intf.connectPoint());
+        }
+
+        @Override
+        public void unregisterNeighbourHandlers(ApplicationId appId) {
+            handlerRegs.clear();
+        }
+
+        @Override
+        public Map<ConnectPoint, Collection<NeighbourHandlerRegistration>> getHandlerRegistrations() {
+            return handlerRegs.asMap();
+        }
+
+        private class HandlerRegistration implements NeighbourHandlerRegistration {
+            private final Interface intf;
+            private final NeighbourMessageHandler handler;
+            private final ApplicationId appId;
+
+            /**
+             * Creates a new registration handler.
+             *
+             * @param handler the neighbour message handler
+             * @param intf the interface
+             */
+            public HandlerRegistration(NeighbourMessageHandler handler,
+                                       Interface intf,
+                                       ApplicationId appId) {
+                this.intf = intf;
+                this.handler = handler;
+                this.appId = appId;
+            }
+
+            @Override
+            public Interface intf() {
+                return intf;
+            }
+
+            @Override
+            public NeighbourMessageHandler handler() {
+                return handler;
+            }
+
+            @Override
+            public ApplicationId appId() {
+                return appId;
+            }
+
+        }
+    }
+
+    class TestInterfaceService implements InterfaceService {
+
+        @Override
+        public void addListener(InterfaceListener listener) {
+
+        }
+
+        @Override
+        public void removeListener(InterfaceListener listener) {
+
+        }
+
+        @Override
+        public Set<Interface> getInterfaces() {
+            return avaliableInterfaces;
+        }
+
+        @Override
+        public Interface getInterfaceByName(ConnectPoint connectPoint,
+                                            String name) {
+            return avaliableInterfaces.stream()
+                    .filter(intf -> intf.name().equals(name))
+                    .findFirst()
+                    .orElse(null);
+
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+            return avaliableInterfaces.stream()
+                    .filter(intf -> intf.connectPoint().equals(port))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByIp(IpAddress ip) {
+            return avaliableInterfaces.stream()
+                    .filter(intf -> intf.ipAddressesList().contains(ip))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByVlan(VlanId vlan) {
+            return avaliableInterfaces.stream()
+                    .filter(intf -> intf.vlan().equals(vlan))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Interface getMatchingInterface(IpAddress ip) {
+            return avaliableInterfaces.stream()
+                    .filter(intf -> intf.ipAddressesList().contains(ip))
+                    .findFirst()
+                    .orElse(null);
+        }
+    }
+}