Added unit tests for OpenstackNode

Change-Id: I4e852145c945cd11586b39e06972fcba23942660
diff --git a/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandlerTest.java b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandlerTest.java
new file mode 100644
index 0000000..aadec13
--- /dev/null
+++ b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandlerTest.java
@@ -0,0 +1,1062 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.LeadershipServiceAdapter;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.BridgeConfig;
+import org.onosproject.net.behaviour.BridgeDescription;
+import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultBridgeDescription;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.behaviour.InterfaceConfig;
+import org.onosproject.net.behaviour.PatchDescription;
+import org.onosproject.net.behaviour.TunnelDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceInterfaceDescription;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.easymock.EasyMock.*;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.net.Device.Type.CONTROLLER;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.openstacknode.api.Constants.*;
+import static org.onosproject.openstacknode.api.NodeState.*;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Unit test for DefaultOpenstackNodeHandler.
+ */
+public class DefaultOpenstackNodeHandlerTest {
+
+    private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+    private static final String ERR_STATE_NOT_MATCH = "Node state did not match";
+    private static final NodeId LOCAL_NODE_ID = new NodeId("local");
+    private static final ControllerNode LOCAL_CTRL =
+            new DefaultControllerNode(LOCAL_NODE_ID, IpAddress.valueOf("127.0.0.1"));
+
+    private static final BridgeDescription ROUT_BRIDGE = DefaultBridgeDescription.builder()
+            .name(ROUTER_BRIDGE)
+            .failMode(BridgeDescription.FailMode.SECURE)
+            .disableInBand()
+            .build();
+
+    private static final PortDescription PATCH_ROUT = new DefaultPortDescription(
+            PortNumber.portNumber(1),
+            true,
+            DefaultAnnotations.builder()
+                    .set(PORT_NAME, PATCH_ROUT_BRIDGE)
+                    .build()
+    );
+
+    private static final String COMPUTE_1_HOSTNAME = "compute_1";
+    private static final String COMPUTE_2_HOSTNAME = "compute_2";
+    private static final String COMPUTE_3_HOSTNAME = "compute_3";
+    private static final String COMPUTE_4_HOSTNAME = "compute_4";
+    private static final String GATEWAY_1_HOSTNAME = "gateway_1";
+    private static final String GATEWAY_2_HOSTNAME = "gateway_2";
+    private static final String GATEWAY_3_HOSTNAME = "gateway_3";
+    private static final String GATEWAY_4_HOSTNAME = "gateway_4";
+
+    private static final IpAddress COMPUTE_1_IP = IpAddress.valueOf("10.100.0.1");
+    private static final IpAddress COMPUTE_2_IP = IpAddress.valueOf("10.100.0.2");
+    private static final IpAddress COMPUTE_3_IP = IpAddress.valueOf("10.100.0.3");
+    private static final IpAddress COMPUTE_4_IP = IpAddress.valueOf("10.100.0.4");
+    private static final IpAddress GATEWAY_1_IP = IpAddress.valueOf("10.100.0.5");
+    private static final IpAddress GATEWAY_2_IP = IpAddress.valueOf("10.100.0.6");
+    private static final IpAddress GATEWAY_3_IP = IpAddress.valueOf("10.100.0.7");
+    private static final IpAddress GATEWAY_4_IP = IpAddress.valueOf("10.100.0.8");
+
+    private static final Device COMPUTE_1_INTG_DEVICE = createOpenFlowDevice(1, INTEGRATION_BRIDGE);
+    private static final Device COMPUTE_2_INTG_DEVICE = createOpenFlowDevice(2, INTEGRATION_BRIDGE);
+    private static final Device COMPUTE_3_INTG_DEVICE = createOpenFlowDevice(3, INTEGRATION_BRIDGE);
+    private static final Device COMPUTE_4_INTG_DEVICE = createOpenFlowDevice(4, INTEGRATION_BRIDGE);
+    private static final Device GATEWAY_1_INTG_DEVICE = createOpenFlowDevice(5, INTEGRATION_BRIDGE);
+    private static final Device GATEWAY_1_ROUT_DEVICE = createOpenFlowDevice(6, ROUTER_BRIDGE);
+    private static final Device GATEWAY_2_INTG_DEVICE = createOpenFlowDevice(7, INTEGRATION_BRIDGE);
+    private static final Device GATEWAY_2_ROUT_DEVICE = createOpenFlowDevice(8, ROUTER_BRIDGE);
+    private static final Device GATEWAY_3_INTG_DEVICE = createOpenFlowDevice(9, INTEGRATION_BRIDGE);
+    private static final Device GATEWAY_3_ROUT_DEVICE = createOpenFlowDevice(10, ROUTER_BRIDGE);
+    private static final Device GATEWAY_4_INTG_DEVICE = createOpenFlowDevice(11, INTEGRATION_BRIDGE);
+    private static final Device GATEWAY_4_ROUT_DEVICE = createOpenFlowDevice(12, ROUTER_BRIDGE);
+
+    private static final Device COMPUTE_1_OVSDB_DEVICE = createOvsdbDevice(COMPUTE_1_IP);
+    private static final Device COMPUTE_2_OVSDB_DEVICE = createOvsdbDevice(COMPUTE_2_IP);
+    private static final Device COMPUTE_3_OVSDB_DEVICE = createOvsdbDevice(COMPUTE_3_IP);
+    private static final Device COMPUTE_4_OVSDB_DEVICE = createOvsdbDevice(COMPUTE_4_IP);
+    private static final Device GATEWAY_1_OVSDB_DEVICE = createOvsdbDevice(GATEWAY_1_IP);
+    private static final Device GATEWAY_2_OVSDB_DEVICE = createOvsdbDevice(GATEWAY_2_IP);
+
+    private static final OpenstackNode COMPUTE_1 = createNode(
+            COMPUTE_1_HOSTNAME,
+            COMPUTE,
+            COMPUTE_1_INTG_DEVICE,
+            COMPUTE_1_IP,
+            INIT
+    );
+
+    private static final OpenstackNode COMPUTE_2 = createNode(
+            COMPUTE_2_HOSTNAME,
+            COMPUTE,
+            COMPUTE_2_INTG_DEVICE,
+            COMPUTE_2_IP,
+            DEVICE_CREATED
+    );
+
+    private static final OpenstackNode COMPUTE_3 = createNode(
+            COMPUTE_3_HOSTNAME,
+            COMPUTE,
+            COMPUTE_3_INTG_DEVICE,
+            COMPUTE_3_IP,
+            PORT_CREATED
+    );
+
+    private static final OpenstackNode COMPUTE_4 = createNode(
+            COMPUTE_4_HOSTNAME,
+            COMPUTE,
+            COMPUTE_4_INTG_DEVICE,
+            COMPUTE_4_IP,
+            COMPLETE
+    );
+
+    private static final OpenstackNode GATEWAY_1 = createNode(
+            GATEWAY_1_HOSTNAME,
+            GATEWAY,
+            GATEWAY_1_INTG_DEVICE,
+            GATEWAY_1_ROUT_DEVICE,
+            GATEWAY_1_IP,
+            INIT
+    );
+
+    private static final OpenstackNode GATEWAY_2 = createNode(
+            GATEWAY_2_HOSTNAME,
+            GATEWAY,
+            GATEWAY_2_INTG_DEVICE,
+            GATEWAY_2_ROUT_DEVICE,
+            GATEWAY_2_IP,
+            DEVICE_CREATED
+    );
+
+    private static final OpenstackNode GATEWAY_3 = createNode(
+            GATEWAY_3_HOSTNAME,
+            GATEWAY,
+            GATEWAY_3_INTG_DEVICE,
+            GATEWAY_3_ROUT_DEVICE,
+            GATEWAY_3_IP,
+            PORT_CREATED
+    );
+
+    private static final OpenstackNode GATEWAY_4 = createNode(
+            GATEWAY_4_HOSTNAME,
+            GATEWAY,
+            GATEWAY_4_INTG_DEVICE,
+            GATEWAY_4_ROUT_DEVICE,
+            GATEWAY_4_IP,
+            COMPLETE
+    );
+
+    private static final TestDeviceService TEST_DEVICE_SERVICE = new TestDeviceService();
+
+    private TestOpenstackNodeManager testNodeManager;
+    private DefaultOpenstackNodeHandler target;
+
+    @Before
+    public void setUp() throws Exception {
+        DeviceAdminService mockDeviceAdminService = createMock(DeviceAdminService.class);
+        mockDeviceAdminService.removeDevice(anyObject());
+        replay(mockDeviceAdminService);
+
+        OvsdbClientService mockOvsdbClient = createMock(OvsdbClientService.class);
+        expect(mockOvsdbClient.isConnected())
+                .andReturn(true)
+                .anyTimes();
+        replay(mockOvsdbClient);
+
+        OvsdbController mockOvsdbController = createMock(OvsdbController.class);
+        expect(mockOvsdbController.getOvsdbClient(anyObject()))
+                .andReturn(mockOvsdbClient)
+                .anyTimes();
+        replay(mockOvsdbController);
+
+        testNodeManager = new TestOpenstackNodeManager();
+        target = new DefaultOpenstackNodeHandler();
+
+        target.coreService = new TestCoreService();
+        target.leadershipService = new TestLeadershipService();
+        target.clusterService = new TestClusterService();
+        target.deviceService = TEST_DEVICE_SERVICE;
+        target.deviceAdminService = mockDeviceAdminService;
+        target.ovsdbController = mockOvsdbController;
+        target.groupService = new TestGroupService();
+        target.osNodeService = testNodeManager;
+        target.osNodeAdminService = testNodeManager;
+        target.componentConfigService = new TestComponentConfigService();
+        TestUtils.setField(target, "eventExecutor", MoreExecutors.newDirectExecutorService());
+        target.activate();
+    }
+
+    @After
+    public void tearDown() {
+        TEST_DEVICE_SERVICE.clear();
+        target.deactivate();
+        target = null;
+        testNodeManager = null;
+    }
+
+    /**
+     * Checks if the compute node state changes from INIT to DEVICE_CREATED
+     * after processing INIT state.
+     */
+    @Test
+    public void testComputeNodeProcessNodeInitState() {
+        testNodeManager.createNode(COMPUTE_1);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_1_OVSDB_DEVICE.id(), COMPUTE_1_OVSDB_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, INIT,
+                testNodeManager.node(COMPUTE_1_HOSTNAME).state());
+        target.processInitState(COMPUTE_1);
+        assertEquals(ERR_STATE_NOT_MATCH, DEVICE_CREATED,
+                testNodeManager.node(COMPUTE_1_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the gateway node state changes from INIT to DEVICE_CREATED
+     * after processing INIT state.
+     */
+    @Test
+    public void testGatewayNodeProcessNodeInitState() {
+        testNodeManager.createNode(GATEWAY_1);
+        TEST_DEVICE_SERVICE.devMap.put(GATEWAY_1_OVSDB_DEVICE.id(), GATEWAY_1_OVSDB_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, INIT,
+                testNodeManager.node(GATEWAY_1_HOSTNAME).state());
+        target.processInitState(GATEWAY_1);
+        assertEquals(ERR_STATE_NOT_MATCH, DEVICE_CREATED,
+                testNodeManager.node(GATEWAY_1_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the compute node state changes from DEVICE_CREATED to
+     * PORT_CREATED after processing DEVICE_CREATED state.
+     */
+    @Test
+    public void testComputeNodeProcessDeviceCreatedState() {
+        testNodeManager.createNode(COMPUTE_2);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_2_OVSDB_DEVICE.id(), COMPUTE_2_OVSDB_DEVICE);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_2_INTG_DEVICE.id(), COMPUTE_2_INTG_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, DEVICE_CREATED,
+                testNodeManager.node(COMPUTE_2_HOSTNAME).state());
+        target.processDeviceCreatedState(COMPUTE_2);
+        assertEquals(ERR_STATE_NOT_MATCH, PORT_CREATED,
+                testNodeManager.node(COMPUTE_2_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the gateway node state changes from DEVICE_CREATED to
+     * PORT_CREATED after processing DEVICE_CREATED state.
+     */
+    @Test
+    public void testGatewayNodeProcessDeviceCreatedState() {
+        testNodeManager.createNode(GATEWAY_2);
+        TEST_DEVICE_SERVICE.devMap.put(GATEWAY_2_OVSDB_DEVICE.id(), GATEWAY_2_OVSDB_DEVICE);
+        TEST_DEVICE_SERVICE.devMap.put(GATEWAY_2_INTG_DEVICE.id(), GATEWAY_2_INTG_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, DEVICE_CREATED,
+                testNodeManager.node(GATEWAY_2_HOSTNAME).state());
+        target.processDeviceCreatedState(GATEWAY_2);
+        assertEquals(ERR_STATE_NOT_MATCH, PORT_CREATED,
+                testNodeManager.node(GATEWAY_2_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the compute node state changes from PORT_CREATED to
+     * COMPLETE after processing PORT_CREATED state.
+     */
+    @Test
+    public void testComputeNodeProcessPortCreatedState() {
+        testNodeManager.createNode(COMPUTE_3);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_3_OVSDB_DEVICE.id(), COMPUTE_3_OVSDB_DEVICE);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_3_INTG_DEVICE.id(), COMPUTE_3_INTG_DEVICE);
+        TEST_DEVICE_SERVICE.portList.add(createPort(COMPUTE_3_INTG_DEVICE, DEFAULT_TUNNEL));
+
+        testNodeManager.createNode(GATEWAY_4);
+        TEST_DEVICE_SERVICE.devMap.put(GATEWAY_4_INTG_DEVICE.id(), GATEWAY_4_INTG_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, PORT_CREATED,
+                testNodeManager.node(COMPUTE_3_HOSTNAME).state());
+        target.processPortCreatedState(COMPUTE_3);
+        assertEquals(ERR_STATE_NOT_MATCH, COMPLETE,
+                testNodeManager.node(COMPUTE_3_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the gateway node state changes from PORT_CREATED to
+     * COMPLETE after processing PORT_CREATED state.
+     */
+    @Test
+    public void testGatewayNodeProcessPortCreatedState() {
+        testNodeManager.createNode(COMPUTE_4);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_4_OVSDB_DEVICE.id(), COMPUTE_4_OVSDB_DEVICE);
+        TEST_DEVICE_SERVICE.devMap.put(COMPUTE_4_INTG_DEVICE.id(), COMPUTE_4_INTG_DEVICE);
+        TEST_DEVICE_SERVICE.portList.add(createPort(COMPUTE_4_INTG_DEVICE, DEFAULT_TUNNEL));
+
+        testNodeManager.createNode(GATEWAY_3);
+        TEST_DEVICE_SERVICE.devMap.put(GATEWAY_3_INTG_DEVICE.id(), GATEWAY_4_INTG_DEVICE);
+
+        assertEquals(ERR_STATE_NOT_MATCH, PORT_CREATED,
+                testNodeManager.node(GATEWAY_3_HOSTNAME).state());
+        target.processPortCreatedState(GATEWAY_3);
+        assertEquals(ERR_STATE_NOT_MATCH, COMPLETE,
+                testNodeManager.node(GATEWAY_3_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the compute node state changes from COMPLETE to INCOMPLETE
+     * when integration bridge is disconnected.
+     */
+    @Test
+    public void testBackToIncompleteWhenBrIntDisconnected() {
+        testNodeManager.createNode(COMPUTE_4);
+
+        assertEquals(ERR_STATE_NOT_MATCH, COMPLETE,
+                testNodeManager.node(COMPUTE_4_HOSTNAME).state());
+        TEST_DEVICE_SERVICE.removeDevice(COMPUTE_4_INTG_DEVICE);
+        assertEquals(ERR_STATE_NOT_MATCH, INCOMPLETE,
+                testNodeManager.node(COMPUTE_4_HOSTNAME).state());
+    }
+
+    /**
+     * Checks if the compute node state changes from COMPLETE to INCOMPLETE
+     * when vxlan port is removed from integration bridge.
+     */
+    @Test
+    public void testBackToIncompleteWhenVxlanRemoved() {
+        testNodeManager.createNode(COMPUTE_4);
+
+        assertEquals(ERR_STATE_NOT_MATCH, COMPLETE,
+                testNodeManager.node(COMPUTE_4_HOSTNAME).state());
+        TEST_DEVICE_SERVICE.removePort(COMPUTE_4_INTG_DEVICE, createPort(
+                COMPUTE_4_INTG_DEVICE, DEFAULT_TUNNEL));
+        assertEquals(ERR_STATE_NOT_MATCH, INCOMPLETE,
+                testNodeManager.node(COMPUTE_4_HOSTNAME).state());
+
+    }
+
+    private static Device createOvsdbDevice(IpAddress ovsdbIp) {
+        return new TestDevice(new ProviderId("of", "foo"),
+                DeviceId.deviceId("ovsdb:" + ovsdbIp.toString()),
+                CONTROLLER,
+                "manufacturer",
+                "hwVersion",
+                "swVersion",
+                "serialNumber",
+                new ChassisId(1));
+    }
+
+    private static Device createOpenFlowDevice(long devIdNum, String type) {
+        return new TestDevice(new ProviderId("of", "foo"),
+                DeviceId.deviceId(String.format("of:%016d", devIdNum)),
+                SWITCH,
+                type,
+                "hwVersion",
+                "swVersion",
+                "serialNumber",
+                new ChassisId(1));
+    }
+
+    private static Port createPort(Device device, String portName) {
+        return new DefaultPort(device,
+                PortNumber.portNumber(1),
+                true,
+                DefaultAnnotations.builder().set(PORT_NAME, portName).build());
+    }
+
+    private static OpenstackNode createNode(String hostname,
+                                            OpenstackNode.NodeType type,
+                                            Device intgBridge,
+                                            IpAddress ipAddr,
+                                            NodeState state) {
+        return new TestOpenstackNode(
+                hostname,
+                type,
+                intgBridge.id(),
+                null,
+                ipAddr,
+                ipAddr,
+                null, state);
+    }
+
+    private static OpenstackNode createNode(String hostname,
+                                            OpenstackNode.NodeType type,
+                                            Device intgBridge,
+                                            Device routerBridge,
+                                            IpAddress ipAddr,
+                                            NodeState state) {
+        return new TestOpenstackNode(
+                hostname,
+                type,
+                intgBridge.id(),
+                routerBridge.id(),
+                ipAddr,
+                ipAddr,
+                null, state);
+    }
+
+    private static final class TestDevice extends DefaultDevice {
+        private TestDevice(ProviderId providerId,
+                           DeviceId id,
+                           Type type,
+                           String manufacturer,
+                           String hwVersion,
+                           String swVersion,
+                           String serialNumber,
+                           ChassisId chassisId,
+                           Annotations... annotations) {
+            super(providerId,
+                    id,
+                    type,
+                    manufacturer,
+                    hwVersion,
+                    swVersion,
+                    serialNumber,
+                    chassisId,
+                    annotations);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <B extends Behaviour> B as(Class<B> projectionClass) {
+            if (projectionClass.equals(BridgeConfig.class)) {
+                return (B) new TestBridgeConfig();
+            } else if (projectionClass.equals(InterfaceConfig.class)) {
+                return (B) new TestInterfaceConfig();
+            } else if (projectionClass.equals(ExtensionTreatmentResolver.class)) {
+                ExtensionTreatmentResolver treatmentResolver = createMock(ExtensionTreatmentResolver.class);
+                expect(treatmentResolver.getExtensionInstruction(anyObject()))
+                        .andReturn(new TestExtensionTreatment())
+                        .anyTimes();
+                replay(treatmentResolver);
+                return (B) treatmentResolver;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public <B extends Behaviour> boolean is(Class<B> projectionClass) {
+            return true;
+        }
+    }
+
+    private static final class TestOpenstackNode extends DefaultOpenstackNode {
+        private TestOpenstackNode(String hostname,
+                                  NodeType type,
+                                  DeviceId intgBridge,
+                                  DeviceId routerBridge,
+                                  IpAddress managementIp,
+                                  IpAddress dataIp,
+                                  String vlanIntf,
+                                  NodeState state) {
+            super(hostname,
+                    type,
+                    intgBridge,
+                    routerBridge,
+                    managementIp,
+                    dataIp,
+                    vlanIntf,
+                    state);
+        }
+
+        @Override
+        public PortNumber tunnelPortNum() {
+            return PortNumber.portNumber(1);
+        }
+
+        @Override
+        public PortNumber vlanPortNum() {
+            return PortNumber.portNumber(1);
+        }
+
+        @Override
+        public PortNumber patchPortNum() {
+            return PortNumber.portNumber(1);
+        }
+
+        @Override
+        public MacAddress vlanPortMac() {
+            return MacAddress.NONE;
+        }
+    }
+
+    private static class TestOpenstackNodeManager implements OpenstackNodeService, OpenstackNodeAdminService {
+        Map<String, OpenstackNode> osNodeMap = Maps.newHashMap();
+        List<OpenstackNodeListener> listeners = Lists.newArrayList();
+
+        @Override
+        public Set<OpenstackNode> nodes() {
+            return ImmutableSet.copyOf(osNodeMap.values());
+        }
+
+        @Override
+        public Set<OpenstackNode> nodes(OpenstackNode.NodeType type) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.type() == type)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<OpenstackNode> completeNodes() {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.state() == COMPLETE)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<OpenstackNode> completeNodes(OpenstackNode.NodeType type) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.type() == type && osNode.state() == COMPLETE)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public OpenstackNode node(String hostname) {
+            return osNodeMap.get(hostname);
+        }
+
+        @Override
+        public OpenstackNode node(DeviceId deviceId) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> Objects.equals(osNode.intgBridge(), deviceId) ||
+                            Objects.equals(osNode.ovsdb(), deviceId) ||
+                            Objects.equals(osNode.routerBridge(), deviceId))
+                    .findFirst().orElse(null);
+        }
+
+        @Override
+        public void addListener(OpenstackNodeListener listener) {
+            listeners.add(listener);
+        }
+
+        @Override
+        public void removeListener(OpenstackNodeListener listener) {
+            listeners.remove(listener);
+        }
+
+        @Override
+        public void createNode(OpenstackNode osNode) {
+            osNodeMap.put(osNode.hostname(), osNode);
+        }
+
+        @Override
+        public void updateNode(OpenstackNode osNode) {
+            osNodeMap.put(osNode.hostname(), osNode);
+        }
+
+        @Override
+        public OpenstackNode removeNode(String hostname) {
+            return null;
+        }
+    }
+
+    private static class TestDeviceService extends DeviceServiceAdapter {
+        Map<DeviceId, Device> devMap = Maps.newHashMap();
+        List<Port> portList = Lists.newArrayList();
+        List<DeviceListener> listeners = Lists.newArrayList();
+
+        @Override
+        public void addListener(DeviceListener listener) {
+            listeners.add(listener);
+        }
+
+        @Override
+        public void removeListener(DeviceListener listener) {
+            listeners.remove(listener);
+        }
+
+        @Override
+        public Device getDevice(DeviceId deviceId) {
+            return devMap.get(deviceId);
+        }
+
+        @Override
+        public List<Port> getPorts(DeviceId deviceId) {
+            return this.portList.stream()
+                    .filter(p -> p.element().id().equals(deviceId))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public boolean isAvailable(DeviceId deviceId) {
+            return devMap.containsKey(deviceId);
+        }
+
+        void addDevice(Device device) {
+            devMap.put(device.id(), device);
+            DeviceEvent event = new DeviceEvent(DEVICE_ADDED, device);
+            listeners.stream().filter(l -> l.isRelevant(event)).forEach(l -> l.event(event));
+        }
+
+        void removeDevice(Device device) {
+            devMap.remove(device.id());
+            DeviceEvent event = new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device);
+            listeners.stream().filter(l -> l.isRelevant(event)).forEach(l -> l.event(event));
+        }
+
+        void addPort(Device device, Port port) {
+            portList.add(port);
+            DeviceEvent event = new DeviceEvent(PORT_ADDED, device, port);
+            listeners.stream().filter(l -> l.isRelevant(event)).forEach(l -> l.event(event));
+        }
+
+        void removePort(Device device, Port port) {
+            portList.remove(port);
+            DeviceEvent event = new DeviceEvent(PORT_REMOVED, device, port);
+            listeners.stream().filter(l -> l.isRelevant(event)).forEach(l -> l.event(event));
+        }
+
+        void clear() {
+            this.listeners.clear();
+            this.devMap.clear();
+            this.portList.clear();
+        }
+    }
+
+    private static class TestBridgeConfig implements BridgeConfig {
+
+        @Override
+        public DriverData data() {
+            return null;
+        }
+
+        @Override
+        public void setData(DriverData data) {
+
+        }
+
+        @Override
+        public DriverHandler handler() {
+            return null;
+        }
+
+        @Override
+        public void setHandler(DriverHandler handler) {
+
+        }
+
+        @Override
+        public void addBridge(BridgeName bridgeName) {
+
+        }
+
+        @Override
+        public void addBridge(BridgeName bridgeName, String dpid, String exPortName) {
+
+        }
+
+        @Override
+        public boolean addBridge(BridgeName bridgeName, String dpid, List<ControllerInfo> controllers) {
+            return false;
+        }
+
+        @Override
+        public boolean addBridge(BridgeDescription bridge) {
+            TEST_DEVICE_SERVICE.addDevice(new DefaultDevice(new ProviderId("of", "foo"),
+                    DeviceId.deviceId("of:" + bridge.datapathId().get()),
+                    SWITCH,
+                    bridge.name(),
+                    "hwVersion",
+                    "swVersion",
+                    "serialNumber",
+                    new ChassisId(1)));
+            return true;
+        }
+
+        @Override
+        public void deleteBridge(BridgeName bridgeName) {
+
+        }
+
+        @Override
+        public Collection<BridgeDescription> getBridges() {
+            return ImmutableSet.of(ROUT_BRIDGE);
+        }
+
+        @Override
+        public void addPort(BridgeName bridgeName, String portName) {
+
+        }
+
+        @Override
+        public void deletePort(BridgeName bridgeName, String portName) {
+
+        }
+
+        @Override
+        public Collection<PortDescription> getPorts() {
+            return ImmutableSet.of(PATCH_ROUT);
+        }
+
+        @Override
+        public Set<PortNumber> getPortNumbers() {
+            return null;
+        }
+
+        @Override
+        public List<PortNumber> getLocalPorts(Iterable<String> ifaceIds) {
+            return null;
+        }
+    }
+
+    private static class TestInterfaceConfig implements InterfaceConfig {
+
+        @Override
+        public DriverData data() {
+            return null;
+        }
+
+        @Override
+        public void setData(DriverData data) {
+
+        }
+
+        @Override
+        public DriverHandler handler() {
+            return null;
+        }
+
+        @Override
+        public void setHandler(DriverHandler handler) {
+
+        }
+
+        @Override
+        public boolean addAccessInterface(DeviceId deviceId, String intf, VlanId vlanId) {
+            return false;
+        }
+
+        @Override
+        public boolean addAccessMode(String intf, VlanId vlanId) {
+            return false;
+        }
+
+        @Override
+        public boolean removeAccessInterface(DeviceId deviceId, String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean removeAccessMode(String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean addTrunkInterface(DeviceId deviceId, String intf, List<VlanId> vlanIds) {
+            return false;
+        }
+
+        @Override
+        public boolean addTrunkMode(String intf, List<VlanId> vlanIds) {
+            return false;
+        }
+
+        @Override
+        public boolean removeTrunkInterface(DeviceId deviceId, String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean removeTrunkMode(String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean addRateLimit(String intf, short limit) {
+            return false;
+        }
+
+        @Override
+        public boolean removeRateLimit(String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean addTunnelMode(String intf, TunnelDescription tunnelDesc) {
+            TEST_DEVICE_SERVICE.devMap.values().stream()
+                    .filter(device -> device.type() == SWITCH &&
+                            device.manufacturer().equals(INTEGRATION_BRIDGE))
+                    .forEach(device -> {
+                        TEST_DEVICE_SERVICE.addPort(device, createPort(device, intf));
+                    });
+            return true;
+        }
+
+        @Override
+        public boolean removeTunnelMode(String intf) {
+            return false;
+        }
+
+        @Override
+        public boolean addPatchMode(String ifaceName, PatchDescription patchInterface) {
+            if (ifaceName.equals(PATCH_INTG_BRIDGE)) {
+                TEST_DEVICE_SERVICE.devMap.values().stream()
+                        .filter(device -> device.type() == SWITCH &&
+                                device.manufacturer().equals(INTEGRATION_BRIDGE))
+                        .forEach(device -> {
+                            TEST_DEVICE_SERVICE.addPort(device, createPort(device, ifaceName));
+                        });
+            } else if (ifaceName.equals(PATCH_ROUT_BRIDGE)) {
+                TEST_DEVICE_SERVICE.devMap.values().stream()
+                        .filter(device -> device.type() == SWITCH &&
+                                device.manufacturer().equals(ROUTER_BRIDGE))
+                        .forEach(device -> {
+                            TEST_DEVICE_SERVICE.addPort(device, createPort(device, ifaceName));
+                        });
+            }
+            return true;
+        }
+
+        @Override
+        public boolean removePatchMode(String ifaceName) {
+            return false;
+        }
+
+        @Override
+        public List<DeviceInterfaceDescription> getInterfaces(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public List<DeviceInterfaceDescription> getInterfaces() {
+            return null;
+        }
+    }
+
+    private static class TestGroupService implements GroupService {
+        Map<GroupKey, Group> groupMap = Maps.newHashMap();
+        Map<GroupKey, GroupBuckets> groupBucketsMap = Maps.newHashMap();
+        List<GroupListener> listeners = Lists.newArrayList();
+
+        @Override
+        public void addListener(GroupListener listener) {
+            listeners.add(listener);
+        }
+
+        @Override
+        public void removeListener(GroupListener listener) {
+            listeners.remove(listener);
+        }
+
+        @Override
+        public void addGroup(GroupDescription groupDesc) {
+            DefaultGroup group = new DefaultGroup(GroupId.valueOf(groupDesc.givenGroupId()), groupDesc);
+            group.setState(Group.GroupState.ADDED);
+            groupMap.put(groupDesc.appCookie(), group);
+            groupBucketsMap.put(groupDesc.appCookie(), groupDesc.buckets());
+
+            GroupEvent event = new GroupEvent(GroupEvent.Type.GROUP_ADDED, group);
+            listeners.stream().filter(listener -> listener.isRelevant(event))
+                    .forEach(listener -> listener.event(event));
+        }
+
+        @Override
+        public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+            return groupMap.get(appCookie);
+        }
+
+        @Override
+        public void addBucketsToGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets,
+                                      GroupKey newCookie, ApplicationId appId) {
+
+        }
+
+        @Override
+        public void removeBucketsFromGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets,
+                                           GroupKey newCookie, ApplicationId appId) {
+
+        }
+
+        @Override
+        public void purgeGroupEntries(DeviceId deviceId) {
+
+        }
+
+        @Override
+        public void removeGroup(DeviceId deviceId, GroupKey appCookie, ApplicationId appId) {
+
+        }
+
+        @Override
+        public Iterable<Group> getGroups(DeviceId deviceId, ApplicationId appId) {
+            return null;
+        }
+
+        @Override
+        public Iterable<Group> getGroups(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public void setBucketsForGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets,
+                                       GroupKey newCookie, ApplicationId appId) {
+            groupBucketsMap.put(newCookie, buckets);
+            GroupEvent event = new GroupEvent(GroupEvent.Type.GROUP_UPDATED, groupMap.get(newCookie));
+            listeners.stream().filter(listener -> listener.isRelevant(event))
+                    .forEach(listener -> listener.event(event));
+        }
+
+    }
+
+    private static class TestExtensionTreatment implements ExtensionTreatment {
+        Ip4Address tunnelDst;
+
+        @Override
+        public ExtensionTreatmentType type() {
+            return null;
+        }
+
+        @Override
+        public <T> void setPropertyValue(String key, T value) throws ExtensionPropertyException {
+            tunnelDst = (Ip4Address) value;
+        }
+
+        @Override
+        public <T> T getPropertyValue(String key) throws ExtensionPropertyException {
+            return null;
+        }
+
+        @Override
+        public List<String> getProperties() {
+            return null;
+        }
+
+        @Override
+        public byte[] serialize() {
+            return new byte[0];
+        }
+
+        @Override
+        public void deserialize(byte[] data) {
+
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            TestExtensionTreatment that = (TestExtensionTreatment) obj;
+            return Objects.equals(tunnelDst, that.tunnelDst);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(tunnelDst);
+        }
+    }
+
+    private static class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId getAppId(String name) {
+            return TEST_APP_ID;
+        }
+    }
+
+    private static class TestLeadershipService extends LeadershipServiceAdapter {
+
+        @Override
+        public NodeId getLeader(String path) {
+            return LOCAL_NODE_ID;
+        }
+    }
+
+    private static class TestClusterService extends ClusterServiceAdapter {
+
+        @Override
+        public ControllerNode getLocalNode() {
+            return LOCAL_CTRL;
+        }
+    }
+
+    private class TestComponentConfigService extends ComponentConfigAdapter {
+
+    }
+}
diff --git a/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeTest.java b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeTest.java
new file mode 100644
index 0000000..fdc7028
--- /dev/null
+++ b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.Device;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+
+/**
+ * Unit tests for DefaultOpenstackNode.
+ */
+public class DefaultOpenstackNodeTest extends OpenstackNodeTest {
+
+    private static final IpAddress TEST_IP = IpAddress.valueOf("10.100.0.3");
+
+    private static final String HOSTNAME_1 = "hostname_1";
+    private static final String HOSTNAME_2 = "hostname_2";
+    private static final Device DEVICE_1 = createDevice(1);
+    private static final Device DEVICE_2 = createDevice(2);
+    private static final OpenstackNode OS_NODE_1 = createNode(
+            HOSTNAME_1,
+            OpenstackNode.NodeType.COMPUTE,
+            DEVICE_1,
+            TEST_IP,
+            NodeState.INIT);
+    private static final OpenstackNode OS_NODE_2 = createNode(
+            HOSTNAME_1,
+            OpenstackNode.NodeType.COMPUTE,
+            DEVICE_1,
+            TEST_IP,
+            NodeState.COMPLETE);
+    private static final OpenstackNode OS_NODE_3 = createNode(
+            HOSTNAME_2,
+            OpenstackNode.NodeType.COMPUTE,
+            DEVICE_2,
+            TEST_IP,
+            NodeState.INIT);
+
+    /**
+     * Checks equals method works as expected.
+     */
+    @Test
+    public void testEquality() {
+        new EqualsTester().addEqualityGroup(OS_NODE_1, OS_NODE_2)
+                .addEqualityGroup(OS_NODE_3)
+                .testEquals();
+    }
+
+    /**
+     * Checks building a node without hostname fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildWithoutHostname() {
+        DefaultOpenstackNode.builder()
+                .type(OpenstackNode.NodeType.COMPUTE)
+                .intgBridge(DEVICE_1.id())
+                .managementIp(TEST_IP)
+                .dataIp(TEST_IP)
+                .state(NodeState.INIT)
+                .build();
+    }
+
+    /**
+     * Checks building a node without type fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildWithoutType() {
+        DefaultOpenstackNode.builder()
+                .hostname(HOSTNAME_1)
+                .intgBridge(DEVICE_1.id())
+                .managementIp(TEST_IP)
+                .dataIp(TEST_IP)
+                .state(NodeState.INIT)
+                .build();
+    }
+
+    /**
+     * Checks building a node without integration bridge ID fails with
+     * proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildWithoutIntegrationBridgeId() {
+        DefaultOpenstackNode.builder()
+                .hostname(HOSTNAME_1)
+                .type(OpenstackNode.NodeType.COMPUTE)
+                .managementIp(TEST_IP)
+                .dataIp(TEST_IP)
+                .state(NodeState.INIT)
+                .build();
+    }
+
+    /**
+     * Checks building a node without management IP address fails with
+     * proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildWithoutManagementIp() {
+        DefaultOpenstackNode.builder()
+                .hostname(HOSTNAME_1)
+                .type(OpenstackNode.NodeType.COMPUTE)
+                .intgBridge(DEVICE_1.id())
+                .dataIp(TEST_IP)
+                .state(NodeState.INIT)
+                .build();
+    }
+
+    /**
+     * Checks building a node without data IP nor VLAN interface name
+     * fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildWithoutDataIpNorVlanIntf() {
+        DefaultOpenstackNode.builder()
+                .hostname(HOSTNAME_1)
+                .type(OpenstackNode.NodeType.COMPUTE)
+                .intgBridge(DEVICE_1.id())
+                .state(NodeState.INIT)
+                .build();
+    }
+
+    /**
+     * Checks building a gateway type node without router bridge ID
+     * fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGatewayWithoutRouterBridgeId() {
+        DefaultOpenstackNode.builder()
+                .hostname(HOSTNAME_1)
+                .type(OpenstackNode.NodeType.GATEWAY)
+                .intgBridge(DEVICE_1.id())
+                .managementIp(TEST_IP)
+                .dataIp(TEST_IP)
+                .state(NodeState.INIT)
+                .build();
+    }
+}
diff --git a/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeManagerTest.java b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeManagerTest.java
new file mode 100644
index 0000000..6dd481d
--- /dev/null
+++ b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeManagerTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.LeadershipServiceAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.net.Device;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.onosproject.openstacknode.api.OpenstackNodeEvent.Type.*;
+
+/**
+ * Unit tests for OpenStack node manager.
+ */
+public class OpenstackNodeManagerTest extends OpenstackNodeTest {
+
+    private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+    private static final String ERR_SIZE = "Number of nodes did not match";
+    private static final String ERR_NOT_MATCH = "Node did not match";
+    private static final String ERR_NOT_FOUND = "Node did not exist";
+
+    private static final String COMPUTE_1_HOSTNAME = "compute_1";
+    private static final String COMPUTE_2_HOSTNAME = "compute_2";
+    private static final String COMPUTE_3_HOSTNAME = "compute_3";
+    private static final String GATEWAY_1_HOSTNAME = "gateway_1";
+
+    private static final Device COMPUTE_1_INTG_DEVICE = createDevice(1);
+    private static final Device COMPUTE_2_INTG_DEVICE = createDevice(2);
+    private static final Device COMPUTE_3_INTG_DEVICE = createDevice(3);
+    private static final Device GATEWAY_1_INTG_DEVICE = createDevice(4);
+    private static final Device GATEWAY_1_ROUT_DEVICE = createDevice(5);
+
+    private static final OpenstackNode COMPUTE_1 = createNode(
+            COMPUTE_1_HOSTNAME,
+            COMPUTE,
+            COMPUTE_1_INTG_DEVICE,
+            IpAddress.valueOf("10.100.0.1"),
+            NodeState.INIT
+    );
+    private static final OpenstackNode COMPUTE_2 = createNode(
+            COMPUTE_2_HOSTNAME,
+            COMPUTE,
+            COMPUTE_2_INTG_DEVICE,
+            IpAddress.valueOf("10.100.0.2"),
+            NodeState.INIT
+    );
+    private static final OpenstackNode COMPUTE_3 = createNode(
+            COMPUTE_3_HOSTNAME,
+            COMPUTE,
+            COMPUTE_3_INTG_DEVICE,
+            IpAddress.valueOf("10.100.0.3"),
+            NodeState.COMPLETE
+    );
+    private static final OpenstackNode GATEWAY_1 = createNode(
+            GATEWAY_1_HOSTNAME,
+            OpenstackNode.NodeType.GATEWAY,
+            GATEWAY_1_INTG_DEVICE,
+            GATEWAY_1_ROUT_DEVICE,
+            IpAddress.valueOf("10.100.0.4"),
+            NodeState.COMPLETE
+    );
+
+    private final TestOpenstackNodeListener testListener = new TestOpenstackNodeListener();
+
+    private OpenstackNodeManager target;
+    private DistributedOpenstackNodeStore osNodeStore;
+
+    @Before
+    public void setUp() {
+        osNodeStore = new DistributedOpenstackNodeStore();
+        TestUtils.setField(osNodeStore, "coreService", new TestCoreService());
+        TestUtils.setField(osNodeStore, "storageService", new TestStorageService());
+        TestUtils.setField(osNodeStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+        osNodeStore.activate();
+
+        osNodeStore.createNode(COMPUTE_2);
+        osNodeStore.createNode(COMPUTE_3);
+        osNodeStore.createNode(GATEWAY_1);
+
+        target = new OpenstackNodeManager();
+        target.coreService = new TestCoreService();
+        target.clusterService = new TestClusterService();
+        target.leadershipService = new TestLeadershipService();
+        target.configRegistry = new TestConfigService();
+        target.osNodeStore = osNodeStore;
+        target.addListener(testListener);
+        target.activate();
+        testListener.events.clear();
+    }
+
+    @After
+    public void tearDown() {
+        target.removeListener(testListener);
+        target.deactivate();
+        osNodeStore.deactivate();
+        osNodeStore = null;
+        target = null;
+    }
+
+    /**
+     * Checks if creating and removing a node work well with proper events.
+     */
+    @Test
+    public void testCreateAndRemoveNode() {
+        target.createNode(COMPUTE_1);
+        assertEquals(ERR_SIZE, 4, target.nodes().size());
+        assertTrue(target.node(COMPUTE_1_HOSTNAME) != null);
+
+        target.removeNode(COMPUTE_1_HOSTNAME);
+        assertEquals(ERR_SIZE, 3, target.nodes().size());
+        assertTrue(target.node(COMPUTE_1_HOSTNAME) == null);
+
+        validateEvents(OPENSTACK_NODE_CREATED, OPENSTACK_NODE_REMOVED);
+    }
+
+    /**
+     * Checks if creating null node fails with proper exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullNode() {
+        target.createNode(null);
+    }
+
+    /**
+     * Checks if creating a duplicated node fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateDuplicateNode() {
+        target.createNode(COMPUTE_1);
+        target.createNode(COMPUTE_1);
+    }
+
+    /**
+     * Checks if removing null node fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRemoveNullNode() {
+        target.removeNode(null);
+    }
+
+    /**
+     * Checks if updating a node works well with proper event.
+     */
+    @Test
+    public void testUpdateNode() {
+        OpenstackNode updated = DefaultOpenstackNode.from(COMPUTE_2)
+                .dataIp(IpAddress.valueOf("10.200.0.100"))
+                .build();
+        target.updateNode(updated);
+        assertEquals(ERR_NOT_MATCH, updated, target.node(COMPUTE_2_INTG_DEVICE.id()));
+        validateEvents(OPENSTACK_NODE_UPDATED);
+    }
+
+    /**
+     * Checks if updating a node state to complete generates proper events.
+     */
+    @Test
+    public void testUpdateNodeStateComplete() {
+        OpenstackNode updated = DefaultOpenstackNode.from(COMPUTE_2)
+                .state(NodeState.COMPLETE)
+                .build();
+        target.updateNode(updated);
+        assertEquals(ERR_NOT_MATCH, updated, target.node(COMPUTE_2_HOSTNAME));
+        validateEvents(OPENSTACK_NODE_UPDATED, OPENSTACK_NODE_COMPLETE);
+    }
+
+    /**
+     * Checks if updating a node state to incomplete generates proper events.
+     */
+    @Test
+    public void testUpdateNodeStateIncomplete() {
+        OpenstackNode updated = DefaultOpenstackNode.from(COMPUTE_3)
+                .state(NodeState.INCOMPLETE)
+                .build();
+        target.updateNode(updated);
+        assertEquals(ERR_NOT_MATCH, updated, target.node(COMPUTE_3_HOSTNAME));
+        validateEvents(OPENSTACK_NODE_UPDATED, OPENSTACK_NODE_INCOMPLETE);
+    }
+
+    /**
+     * Checks if updating a null node fails with proper exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testUpdateNullNode() {
+        target.updateNode(null);
+    }
+
+    /**
+     * Checks if updating not existing node fails with proper exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateNotExistingNode() {
+        target.updateNode(COMPUTE_1);
+    }
+
+    /**
+     * Checks if getting all nodes method returns correct set of nodes.
+     */
+    @Test
+    public void testGetAllNodes() {
+        assertEquals(ERR_SIZE, 3, target.nodes().size());
+        assertTrue(ERR_NOT_FOUND, target.nodes().contains(COMPUTE_2));
+        assertTrue(ERR_NOT_FOUND, target.nodes().contains(COMPUTE_3));
+        assertTrue(ERR_NOT_FOUND, target.nodes().contains(GATEWAY_1));
+    }
+
+    /**
+     * Checks if getting complete nodes method returns correct set of nodes.
+     */
+    @Test
+    public void testGetCompleteNodes() {
+        assertEquals(ERR_SIZE, 2, target.completeNodes().size());
+        assertTrue(ERR_NOT_FOUND, target.completeNodes().contains(COMPUTE_3));
+        assertTrue(ERR_NOT_FOUND, target.completeNodes().contains(GATEWAY_1));
+    }
+
+    /**
+     * Checks if getting nodes by type method returns correct set of nodes.
+     */
+    @Test
+    public void testGetNodesByType() {
+        assertEquals(ERR_SIZE, 2, target.nodes(COMPUTE).size());
+        assertTrue(ERR_NOT_FOUND, target.nodes(COMPUTE).contains(COMPUTE_2));
+        assertTrue(ERR_NOT_FOUND, target.nodes(COMPUTE).contains(COMPUTE_3));
+
+        assertEquals(ERR_SIZE, 1, target.nodes(GATEWAY).size());
+        assertTrue(ERR_NOT_FOUND, target.nodes(GATEWAY).contains(GATEWAY_1));
+    }
+
+    /**
+     * Checks if getting a node by hostname returns correct node.
+     */
+    @Test
+    public void testGetNodeByHostname() {
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(COMPUTE_2_HOSTNAME), COMPUTE_2));
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(COMPUTE_3_HOSTNAME), COMPUTE_3));
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(GATEWAY_1_HOSTNAME), GATEWAY_1));
+    }
+
+    /**
+     * Checks if getting a node by device ID returns correct node.
+     */
+    @Test
+    public void testGetNodeByDeviceId() {
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(GATEWAY_1_INTG_DEVICE.id()), GATEWAY_1));
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(GATEWAY_1.ovsdb()), GATEWAY_1));
+        assertTrue(ERR_NOT_FOUND, Objects.equals(
+                target.node(GATEWAY_1.routerBridge()), GATEWAY_1));
+    }
+
+    private void validateEvents(Enum... types) {
+        int i = 0;
+        assertEquals("Number of events did not match", types.length, testListener.events.size());
+        for (Event event : testListener.events) {
+            assertEquals("Incorrect event received", types[i], event.type());
+            i++;
+        }
+        testListener.events.clear();
+    }
+
+    private static class TestOpenstackNodeListener implements OpenstackNodeListener {
+        private List<OpenstackNodeEvent> events = Lists.newArrayList();
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            events.add(event);
+        }
+    }
+
+    private static class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return TEST_APP_ID;
+        }
+    }
+
+    private class TestConfigService extends NetworkConfigRegistryAdapter {
+
+    }
+
+    private class TestClusterService extends ClusterServiceAdapter {
+
+    }
+
+    private static class TestLeadershipService extends LeadershipServiceAdapter {
+
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java
new file mode 100644
index 0000000..d551efb
--- /dev/null
+++ b/apps/openstacknode/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNode.NodeType;
+
+import static org.onosproject.net.Device.Type.SWITCH;
+
+/**
+ * Provides a set of test OpenstackNode parameters for use with OpenstackNode related tests.
+ */
+abstract class OpenstackNodeTest {
+
+    protected static Device createDevice(long devIdNum) {
+        return new DefaultDevice(new ProviderId("of", "foo"),
+                DeviceId.deviceId(String.format("of:%016d", devIdNum)),
+                SWITCH,
+                "manufacturer",
+                "hwVersion",
+                "swVersion",
+                "serialNumber",
+                new ChassisId(1));
+    }
+
+    protected static OpenstackNode createNode(String hostname, NodeType type,
+                                              Device intgBridge, IpAddress ipAddr,
+                                              NodeState state) {
+        return DefaultOpenstackNode.builder()
+                .hostname(hostname)
+                .type(type)
+                .intgBridge(intgBridge.id())
+                .managementIp(ipAddr)
+                .dataIp(ipAddr)
+                .state(state)
+                .build();
+    }
+
+    protected static OpenstackNode createNode(String hostname, NodeType type,
+                                              Device intgBridge, Device routerBridge,
+                                              IpAddress ipAddr, NodeState state) {
+        return DefaultOpenstackNode.builder()
+                .hostname(hostname)
+                .type(type)
+                .intgBridge(intgBridge.id())
+                .routerBridge(routerBridge.id())
+                .managementIp(ipAddr)
+                .dataIp(ipAddr)
+                .state(state)
+                .build();
+    }
+}