Unit tests for NetworkCongifLinksProvider

Also fixed a few bugs in the provider that were turned up by the new tests.

Change-Id: Icafc945251b4d35ac3f285302af150c0a4d646d6
diff --git a/providers/netcfglinks/pom.xml b/providers/netcfglinks/pom.xml
index 3cc0799..aeb2c34 100644
--- a/providers/netcfglinks/pom.xml
+++ b/providers/netcfglinks/pom.xml
@@ -56,6 +56,15 @@
             <artifactId>easymock</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 </project>
diff --git a/providers/netcfglinks/src/main/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProvider.java b/providers/netcfglinks/src/main/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProvider.java
index edbc25c..2671d93 100644
--- a/providers/netcfglinks/src/main/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProvider.java
+++ b/providers/netcfglinks/src/main/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProvider.java
@@ -117,7 +117,7 @@
     private final InternalDeviceListener deviceListener = new InternalDeviceListener();
     private final InternalConfigListener cfgListener = new InternalConfigListener();
 
-    private Set<LinkKey> configuredLinks = new HashSet<>();
+    protected Set<LinkKey> configuredLinks = new HashSet<>();
 
     public NetworkConfigLinksProvider() {
         super(new ProviderId("lldp", PROVIDER_NAME));
@@ -259,6 +259,35 @@
     }
 
     /**
+     * Removes after stopping discovery helper for specified device.
+     * @param deviceId device to remove
+     */
+    private void removeDevice(final DeviceId deviceId) {
+        discoverers.computeIfPresent(deviceId, (did, ld) -> {
+            ld.stop();
+            return null;
+        });
+
+    }
+
+    /**
+     * Removes a port from the specified discovery helper.
+     * @param port the port
+     */
+    private void removePort(Port port) {
+        if (port.element() instanceof Device) {
+            Device d = (Device) port.element();
+            LinkDiscovery ld = discoverers.get(d.id());
+            if (ld != null) {
+                ld.removePort(port.number());
+            }
+        } else {
+            log.warn("Attempted to remove non-Device port", port);
+        }
+    }
+
+
+    /**
      * Processes incoming packets.
      */
     private class InternalPacketProcessor implements PacketProcessor {
@@ -355,21 +384,21 @@
                         updateDevice(device).ifPresent(ld -> updatePort(ld, port));
                     } else {
                         log.debug("Port down {}", port);
-                        //removePort(port);
+                        removePort(port);
                         providerService.linksVanished(new ConnectPoint(port.element().id(),
                                                                        port.number()));
                     }
                     break;
                 case PORT_REMOVED:
                     log.debug("Port removed {}", port);
-                    //removePort(port);
+                    removePort(port);
                     providerService.linksVanished(new ConnectPoint(port.element().id(),
                                                                    port.number()));
                     break;
                 case DEVICE_REMOVED:
                 case DEVICE_SUSPENDED:
                     log.debug("Device removed {}", deviceId);
-                    //removeDevice(deviceId);
+                    removeDevice(deviceId);
                     providerService.linksVanished(deviceId);
                     break;
                 case DEVICE_AVAILABILITY_CHANGED:
@@ -378,7 +407,7 @@
                         updateDevice(device).ifPresent(ld -> updatePorts(ld, deviceId));
                     } else {
                         log.debug("Device down {}", deviceId);
-                        //removeDevice(deviceId);
+                        removeDevice(deviceId);
                         providerService.linksVanished(deviceId);
                     }
                     break;
diff --git a/providers/netcfglinks/src/test/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProviderTest.java b/providers/netcfglinks/src/test/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProviderTest.java
new file mode 100644
index 0000000..70d2245
--- /dev/null
+++ b/providers/netcfglinks/src/test/java/org/onosproject/provider/netcfglinks/NetworkConfigLinksProviderTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2016 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.provider.netcfglinks;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ONOSLLDP;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.link.LinkProviderRegistryAdapter;
+import org.onosproject.net.link.LinkProviderServiceAdapter;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for the network config links provider.
+ */
+public class NetworkConfigLinksProviderTest {
+
+    private NetworkConfigLinksProvider provider;
+
+    private PacketProcessor testProcessor;
+    private LinkProviderServiceAdapter providerService;
+    private NetworkConfigListener configListener;
+    private final TestNetworkConfigRegistry configRegistry =
+            new TestNetworkConfigRegistry();
+
+    static Device dev1 = NetTestTools.device("sw1");
+    static Device dev2 = NetTestTools.device("sw2");
+    static Device dev3 = NetTestTools.device("sw3");
+    static PortNumber portNumber1 = PortNumber.portNumber(1);
+    static PortNumber portNumber2 = PortNumber.portNumber(2);
+    static PortNumber portNumber3 = PortNumber.portNumber(3);
+    static ConnectPoint src = new ConnectPoint(dev1.id(), portNumber2);
+    static ConnectPoint dst = new ConnectPoint(dev2.id(), portNumber2);
+
+    static DeviceListener deviceListener;
+
+    /**
+     * Test device manager. Returns a known set of devices and ports.
+     */
+    static class TestDeviceManager extends DeviceServiceAdapter {
+
+        @Override
+        public Iterable<Device> getAvailableDevices() {
+            return ImmutableList.of(dev1, dev2);
+        }
+
+        @Override
+        public List<Port> getPorts(DeviceId deviceId) {
+            return ImmutableList.of(new DefaultPort(dev1, portNumber1, true),
+                                    new DefaultPort(dev2, portNumber2, true));
+        }
+
+        @Override
+        public void addListener(DeviceListener listener) {
+            deviceListener = listener;
+        }
+    }
+
+    /**
+     * Test mastership service. All devices owned by the local node for testing.
+     */
+    static class TestMastershipService extends MastershipServiceAdapter {
+        @Override
+        public boolean isLocalMaster(DeviceId deviceId) {
+            return true;
+        }
+    }
+
+    /**
+     * Test packet context for generation of LLDP packets.
+     */
+    private class TestPacketContext implements PacketContext {
+
+        protected ConnectPoint src;
+        protected ConnectPoint dst;
+        protected boolean blocked = false;
+
+        public TestPacketContext(ConnectPoint src, ConnectPoint dst) {
+            this.src = src;
+            this.dst = dst;
+        }
+
+        @Override
+        public long time() {
+            return 0;
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            ONOSLLDP lldp = ONOSLLDP.onosLLDP(src.deviceId().toString(),
+                                              new ChassisId(),
+                                              (int) src.port().toLong());
+
+            Ethernet ethPacket = new Ethernet();
+            ethPacket.setEtherType(Ethernet.TYPE_LLDP);
+            ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
+            ethPacket.setPayload(lldp);
+            ethPacket.setPad(true);
+
+            ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
+
+            return new DefaultInboundPacket(dst, ethPacket,
+                                            ByteBuffer.wrap(ethPacket.serialize()));
+
+        }
+
+        @Override
+        public OutboundPacket outPacket() {
+            return null;
+        }
+
+        @Override
+        public TrafficTreatment.Builder treatmentBuilder() {
+            return null;
+        }
+
+        @Override
+        public void send() {
+
+        }
+
+        @Override
+        public boolean block() {
+            blocked = true;
+            return true;
+        }
+
+        @Override
+        public boolean isHandled() {
+            return blocked;
+        }
+
+    }
+
+    /**
+     * Test packet service for capturing the packet processor from the service
+     * under test.
+     */
+    private class TestPacketService extends PacketServiceAdapter {
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+            testProcessor = processor;
+        }
+    }
+
+    /**
+     * Test network config registry. Captures the network config listener from
+     * the service under test.
+     */
+    private final class TestNetworkConfigRegistry
+            extends NetworkConfigRegistryAdapter {
+
+
+        @Override
+        public void addListener(NetworkConfigListener listener) {
+            configListener = listener;
+        }
+    }
+
+    /**
+     * Sets up a network config links provider under test and the services
+     * required to run it.
+     */
+    @Before
+    public void setUp() {
+        provider = new NetworkConfigLinksProvider();
+
+        provider.coreService = new CoreServiceAdapter();
+        provider.packetService = new PacketServiceAdapter();
+        LinkProviderRegistryAdapter linkRegistry =
+                new LinkProviderRegistryAdapter();
+        provider.providerRegistry = linkRegistry;
+        provider.deviceService = new TestDeviceManager();
+        provider.masterService = new TestMastershipService();
+        provider.packetService = new TestPacketService();
+        provider.netCfgService = configRegistry;
+
+        provider.activate();
+
+        providerService = linkRegistry.registeredProvider();
+    }
+
+    /**
+     * Tears down the provider under test.
+     */
+    @After
+    public void tearDown() {
+        provider.deactivate();
+    }
+
+    /**
+     * Tests that a network config links provider object can be created.
+     * The actual creation is done in the setUp() method.
+     */
+    @Test
+    public void testCreation() {
+        assertThat(provider, notNullValue());
+        assertThat(provider.configuredLinks, empty());
+    }
+
+    /**
+     * Tests loading of devices from the device manager.
+     */
+    @Test
+    public void testDeviceLoad() {
+        assertThat(provider, notNullValue());
+        assertThat(provider.discoverers.entrySet(), hasSize(2));
+    }
+
+    /**
+     * Tests discovery of a link that is not expected in the configuration.
+     */
+    @Test
+    public void testNotConfiguredLink() {
+        PacketContext pktCtx = new TestPacketContext(src, dst);
+
+        testProcessor.process(pktCtx);
+
+        assertThat(providerService.discoveredLinks().entrySet(), hasSize(1));
+        DeviceId destination = providerService.discoveredLinks().get(dev1.id());
+        assertThat(destination, notNullValue());
+        LinkKey key = LinkKey.linkKey(src, dst);
+        LinkDescription linkDescription = providerService
+                .discoveredLinkDescriptions().get(key);
+        assertThat(linkDescription, notNullValue());
+        assertThat(linkDescription.isExpected(), is(false));
+    }
+
+    /**
+     * Tests discovery of an expected link.
+     */
+    @Test
+    public void testConfiguredLink() {
+        LinkKey key = LinkKey.linkKey(src, dst);
+        configListener.event(new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED,
+                                                    key,
+                                                    BasicLinkConfig.class));
+
+        PacketContext pktCtx = new TestPacketContext(src, dst);
+
+        testProcessor.process(pktCtx);
+
+        assertThat(providerService.discoveredLinks().entrySet(), hasSize(1));
+        DeviceId destination = providerService.discoveredLinks().get(dev1.id());
+        assertThat(destination, notNullValue());
+        LinkDescription linkDescription = providerService
+                .discoveredLinkDescriptions().get(key);
+        assertThat(linkDescription, notNullValue());
+        assertThat(linkDescription.isExpected(), is(true));
+    }
+
+    /**
+     * Tests removal of a link from the configuration.
+     */
+    @Test
+    public void testRemoveLink() {
+        LinkKey key = LinkKey.linkKey(src, dst);
+        configListener.event(new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED,
+                                                    key,
+                                                    BasicLinkConfig.class));
+
+        assertThat(provider.configuredLinks, hasSize(1));
+
+        configListener.event(new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_REMOVED,
+                                                    key,
+                                                    BasicLinkConfig.class));
+        assertThat(provider.configuredLinks, hasSize(0));
+    }
+
+    /**
+     * Tests adding a new device via an event.
+     */
+    @Test
+    public void testAddDevice() {
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, dev3));
+        assertThat(provider.discoverers.entrySet(), hasSize(3));
+    }
+
+    /**
+     * Tests adding a new port via an event.
+     */
+    @Test
+    public void testAddPort() {
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.PORT_ADDED, dev3,
+                                             new DefaultPort(dev3, portNumber3, true)));
+        assertThat(provider.discoverers.entrySet(), hasSize(3));
+    }
+
+    /**
+     * Tests removing a device via an event.
+     */
+    @Test
+    public void testRemoveDevice() {
+        assertThat(provider.discoverers.entrySet(), hasSize(2));
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, dev3));
+        assertThat(provider.discoverers.entrySet(), hasSize(3));
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.DEVICE_REMOVED, dev3));
+        assertThat(provider.discoverers.entrySet(), hasSize(2));
+    }
+
+    /**
+     * Tests removing a port via an event.
+     */
+    @Test
+    public void testRemovePort() {
+        assertThat(provider.discoverers.entrySet(), hasSize(2));
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.PORT_ADDED, dev3,
+                                             new DefaultPort(dev3, portNumber3, true)));
+        assertThat(provider.discoverers.entrySet(), hasSize(3));
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.PORT_REMOVED, dev3,
+                                             new DefaultPort(dev3, portNumber3, true)));
+        assertThat(provider.discoverers.entrySet(), hasSize(3));
+    }
+
+    /**
+     * Tests changing device availability via an event.
+     */
+    @Test
+    public void testDeviceAvailabilityChanged() {
+        assertThat(providerService.vanishedDpid(), hasSize(0));
+
+        deviceListener.event(
+                new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, dev3));
+        assertThat(providerService.vanishedDpid(), hasSize(0));
+
+        deviceListener.event(
+                new DeviceEvent(DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED, dev3));
+        assertThat(providerService.vanishedDpid(), hasSize(1));
+    }
+}