Refactor: split api from single bundle for openstacknetworking

Change-Id: I8d94476d04bea1c0440e9735f519fdca3b1bd77d
diff --git a/apps/openstacknetworking/app/BUCK b/apps/openstacknetworking/app/BUCK
new file mode 100644
index 0000000..b4c5f92
--- /dev/null
+++ b/apps/openstacknetworking/app/BUCK
@@ -0,0 +1,44 @@
+include_defs('//apps/openstacknetworking/openstack4j.bucklet')
+
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:JACKSON',
+    '//lib:KRYO',
+    '//core/store/serializers:onos-core-serializers',
+    '//lib:org.apache.karaf.shell.console',
+    '//lib:javax.ws.rs-api',
+    '//utils/rest:onlab-rest',
+    '//cli:onos-cli',
+    '//apps/openstacknode/api:onos-apps-openstacknode-api',
+    '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
+    '//lib:openstack4j-core',
+    '//lib:openstack4j-http-connector',
+    '//lib:openstack4j-httpclient',
+    '//lib:json-patch',
+    '//lib:jackson-coreutils',
+    '//lib:btf',
+    '//lib:msg-simple',
+    '//lib:snakeyaml',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+    '//core/api:onos-api-tests',
+    '//core/common:onos-core-common-tests',
+    '//web/api:onos-rest-tests',
+    '//lib:TEST_REST',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+    web_context = '/onos/openstacknetworking',
+    api_title = 'OpenStack Networking API',
+    api_version = '1.0',
+    api_description = 'REST API for OpenStack Networking',
+    api_package = 'org.onosproject.openstacknetworking.web',
+
+    # dependency embedding
+    import_packages = INCLUDE_PACKAGES + ',' + EXCLUDE_PACKAGES  + ',' + ALL_PACKAGES,
+    bundle_classpath = get_openstack4j_deps_path() + get_jackson_deps_path()
+)
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/app.xml b/apps/openstacknetworking/app/app.xml
new file mode 100644
index 0000000..cb178ed
--- /dev/null
+++ b/apps/openstacknetworking/app/app.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2018-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<app name="org.onosproject.openstacknetworking" origin="ON.Lab" version="${project.version}"
+     category="Utility" url="https://wiki.onosproject.org/display/ONOS/SONA%3A+DC+Network+Virtualization"
+     title="OpenStack Networking App" features="${project.artifactId}" apps="org.onosproject.openstacknode"
+     featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features">
+    <description>${project.description}</description>
+    <artifact>mvn:${project.groupId}/onos-apps-openstacknetworking-api/${project.version}</artifact>
+    <artifact>mvn:${project.groupId}/onos-apps-openstacknetworking-app/${project.version}</artifact>
+</app>
diff --git a/apps/openstacknetworking/app/features.xml b/apps/openstacknetworking/app/features.xml
new file mode 100644
index 0000000..51cb23f
--- /dev/null
+++ b/apps/openstacknetworking/app/features.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ Copyright 2018-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <feature name="${project.artifactId}" version="${project.version}"
+             description="${project.description}">
+        <feature>onos-api</feature>
+        <bundle>mvn:${project.groupId}/onos-apps-openstacknetworking-api/${project.version}</bundle>
+        <bundle>mvn:${project.groupId}/onos-apps-openstacknetworking-app/${project.version}</bundle>
+    </feature>
+</features>
diff --git a/apps/openstacknetworking/app/pom.xml b/apps/openstacknetworking/app/pom.xml
new file mode 100644
index 0000000..0658518
--- /dev/null
+++ b/apps/openstacknetworking/app/pom.xml
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2018-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps-openstacknetworking</artifactId>
+        <version>1.13.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-apps-openstacknetworking-app</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>SONA Openstack Networking Application</description>
+
+    <properties>
+        <web.context>/onos/openstacknetworking</web.context>
+        <api.version>1.0.0</api.version>
+        <api.title>ONOS OpenStack Networking REST API</api.title>
+        <api.description>
+            APIs for interacting with OpenStack Neutron ONOS driver.
+        </api.description>
+        <api.package>org.onosproject.openstacknetworking.web</api.package>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknetworking-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknode-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.pacesys</groupId>
+            <artifactId>openstack4j-core</artifactId>
+            <version>${openstack4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.pacesys.openstack4j.connectors</groupId>
+            <artifactId>openstack4j-http-connector</artifactId>
+            <version>${openstack4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.pacesys.openstack4j.connectors</groupId>
+            <artifactId>openstack4j-httpclient</artifactId>
+            <version>${openstack4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>json-patch</artifactId>
+            <version>${json-patch.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>jackson-coreutils</artifactId>
+            <version>${jackson-coreutils.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>btf</artifactId>
+            <version>${btf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>msg-simple</artifactId>
+            <version>${msg-simple.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>${snakeyaml.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework</groupId>
+            <artifactId>jersey-test-framework-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-jetty</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-rest</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-scr-srcdescriptor</id>
+                        <goals>
+                            <goal>scr</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>bundle</supportedProjectType>
+                        <supportedProjectType>war</supportedProjectType>
+                    </supportedProjectTypes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp/</_wab>
+                        <Include-Resource>
+                            WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+                            {maven-resources}
+                        </Include-Resource>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Web-ContextPath>${web.context}</Web-ContextPath>
+                        <Import-Package>
+                            !org.apache.http.*,
+                            !com.fasterxml.jackson.dataformat.*,
+                            !javax.annotation,
+                            *,org.glassfish.jersey.servlet
+                        </Import-Package>
+                        <Embed-Dependency>
+                            openstack4j-core,
+                            openstack4j-http-connector,
+                            openstack4j-httpclient,
+                            json-patch,
+                            jackson-coreutils,
+                            btf,
+                            msg-simple,
+                            snakeyaml
+                        </Embed-Dependency>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java
new file mode 100644
index 0000000..efda0ae
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Deletes external peer router.
+ */
+@Command(scope = "onos", name = "openstack-delete-peer-router",
+        description = "Delete external peer router")
+public class DeleteExternalPeerRouterCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "ip address", description = "ip address",
+            required = true, multiValued = false)
+    private String ipAddress = null;
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+    private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        if (service.externalPeerRouters().stream()
+                .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+            print(NO_ELEMENT);
+            return;
+        }
+
+        try {
+            service.deleteExternalPeerRouter(ipAddress);
+        } catch (IllegalArgumentException e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
new file mode 100644
index 0000000..ebd949c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Lists external peer router lists.
+ */
+@Command(scope = "onos", name = "openstack-peer-routers",
+        description = "Lists external peer router lists")
+public class ExternalPeerRouterListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
new file mode 100644
index 0000000..4ae1dd4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * IP Address Completer.
+ */
+public class IpAddressCompleter implements Completer {
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<IpAddress> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterIp)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<IpAddress> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
new file mode 100644
index 0000000..6e21de6
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Mac Address Completer.
+ */
+public class MacAddressCompleter implements Completer {
+
+        @Override
+        public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<MacAddress> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterMac)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<MacAddress> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+    }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java
new file mode 100644
index 0000000..a2ef491
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack floating IP addresses.
+ */
+@Command(scope = "onos", name = "openstack-floatingips",
+        description = "Lists all OpenStack floating IP addresses")
+public class OpenstackFloatingIpListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-20s%-20s";
+
+    @Override
+    protected void execute() {
+        OpenstackRouterService service = AbstractShellCommand.get(OpenstackRouterService.class);
+        List<NetFloatingIP> floatingIps = Lists.newArrayList(service.floatingIps());
+        floatingIps.sort(Comparator.comparing(NetFloatingIP::getFloatingIpAddress));
+
+        if (outputJson()) {
+            try {
+                print("%s", mapper().writeValueAsString(json(floatingIps)));
+            } catch (JsonProcessingException e) {
+                print("Failed to list networks in JSON format");
+            }
+            return;
+        }
+
+        print(FORMAT, "ID", "Floating IP", "Fixed IP");
+        for (NetFloatingIP floatingIp: floatingIps) {
+            print(FORMAT, floatingIp.getId(),
+                    floatingIp.getFloatingIpAddress(),
+                    Strings.isNullOrEmpty(floatingIp.getFixedIpAddress()) ?
+            "" : floatingIp.getFixedIpAddress());
+        }
+    }
+
+    private JsonNode json(List<NetFloatingIP> floatingIps) {
+        ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+        for (NetFloatingIP floatingIp: floatingIps) {
+            result.add(modelEntityToJson(floatingIp, NeutronFloatingIP.class));
+        }
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java
new file mode 100644
index 0000000..401dddc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack networks.
+ */
+@Command(scope = "onos", name = "openstack-networks",
+        description = "Lists all OpenStack networks")
+public class OpenstackNetworkListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-20s%-20s%-20s%-8s";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+        List<Network> networks = Lists.newArrayList(service.networks());
+        networks.sort(Comparator.comparing(Network::getName));
+
+        if (outputJson()) {
+            try {
+                print("%s", mapper().writeValueAsString(json(networks)));
+            } catch (JsonProcessingException e) {
+                print("Failed to list networks in JSON format");
+            }
+            return;
+        }
+
+        print(FORMAT, "ID", "Name", "Network Mode", "VNI", "Subnets");
+        for (Network net: networks) {
+            List<String> subnets = service.subnets().stream()
+                    .filter(subnet -> subnet.getNetworkId().equals(net.getId()))
+                    .map(Subnet::getCidr)
+                    .collect(Collectors.toList());
+            print(FORMAT, net.getId(),
+                    net.getName(),
+                    net.getNetworkType().toString(),
+                    net.getProviderSegID(),
+                    subnets.isEmpty() ? "" : subnets);
+        }
+    }
+
+    private JsonNode json(List<Network> networks) {
+        ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+        for (Network net: networks) {
+            result.add(modelEntityToJson(net, NeutronNetwork.class));
+        }
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java
new file mode 100644
index 0000000..aa38ffe
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack ports.
+ */
+@Command(scope = "onos", name = "openstack-ports",
+        description = "Lists all OpenStack ports")
+public class OpenstackPortListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-20s%-20s%-8s";
+
+    @Argument(name = "networkId", description = "Network ID")
+    private String networkId = null;
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        List<Port> ports = Lists.newArrayList(service.ports());
+        ports.sort(Comparator.comparing(Port::getNetworkId));
+
+        if (!Strings.isNullOrEmpty(networkId)) {
+            ports.removeIf(port -> !port.getNetworkId().equals(networkId));
+        }
+
+        if (outputJson()) {
+            try {
+                print("%s", mapper().writeValueAsString(json(ports)));
+            } catch (JsonProcessingException e) {
+                print("Failed to list ports in JSON format");
+            }
+            return;
+        }
+
+        print(FORMAT, "ID", "Network", "MAC", "Fixed IPs");
+        for (Port port: ports) {
+            List<String> fixedIps = port.getFixedIps().stream()
+                    .map(IP::getIpAddress)
+                    .collect(Collectors.toList());
+            Network osNet = service.network(port.getNetworkId());
+            print(FORMAT, port.getId(),
+                    osNet.getName(),
+                    port.getMacAddress(),
+                    fixedIps.isEmpty() ? "" : fixedIps);
+        }
+    }
+
+    private JsonNode json(List<Port> ports) {
+        ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+        for (Port port: ports) {
+            result.add(modelEntityToJson(port, NeutronPort.class));
+        }
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java
new file mode 100644
index 0000000..be72076
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.openstacknetworking.api.Constants;
+
+/**
+ * Purges all existing network states.
+ */
+@Command(scope = "onos", name = "openstack-purge-rules",
+        description = "Purges all flow rules installed by OpenStack networking app")
+public class OpenstackPurgeRulesCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        FlowRuleService flowRuleService = AbstractShellCommand.get(FlowRuleService.class);
+        CoreService coreService = AbstractShellCommand.get(CoreService.class);
+        ApplicationId appId = coreService.getAppId(Constants.OPENSTACK_NETWORKING_APP_ID);
+        if (appId == null) {
+            error("Failed to purge OpenStack networking flow rules.");
+            return;
+        }
+        flowRuleService.removeFlowRulesById(appId);
+        print("Successfully purged flow rules installed by OpenStack networking application.");
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
new file mode 100644
index 0000000..8601505
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+
+/**
+ * Purges all existing network states.
+ */
+@Command(scope = "onos", name = "openstack-purge-states",
+        description = "Purges all OpenStack network states")
+public class OpenstackPurgeStateCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        get(OpenstackRouterAdminService.class).clear();
+        get(OpenstackNetworkAdminService.class).clear();
+        get(OpenstackSecurityGroupAdminService.class).clear();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java
new file mode 100644
index 0000000..772fa9b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack routers.
+ */
+@Command(scope = "onos", name = "openstack-routers",
+        description = "Lists all OpenStack routers")
+public class OpenstackRouterListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-20s%-20s%-8s";
+
+    private final OpenstackRouterService routerService =
+            AbstractShellCommand.get(OpenstackRouterService.class);
+    private final OpenstackNetworkService netService =
+            AbstractShellCommand.get(OpenstackNetworkService.class);
+
+    @Override
+    protected void execute() {
+        List<Router> routers = Lists.newArrayList(routerService.routers());
+        routers.sort(Comparator.comparing(Router::getName));
+
+        if (outputJson()) {
+            try {
+                print("%s", mapper().writeValueAsString(json(routers)));
+            } catch (JsonProcessingException e) {
+                print("Failed to list routers in JSON format");
+            }
+            return;
+        }
+        print(FORMAT, "ID", "Name", "External", "Internal");
+        for (Router router: routers) {
+            String exNetId = router.getExternalGatewayInfo() != null ?
+                router.getExternalGatewayInfo().getNetworkId() : null;
+
+            List<String> externals = Lists.newArrayList();
+            if (exNetId != null) {
+                // FIXME fix openstack4j to provide external gateway ip info
+                externals = netService.ports(exNetId).stream()
+                        .filter(port -> Objects.equals(port.getDeviceId(),
+                                router.getId()))
+                        .flatMap(port -> port.getFixedIps().stream())
+                        .map(IP::getIpAddress)
+                        .collect(Collectors.toList());
+            }
+
+            List<String> internals = Lists.newArrayList();
+            routerService.routerInterfaces(router.getId()).forEach(iface -> {
+                    internals.add(getRouterIfaceIp(iface));
+            });
+            print(FORMAT, router.getId(), router.getName(), externals, internals);
+        }
+    }
+
+    private String getRouterIfaceIp(RouterInterface iface) {
+        Port osPort = netService.port(iface.getPortId());
+        IP ipAddr = osPort.getFixedIps().stream()
+                .filter(ip -> ip.getSubnetId().equals(iface.getSubnetId()))
+                .findAny().orElse(null);
+        return ipAddr == null ? "" : ipAddr.getIpAddress();
+    }
+
+    private JsonNode json(List<Router> routers) {
+        ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+        for (Router router: routers) {
+            result.add(modelEntityToJson(router, NeutronRouter.class));
+        }
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java
new file mode 100644
index 0000000..9342bca
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack security groups.
+ */
+@Command(scope = "onos", name = "openstack-security-groups",
+        description = "Lists all OpenStack security groups")
+public class OpenstackSecurityGroupListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-20s";
+
+    @Argument(name = "networkId", description = "Network ID")
+    private String networkId = null;
+
+    @Override
+    protected void execute() {
+        OpenstackSecurityGroupService service =
+                AbstractShellCommand.get(OpenstackSecurityGroupService.class);
+
+        List<SecurityGroup> sgs = Lists.newArrayList(service.securityGroups());
+        sgs.sort(Comparator.comparing(SecurityGroup::getId));
+
+        if (outputJson()) {
+            try {
+                print("%s", mapper().writeValueAsString(json(sgs)));
+            } catch (JsonProcessingException e) {
+                error("Failed to list security groups in JSON format");
+            }
+            return;
+        }
+
+        print("Hint: use --json option to see security group rules as well\n");
+        print(FORMAT, "ID", "Name");
+        for (SecurityGroup sg: service.securityGroups()) {
+            print(FORMAT, sg.getId(), sg.getName());
+        }
+    }
+
+    private JsonNode json(List<SecurityGroup> securityGroups) {
+        ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+        for (SecurityGroup sg: securityGroups) {
+            result.add(modelEntityToJson(sg, NeutronSecurityGroup.class));
+        }
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java
new file mode 100644
index 0000000..88b9a71
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+/**
+ * Re-installs flow rules for OpenStack networking.
+ */
+@Command(scope = "onos", name = "openstack-sync-rules",
+        description = "Re-installs flow rules for OpenStack networking")
+public class OpenstackSyncRulesCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        // All handlers in this application reacts the node complete event and
+        // tries to re-configure flow rules for the complete node.
+        OpenstackNodeService osNodeService = AbstractShellCommand.get(OpenstackNodeService.class);
+        OpenstackNodeAdminService osNodeAdminService = AbstractShellCommand.get(OpenstackNodeAdminService.class);
+        if (osNodeService == null) {
+            error("Failed to re-install flow rules for OpenStack networking.");
+            return;
+        }
+        osNodeService.completeNodes().forEach(osNode -> {
+            OpenstackNode updated = osNode.updateState(NodeState.INIT);
+            osNodeAdminService.updateNode(updated);
+        });
+        print("Successfully requested re-installing flow rules.");
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java
new file mode 100644
index 0000000..a297ca0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Strings;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.openstack4j.api.OSClient;
+import org.openstack4j.api.exceptions.AuthenticationException;
+import org.openstack4j.model.identity.v2.Access;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.OSFactory;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
+
+/**
+ * Synchronizes OpenStack network states.
+ */
+@Command(scope = "onos", name = "openstack-sync-states",
+        description = "Synchronizes all OpenStack network states")
+public class OpenstackSyncStateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "endpoint", description = "OpenStack service endpoint",
+            required = true, multiValued = false)
+    private String endpoint = null;
+
+    @Argument(index = 1, name = "tenant", description = "OpenStack admin tenant name",
+            required = true, multiValued = false)
+    private String tenant = null;
+
+    @Argument(index = 2, name = "user", description = "OpenStack admin user name",
+            required = true, multiValued = false)
+    private String user = null;
+
+    @Argument(index = 3, name = "password", description = "OpenStack admin user password",
+            required = true, multiValued = false)
+    private String password = null;
+
+    private static final String SECURITY_GROUP_FORMAT = "%-40s%-20s";
+    private static final String NETWORK_FORMAT = "%-40s%-20s%-20s%-8s";
+    private static final String SUBNET_FORMAT = "%-40s%-20s%-20s";
+    private static final String PORT_FORMAT = "%-40s%-20s%-20s%-8s";
+    private static final String ROUTER_FORMAT = "%-40s%-20s%-20s%-8s";
+    private static final String FLOATING_IP_FORMAT = "%-40s%-20s%-20s";
+
+    private static final String DEVICE_OWNER_GW = "network:router_gateway";
+    private static final String DEVICE_OWNER_IFACE = "network:router_interface";
+
+    @Override
+    protected void execute() {
+        OpenstackSecurityGroupAdminService osSgAdminService = get(OpenstackSecurityGroupAdminService.class);
+        OpenstackSecurityGroupService osSgService = get(OpenstackSecurityGroupService.class);
+        OpenstackNetworkAdminService osNetAdminService = get(OpenstackNetworkAdminService.class);
+        OpenstackNetworkService osNetService = get(OpenstackNetworkService.class);
+        OpenstackRouterAdminService osRouterAdminService = get(OpenstackRouterAdminService.class);
+        OpenstackRouterService osRouterService = get(OpenstackRouterService.class);
+
+        Access osAccess;
+        try {
+            osAccess = OSFactory.builderV2()
+                    .endpoint(this.endpoint)
+                    .tenantName(this.tenant)
+                    .credentials(this.user, this.password)
+                    .authenticate()
+                    .getAccess();
+        } catch (AuthenticationException e) {
+            print("Authentication failed");
+            return;
+        } catch (Exception e) {
+            print("Failed to access OpenStack service");
+            return;
+        }
+
+        OSClient osClient = OSFactory.clientFromAccess(osAccess);
+
+        print("Synchronizing OpenStack security groups");
+        print(SECURITY_GROUP_FORMAT, "ID", "Name");
+        osClient.networking().securitygroup().list().forEach(osSg -> {
+            if (osSgService.securityGroup(osSg.getId()) != null) {
+                osSgAdminService.updateSecurityGroup(osSg);
+            } else {
+                osSgAdminService.createSecurityGroup(osSg);
+            }
+            print(SECURITY_GROUP_FORMAT, osSg.getId(), osSg.getName());
+        });
+
+        print("\nSynchronizing OpenStack networks");
+        print(NETWORK_FORMAT, "ID", "Name", "VNI", "Subnets");
+        osClient.networking().network().list().forEach(osNet -> {
+            if (osNetService.network(osNet.getId()) != null) {
+                osNetAdminService.updateNetwork(osNet);
+            } else {
+                osNetAdminService.createNetwork(osNet);
+            }
+            printNetwork(osNet);
+        });
+
+        print("\nSynchronizing OpenStack subnets");
+        print(SUBNET_FORMAT, "ID", "Network", "CIDR");
+        osClient.networking().subnet().list().forEach(osSubnet -> {
+            if (osNetService.subnet(osSubnet.getId()) != null) {
+                osNetAdminService.updateSubnet(osSubnet);
+            } else {
+                osNetAdminService.createSubnet(osSubnet);
+            }
+            printSubnet(osSubnet, osNetService);
+        });
+
+        print("\nSynchronizing OpenStack ports");
+        print(PORT_FORMAT, "ID", "Network", "MAC", "Fixed IPs");
+        osClient.networking().port().list().forEach(osPort -> {
+            if (osNetService.port(osPort.getId()) != null) {
+                osNetAdminService.updatePort(osPort);
+            } else {
+                osNetAdminService.createPort(osPort);
+            }
+            printPort(osPort, osNetService);
+        });
+
+        print("\nSynchronizing OpenStack routers");
+        print(ROUTER_FORMAT, "ID", "Name", "External", "Internal");
+        osClient.networking().router().list().forEach(osRouter -> {
+            if (osRouterService.router(osRouter.getId()) != null) {
+                osRouterAdminService.updateRouter(osRouter);
+            } else {
+                osRouterAdminService.createRouter(osRouter);
+            }
+
+            // FIXME do we need to manage router interfaces separately?
+            osNetService.ports().stream()
+                    .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+                            Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_IFACE))
+                    .forEach(osPort -> addRouterIface(osPort, osRouterService,
+                            osRouterAdminService));
+
+            printRouter(osRouter, osNetService);
+        });
+
+        print("\nSynchronizing OpenStack floating IPs");
+        print(FLOATING_IP_FORMAT, "ID", "Floating IP", "Fixed IP");
+        osClient.networking().floatingip().list().forEach(osFloating -> {
+            if (osRouterService.floatingIp(osFloating.getId()) != null) {
+                osRouterAdminService.updateFloatingIp(osFloating);
+            } else {
+                osRouterAdminService.createFloatingIp(osFloating);
+            }
+            printFloatingIp(osFloating);
+        });
+    }
+
+    // TODO fix the logic to add router interface to router
+    private void addRouterIface(Port osPort, OpenstackRouterService service,
+                                OpenstackRouterAdminService adminService) {
+        osPort.getFixedIps().forEach(p -> {
+            JsonNode jsonTree = mapper().createObjectNode()
+                    .put("id", osPort.getDeviceId())
+                    .put("tenant_id", osPort.getTenantId())
+                    .put("subnet_id", p.getSubnetId())
+                    .put("port_id", osPort.getId());
+            try {
+                RouterInterface rIface = getContext(NeutronRouterInterface.class)
+                        .readerFor(NeutronRouterInterface.class)
+                        .readValue(jsonTree);
+                if (service.routerInterface(rIface.getPortId()) != null) {
+                    adminService.updateRouterInterface(rIface);
+                } else {
+                    adminService.addRouterInterface(rIface);
+                }
+            } catch (IOException ignore) {
+            }
+        });
+    }
+
+    private void printNetwork(Network osNet) {
+        final String strNet = String.format(NETWORK_FORMAT,
+                osNet.getId(),
+                osNet.getName(),
+                osNet.getProviderSegID(),
+                osNet.getSubnets());
+        print(strNet);
+    }
+
+    private void printSubnet(Subnet osSubnet, OpenstackNetworkService osNetService) {
+        final String strSubnet = String.format(SUBNET_FORMAT,
+                osSubnet.getId(),
+                osNetService.network(osSubnet.getNetworkId()).getName(),
+                osSubnet.getCidr());
+        print(strSubnet);
+    }
+
+    private void printPort(Port osPort, OpenstackNetworkService osNetService) {
+        List<String> fixedIps = osPort.getFixedIps().stream()
+                .map(IP::getIpAddress)
+                .collect(Collectors.toList());
+        final String strPort = String.format(PORT_FORMAT,
+                osPort.getId(),
+                osNetService.network(osPort.getNetworkId()).getName(),
+                osPort.getMacAddress(),
+                fixedIps.isEmpty() ? "" : fixedIps);
+        print(strPort);
+    }
+
+    private void printRouter(Router osRouter, OpenstackNetworkService osNetService) {
+        List<String> externals = osNetService.ports().stream()
+                .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+                        Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_GW))
+                .flatMap(osPort -> osPort.getFixedIps().stream())
+                .map(IP::getIpAddress)
+                .collect(Collectors.toList());
+
+        List<String> internals = osNetService.ports().stream()
+                .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+                        Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_IFACE))
+                .flatMap(osPort -> osPort.getFixedIps().stream())
+                .map(IP::getIpAddress)
+                .collect(Collectors.toList());
+
+        final String strRouter = String.format(ROUTER_FORMAT,
+                osRouter.getId(),
+                osRouter.getName(),
+                externals.isEmpty() ? "" : externals,
+                internals.isEmpty() ? "" : internals);
+        print(strRouter);
+    }
+
+    private void printFloatingIp(NetFloatingIP floatingIp) {
+        final String strFloating = String.format(FLOATING_IP_FORMAT,
+                floatingIp.getId(),
+                floatingIp.getFloatingIpAddress(),
+                Strings.isNullOrEmpty(floatingIp.getFixedIpAddress()) ?
+                        "" : floatingIp.getFixedIpAddress());
+        print(strFloating);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
new file mode 100644
index 0000000..15a7555
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router",
+        description = "Update external peer router")
+public class UpdateExternalPeerRouterCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "ip address", description = "ip address",
+            required = true, multiValued = false)
+    private String ipAddress = null;
+
+    @Argument(index = 1, name = "mac address", description = "mac address",
+            required = true, multiValued = false)
+    private String macAddress = null;
+
+    @Argument(index = 2, name = "vlan id", description = "vlan id",
+            required = true, multiValued = false)
+    private String vlanId = null;
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+    private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+    private static final String NONE = "None";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        IpAddress externalPeerIpAddress = IpAddress.valueOf(
+                IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+        if (service.externalPeerRouters().isEmpty()) {
+            print(NO_ELEMENT);
+            return;
+        } else if (service.externalPeerRouters().stream()
+                .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+            print(NO_ELEMENT);
+            return;
+        }
+        try {
+            if (vlanId.equals(NONE)) {
+                service.updateExternalPeerRouter(externalPeerIpAddress,
+                        MacAddress.valueOf(macAddress),
+                        VlanId.NONE);
+
+            } else {
+                service.updateExternalPeerRouter(externalPeerIpAddress,
+                        MacAddress.valueOf(macAddress),
+                        VlanId.vlanId(vlanId));
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
+
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
new file mode 100644
index 0000000..eed62e7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router macc address.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router-vlan",
+        description = "Updates external peer router vlan")
+public class UpdateExternalPeerRouterVlanCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "ip address", description = "ip address",
+            required = true, multiValued = false)
+    private String ipAddress = null;
+
+    @Argument(index = 1, name = "vlan id", description = "vlan id",
+            required = true, multiValued = false)
+    private String vlanId = null;
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+    private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+    private static final String NONE = "None";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        IpAddress externalPeerIpAddress = IpAddress.valueOf(
+                IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+        if (service.externalPeerRouters().isEmpty()) {
+            print(NO_ELEMENT);
+            return;
+        } else if (service.externalPeerRouters().stream()
+                .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+            print(NO_ELEMENT);
+            return;
+        }
+
+        try {
+            if (vlanId.equals(NONE)) {
+                service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.NONE);
+            } else {
+                service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.vlanId(vlanId));
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
new file mode 100644
index 0000000..0768859
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Vlan Id Completer.
+ */
+public class VlanIdCompleter implements Completer {
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<VlanId> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterVlanId)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<VlanId> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
new file mode 100644
index 0000000..ad7bca1
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * CLI implementation for refresh/reprogram the data plane for the existing VM(OpenStackInstance).
+ */
+package org.onosproject.openstacknetworking.cli;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
new file mode 100644
index 0000000..fb18502
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+
+import java.util.Objects;
+
+/**
+ * Implementation of external peer router.
+ */
+public class DefaultExternalPeerRouter implements ExternalPeerRouter {
+
+    private IpAddress externalPeerRouterIp;
+    private MacAddress externalPeerRouterMac;
+    private VlanId externalPeerRouterVlanId;
+
+    public DefaultExternalPeerRouter(IpAddress externalPeerRouterIp,
+                                     MacAddress externalPeerRouterMac,
+                                     VlanId externalPeerRouterVlanId) {
+        this.externalPeerRouterIp = externalPeerRouterIp;
+        this.externalPeerRouterMac = externalPeerRouterMac;
+        this.externalPeerRouterVlanId = externalPeerRouterVlanId;
+    }
+
+    @Override
+    public IpAddress externalPeerRouterIp() {
+        return this.externalPeerRouterIp;
+    }
+    @Override
+    public MacAddress externalPeerRouterMac() {
+        return this.externalPeerRouterMac;
+    }
+    @Override
+    public VlanId externalPeerRouterVlanId() {
+        return this.externalPeerRouterVlanId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof DefaultExternalPeerRouter) {
+            DefaultExternalPeerRouter that = (DefaultExternalPeerRouter) obj;
+            return Objects.equals(externalPeerRouterIp, that.externalPeerRouterIp) &&
+                    Objects.equals(externalPeerRouterMac, that.externalPeerRouterMac) &&
+                    Objects.equals(externalPeerRouterVlanId, that.externalPeerRouterVlanId);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(externalPeerRouterIp,
+                externalPeerRouterMac,
+                externalPeerRouterVlanId);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("externalPeerRouterIp", externalPeerRouterIp)
+                .add("externalPeerRouterMac", externalPeerRouterMac)
+                .add("externalPeerRouterVlanId", externalPeerRouterVlanId)
+                .toString();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
new file mode 100644
index 0000000..19650e3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.IPVersionType;
+import org.openstack4j.model.network.Ipv6AddressMode;
+import org.openstack4j.model.network.Ipv6RaMode;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.State;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronAllowedAddressPair;
+import org.openstack4j.openstack.networking.domain.NeutronExtraDhcpOptCreate;
+import org.openstack4j.openstack.networking.domain.NeutronHostRoute;
+import org.openstack4j.openstack.networking.domain.NeutronIP;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.openstack4j.openstack.networking.domain.NeutronPool;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+import org.slf4j.Logger;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack network, subnet, and port using a {@code ConsistentMap}.
+ */
+@Service
+@Component(immediate = true)
+public class DistributedOpenstackNetworkStore
+        extends AbstractStore<OpenstackNetworkEvent, OpenstackNetworkStoreDelegate>
+        implements OpenstackNetworkStore {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String ERR_NOT_FOUND = " does not exist";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    private static final KryoNamespace SERIALIZER_NEUTRON_L2 = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(Network.class)
+            .register(NeutronNetwork.class)
+            .register(State.class)
+            .register(NetworkType.class)
+            .register(Port.class)
+            .register(NeutronPort.class)
+            .register(NeutronIP.class)
+            .register(NeutronAllowedAddressPair.class)
+            .register(NeutronExtraDhcpOptCreate.class)
+            .register(Subnet.class)
+            .register(NeutronSubnet.class)
+            .register(NeutronPool.class)
+            .register(NeutronHostRoute.class)
+            .register(IPVersionType.class)
+            .register(Ipv6AddressMode.class)
+            .register(Ipv6RaMode.class)
+            .register(LinkedHashMap.class)
+            .build();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final MapEventListener<String, Network> networkMapListener = new OpenstackNetworkMapListener();
+    private final MapEventListener<String, Subnet> subnetMapListener = new OpenstackSubnetMapListener();
+    private final MapEventListener<String, Port> portMapListener = new OpenstackPortMapListener();
+
+    private ConsistentMap<String, Network> osNetworkStore;
+    private ConsistentMap<String, Subnet> osSubnetStore;
+    private ConsistentMap<String, Port> osPortStore;
+
+    @Activate
+    protected void activate() {
+        ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+        osNetworkStore = storageService.<String, Network>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+                .withName("openstack-networkstore")
+                .withApplicationId(appId)
+                .build();
+        osNetworkStore.addListener(networkMapListener);
+
+        osSubnetStore = storageService.<String, Subnet>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+                .withName("openstack-subnetstore")
+                .withApplicationId(appId)
+                .build();
+        osSubnetStore.addListener(subnetMapListener);
+
+        osPortStore = storageService.<String, Port>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+                .withName("openstack-portstore")
+                .withApplicationId(appId)
+                .build();
+        osPortStore.addListener(portMapListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNetworkStore.removeListener(networkMapListener);
+        osSubnetStore.removeListener(subnetMapListener);
+        osPortStore.removeListener(portMapListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createNetwork(Network osNet) {
+        osNetworkStore.compute(osNet.getId(), (id, existing) -> {
+            final String error = osNet.getName() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osNet;
+        });
+    }
+
+    @Override
+    public void updateNetwork(Network osNet) {
+        osNetworkStore.compute(osNet.getId(), (id, existing) -> {
+            final String error = osNet.getName() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osNet;
+        });
+    }
+
+    @Override
+    public Network removeNetwork(String netId) {
+        Versioned<Network> osNet = osNetworkStore.remove(netId);
+        return osNet == null ? null : osNet.value();
+    }
+
+    @Override
+    public Network network(String netId) {
+        Versioned<Network> versioned = osNetworkStore.get(netId);
+        return versioned == null ? null : versioned.value();
+    }
+
+    @Override
+    public Set<Network> networks() {
+        Set<Network> osNets = osNetworkStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osNets);
+    }
+
+    @Override
+    public void createSubnet(Subnet osSubnet) {
+        osSubnetStore.compute(osSubnet.getId(), (id, existing) -> {
+            final String error = osSubnet.getId() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osSubnet;
+        });
+    }
+
+    @Override
+    public void updateSubnet(Subnet osSubnet) {
+        osSubnetStore.compute(osSubnet.getId(), (id, existing) -> {
+            final String error = osSubnet.getId() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osSubnet;
+        });
+    }
+
+    @Override
+    public Subnet removeSubnet(String subnetId) {
+        Versioned<Subnet> osSubnet = osSubnetStore.remove(subnetId);
+        return osSubnet == null ? null : osSubnet.value();
+    }
+
+    @Override
+    public Subnet subnet(String subnetId) {
+        Versioned<Subnet> osSubnet = osSubnetStore.get(subnetId);
+        return osSubnet == null ? null : osSubnet.value();
+    }
+
+    @Override
+    public Set<Subnet> subnets() {
+        Set<Subnet> osSubnets = osSubnetStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osSubnets);
+    }
+
+    @Override
+    public void createPort(Port osPort) {
+        osPortStore.compute(osPort.getId(), (id, existing) -> {
+            final String error = osPort.getId() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osPort;
+        });
+    }
+
+    @Override
+    public void updatePort(Port osPort) {
+        osPortStore.compute(osPort.getId(), (id, existing) -> {
+            final String error = osPort.getId() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osPort;
+        });
+    }
+
+    @Override
+    public Port removePort(String portId) {
+        Versioned<Port> osPort = osPortStore.remove(portId);
+        return osPort == null ? null : osPort.value();
+    }
+
+    @Override
+    public Port port(String portId) {
+        Versioned<Port> osPort = osPortStore.get(portId);
+        return osPort == null ? null : osPort.value();
+    }
+
+    @Override
+    public Set<Port> ports() {
+        Set<Port> osPorts = osPortStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osPorts);
+    }
+
+    @Override
+    public void clear() {
+        osPortStore.clear();
+        osSubnetStore.clear();
+        osNetworkStore.clear();
+    }
+
+    private class OpenstackNetworkMapListener implements MapEventListener<String, Network> {
+
+        @Override
+        public void event(MapEvent<String, Network> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack network updated");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_NETWORK_UPDATED,
+                                event.newValue().value()));
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack network created");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_NETWORK_CREATED,
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack network removed");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_NETWORK_REMOVED,
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+    }
+
+    private class OpenstackSubnetMapListener implements MapEventListener<String, Subnet> {
+
+        @Override
+        public void event(MapEvent<String, Subnet> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack subnet updated");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_SUBNET_UPDATED,
+                                network(event.newValue().value().getNetworkId()),
+                                event.newValue().value()));
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack subnet created");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_SUBNET_CREATED,
+                                network(event.newValue().value().getNetworkId()),
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack subnet removed");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_SUBNET_REMOVED,
+                                network(event.oldValue().value().getNetworkId()),
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+    }
+
+    private class OpenstackPortMapListener implements MapEventListener<String, Port> {
+
+        @Override
+        public void event(MapEvent<String, Port> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack port updated");
+                    eventExecutor.execute(() -> {
+                        Port oldPort = event.oldValue().value();
+                        Port newPort = event.newValue().value();
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_PORT_UPDATED,
+                                network(event.newValue().value().getNetworkId()), newPort));
+                        processSecurityGroupUpdate(oldPort, newPort);
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack port created");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_PORT_CREATED,
+                                network(event.newValue().value().getNetworkId()),
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack port removed");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNetworkEvent(
+                                OPENSTACK_PORT_REMOVED,
+                                network(event.oldValue().value().getNetworkId()),
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+
+        private void processSecurityGroupUpdate(Port oldPort, Port newPort) {
+            List<String> oldSecurityGroups = oldPort.getSecurityGroups() == null ?
+                    ImmutableList.of() : oldPort.getSecurityGroups();
+            List<String> newSecurityGroups = newPort.getSecurityGroups() == null ?
+                    ImmutableList.of() : newPort.getSecurityGroups();
+
+            oldSecurityGroups.stream()
+                    .filter(sgId -> !newPort.getSecurityGroups().contains(sgId))
+                    .forEach(sgId -> notifyDelegate(new OpenstackNetworkEvent(
+                            OPENSTACK_PORT_SECURITY_GROUP_REMOVED, newPort, sgId
+                    )));
+
+            newSecurityGroups.stream()
+                    .filter(sgId -> !oldPort.getSecurityGroups().contains(sgId))
+                    .forEach(sgId -> notifyDelegate(new OpenstackNetworkEvent(
+                            OPENSTACK_PORT_SECURITY_GROUP_ADDED, newPort, sgId
+                    )));
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
new file mode 100644
index 0000000..62fc7ac
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStore;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.State;
+import org.openstack4j.openstack.networking.domain.NeutronExternalGateway;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+import org.openstack4j.openstack.networking.domain.NeutronHostRoute;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+import org.osgi.service.component.annotations.Activate;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackRouterEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack router and floating IP using a {@code ConsistentMap}.
+ */
+@Service
+@Component(immediate = true)
+public class DistributedOpenstackRouterStore
+        extends AbstractStore<OpenstackRouterEvent, OpenstackRouterStoreDelegate>
+        implements OpenstackRouterStore {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String ERR_NOT_FOUND = " does not exist";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    private static final KryoNamespace SERIALIZER_NEUTRON_L3 = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(Router.class)
+            .register(NeutronRouter.class)
+            .register(State.class)
+            .register(NeutronHostRoute.class)
+            .register(ExternalGateway.class)
+            .register(NeutronExternalGateway.class)
+            .register(RouterInterface.class)
+            .register(NeutronRouterInterface.class)
+            .register(NetFloatingIP.class)
+            .register(NeutronFloatingIP.class)
+            .build();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final MapEventListener<String, Router> routerMapListener = new OpenstackRouterMapListener();
+    private final MapEventListener<String, RouterInterface> routerInterfaceMapListener =
+            new OpenstackRouterInterfaceMapListener();
+    private final MapEventListener<String, NetFloatingIP> floatingIpMapListener =
+            new OpenstackFloatingIpMapListener();
+
+    private ConsistentMap<String, Router> osRouterStore;
+    private ConsistentMap<String, RouterInterface> osRouterInterfaceStore;
+    private ConsistentMap<String, NetFloatingIP> osFloatingIpStore;
+
+    @Activate
+    protected void activate() {
+        ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+        osRouterStore = storageService.<String, Router>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+                .withName("openstack-routerstore")
+                .withApplicationId(appId)
+                .build();
+        osRouterStore.addListener(routerMapListener);
+
+        osRouterInterfaceStore = storageService.<String, RouterInterface>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+                .withName("openstack-routerifacestore")
+                .withApplicationId(appId)
+                .build();
+        osRouterInterfaceStore.addListener(routerInterfaceMapListener);
+
+        osFloatingIpStore = storageService.<String, NetFloatingIP>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+                .withName("openstack-floatingipstore")
+                .withApplicationId(appId)
+                .build();
+        osFloatingIpStore.addListener(floatingIpMapListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osRouterStore.removeListener(routerMapListener);
+        osRouterInterfaceStore.removeListener(routerInterfaceMapListener);
+        osFloatingIpStore.removeListener(floatingIpMapListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createRouter(Router osRouter) {
+        osRouterStore.compute(osRouter.getId(), (id, existing) -> {
+            final String error = osRouter.getName() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osRouter;
+        });
+    }
+
+    @Override
+    public void updateRouter(Router osRouter) {
+        osRouterStore.compute(osRouter.getId(), (id, existing) -> {
+            final String error = osRouter.getName() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osRouter;
+        });
+    }
+
+    @Override
+    public Router removeRouter(String routerId) {
+        Versioned<Router> osRouter = osRouterStore.remove(routerId);
+        return osRouter == null ? null : osRouter.value();
+    }
+
+    @Override
+    public Router router(String routerId) {
+        Versioned<Router> versioned = osRouterStore.get(routerId);
+        return versioned == null ? null : versioned.value();
+    }
+
+    @Override
+    public Set<Router> routers() {
+        Set<Router> osRouters = osRouterStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osRouters);
+    }
+
+    @Override
+    public void addRouterInterface(RouterInterface osRouterIface) {
+        osRouterInterfaceStore.compute(osRouterIface.getPortId(), (id, existing) -> {
+            final String error = osRouterIface.getPortId() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osRouterIface;
+        });
+    }
+
+    @Override
+    public void updateRouterInterface(RouterInterface osRouterIface) {
+        osRouterInterfaceStore.compute(osRouterIface.getPortId(), (id, existing) -> {
+            final String error = osRouterIface.getPortId() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osRouterIface;
+        });
+    }
+
+    @Override
+    public RouterInterface removeRouterInterface(String routerIfaceId) {
+        Versioned<RouterInterface> osRouterIface = osRouterInterfaceStore.remove(routerIfaceId);
+        return osRouterIface == null ? null : osRouterIface.value();
+    }
+
+    @Override
+    public RouterInterface routerInterface(String routerIfaceId) {
+        Versioned<RouterInterface> osRouterIface = osRouterInterfaceStore.get(routerIfaceId);
+        return osRouterIface == null ? null : osRouterIface.value();
+    }
+
+    @Override
+    public Set<RouterInterface> routerInterfaces() {
+        Set<RouterInterface> osRouterIfaces = osRouterInterfaceStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osRouterIfaces);
+    }
+
+    @Override
+    public void createFloatingIp(NetFloatingIP osFloatingIp) {
+        osFloatingIpStore.compute(osFloatingIp.getId(), (id, existing) -> {
+            final String error = osFloatingIp.getId() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osFloatingIp;
+        });
+    }
+
+    @Override
+    public void updateFloatingIp(NetFloatingIP osFloatingIp) {
+        osFloatingIpStore.compute(osFloatingIp.getId(), (id, existing) -> {
+            final String error = osFloatingIp.getId() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osFloatingIp;
+        });
+    }
+
+    @Override
+    public NetFloatingIP removeFloatingIp(String floatingIpId) {
+        Versioned<NetFloatingIP> osFloatingIp = osFloatingIpStore.remove(floatingIpId);
+        return osFloatingIp == null ? null : osFloatingIp.value();
+    }
+
+    @Override
+    public NetFloatingIP floatingIp(String floatingIpId) {
+        Versioned<NetFloatingIP> osFloatingIp = osFloatingIpStore.get(floatingIpId);
+        return osFloatingIp == null ? null : osFloatingIp.value();
+    }
+
+    @Override
+    public Set<NetFloatingIP> floatingIps() {
+        Set<NetFloatingIP> osFloatingIps = osFloatingIpStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osFloatingIps);
+    }
+
+    @Override
+    public void clear() {
+        osFloatingIpStore.clear();
+        osRouterInterfaceStore.clear();
+        osRouterStore.clear();
+    }
+
+    private class OpenstackRouterMapListener implements MapEventListener<String, Router> {
+
+        @Override
+        public void event(MapEvent<String, Router> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack router updated");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_UPDATED,
+                                event.newValue().value()));
+                        processGatewayUpdate(event);
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack router created");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_CREATED,
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack router removed");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_REMOVED,
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+
+        private void processGatewayUpdate(MapEvent<String, Router> event) {
+            ExternalGateway oldGateway = event.oldValue().value().getExternalGatewayInfo();
+            ExternalGateway newGateway = event.newValue().value().getExternalGatewayInfo();
+
+            if (oldGateway == null && newGateway != null) {
+                notifyDelegate(new OpenstackRouterEvent(
+                        OPENSTACK_ROUTER_GATEWAY_ADDED,
+                        event.newValue().value(), newGateway));
+            }
+            if (oldGateway != null && newGateway == null) {
+                notifyDelegate(new OpenstackRouterEvent(
+                        OPENSTACK_ROUTER_GATEWAY_REMOVED,
+                        event.newValue().value(), oldGateway));
+            }
+        }
+    }
+
+    private class OpenstackRouterInterfaceMapListener implements MapEventListener<String, RouterInterface> {
+
+        @Override
+        public void event(MapEvent<String, RouterInterface> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack router interface updated");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_INTERFACE_UPDATED,
+                                router(event.newValue().value().getId()),
+                                event.newValue().value()));
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack router interface created");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_INTERFACE_ADDED,
+                                router(event.newValue().value().getId()),
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack router interface removed");
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_ROUTER_INTERFACE_REMOVED,
+                                router(event.oldValue().value().getId()),
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+    }
+
+    private class OpenstackFloatingIpMapListener implements MapEventListener<String, NetFloatingIP> {
+
+        @Override
+        public void event(MapEvent<String, NetFloatingIP> event) {
+            switch (event.type()) {
+                case UPDATE:
+                    log.debug("OpenStack floating IP updated");
+                    eventExecutor.execute(() -> {
+                        Router osRouter = Strings.isNullOrEmpty(
+                                event.newValue().value().getRouterId()) ?
+                                null :
+                                router(event.newValue().value().getRouterId());
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_FLOATING_IP_UPDATED,
+                                osRouter,
+                                event.newValue().value()));
+                        processFloatingIpUpdate(event, osRouter);
+                    });
+                    break;
+                case INSERT:
+                    log.debug("OpenStack floating IP created");
+                    eventExecutor.execute(() -> {
+                        Router osRouter = Strings.isNullOrEmpty(
+                                event.newValue().value().getRouterId()) ?
+                                null :
+                                router(event.newValue().value().getRouterId());
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_FLOATING_IP_CREATED,
+                                osRouter,
+                                event.newValue().value()));
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack floating IP removed");
+                    eventExecutor.execute(() -> {
+                        Router osRouter = Strings.isNullOrEmpty(
+                                event.oldValue().value().getRouterId()) ?
+                                null :
+                                router(event.oldValue().value().getRouterId());
+                        notifyDelegate(new OpenstackRouterEvent(
+                                OPENSTACK_FLOATING_IP_REMOVED,
+                                osRouter,
+                                event.oldValue().value()));
+                    });
+                    break;
+                default:
+                    log.error("Unsupported event type");
+                    break;
+            }
+        }
+
+        private void processFloatingIpUpdate(MapEvent<String, NetFloatingIP> event,
+                                             Router osRouter) {
+            String oldPortId = event.oldValue().value().getPortId();
+            String newPortId = event.newValue().value().getPortId();
+
+            if (Strings.isNullOrEmpty(oldPortId) && !Strings.isNullOrEmpty(newPortId)) {
+                notifyDelegate(new OpenstackRouterEvent(
+                        OPENSTACK_FLOATING_IP_ASSOCIATED,
+                        osRouter,
+                        event.newValue().value(), newPortId));
+            }
+            if (!Strings.isNullOrEmpty(oldPortId) && Strings.isNullOrEmpty(newPortId)) {
+                notifyDelegate(new OpenstackRouterEvent(
+                        OPENSTACK_FLOATING_IP_DISASSOCIATED,
+                        osRouter,
+                        event.newValue().value(), oldPortId));
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java
new file mode 100644
index 0000000..c82c703
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStore;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack security group using a {@code ConsistentMap}.
+ *
+ */
+@Service
+@Component(immediate = true)
+public class DistributedSecurityGroupStore
+        extends AbstractStore<OpenstackSecurityGroupEvent, OpenstackSecurityGroupStoreDelegate>
+        implements OpenstackSecurityGroupStore {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String ERR_NOT_FOUND = " does not exist";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    private static final KryoNamespace SERIALIZER_SECURITY_GROUP = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(SecurityGroup.class)
+            .register(SecurityGroupRule.class)
+            .register(NeutronSecurityGroupRule.class)
+            .register(NeutronSecurityGroup.class)
+            .build();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final MapEventListener<String, SecurityGroup> securityGroupMapListener =
+            new OpenstackSecurityGroupMapListener();
+
+    private ConsistentMap<String, SecurityGroup> osSecurityGroupStore;
+
+    @Activate
+    protected void activate() {
+        ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+        osSecurityGroupStore = storageService.<String, SecurityGroup>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_SECURITY_GROUP))
+                .withName("openstack-securitygroupstore")
+                .withApplicationId(appId)
+                .build();
+        osSecurityGroupStore.addListener(securityGroupMapListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osSecurityGroupStore.removeListener(securityGroupMapListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createSecurityGroup(SecurityGroup sg) {
+        osSecurityGroupStore.compute(sg.getId(), (id, existing) -> {
+            final String error = sg.getName() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return sg;
+        });
+    }
+
+    @Override
+    public void updateSecurityGroup(SecurityGroup sg) {
+        osSecurityGroupStore.compute(sg.getId(), (id, existing) -> {
+            final String error = sg.getName() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return sg;
+        });
+    }
+
+    @Override
+    public SecurityGroup removeSecurityGroup(String sgId) {
+        Versioned<SecurityGroup> sg = osSecurityGroupStore.remove(sgId);
+        return sg == null ? null : sg.value();
+    }
+
+    @Override
+    public SecurityGroup securityGroup(String sgId) {
+        Versioned<SecurityGroup> osSg = osSecurityGroupStore.get(sgId);
+        return osSg == null ? null : osSg.value();
+    }
+
+    @Override
+    public Set<SecurityGroup> securityGroups() {
+        Set<SecurityGroup> osSgs = osSecurityGroupStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osSgs);
+    }
+
+    @Override
+    public void clear() {
+        osSecurityGroupStore.clear();
+    }
+
+    private class OpenstackSecurityGroupMapListener implements MapEventListener<String, SecurityGroup> {
+
+        @Override
+        public void event(MapEvent<String, SecurityGroup> event) {
+            switch (event.type()) {
+                case INSERT:
+                    log.debug("OpenStack security group created {}", event.newValue());
+                    eventExecutor.execute(() ->
+                            notifyDelegate(new OpenstackSecurityGroupEvent(
+                                    OPENSTACK_SECURITY_GROUP_CREATED,
+                                    event.newValue().value())));
+                    break;
+                case UPDATE:
+                    log.debug("OpenStack security group updated {}", event.newValue());
+                    eventExecutor.execute(() -> processUpdate(
+                            event.oldValue().value(),
+                            event.newValue().value()));
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack security group removed {}", event.newValue());
+                    eventExecutor.execute(() ->
+                            notifyDelegate(new OpenstackSecurityGroupEvent(
+                                    OPENSTACK_SECURITY_GROUP_REMOVED,
+                                    event.oldValue().value())));
+                    break;
+                default:
+            }
+        }
+
+        private void processUpdate(SecurityGroup oldSg, SecurityGroup newSg) {
+            Set<String> oldSgRuleIds = oldSg.getRules().stream()
+                    .map(SecurityGroupRule::getId).collect(Collectors.toSet());
+            Set<String> newSgRuleIds = newSg.getRules().stream()
+                    .map(SecurityGroupRule::getId).collect(Collectors.toSet());
+
+            oldSg.getRules().stream().filter(sgRule -> !newSgRuleIds.contains(sgRule.getId()))
+                    .forEach(sgRule -> notifyDelegate(new OpenstackSecurityGroupEvent(
+                            OPENSTACK_SECURITY_GROUP_RULE_REMOVED, newSg, sgRule)
+                    ));
+            newSg.getRules().stream().filter(sgRule -> !oldSgRuleIds.contains(sgRule.getId()))
+                    .forEach(sgRule -> notifyDelegate(new OpenstackSecurityGroupEvent(
+                            OPENSTACK_SECURITY_GROUP_RULE_CREATED, newSg, sgRule)
+                    ));
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java
new file mode 100644
index 0000000..6e20290
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.openstacknetworking.api.InstancePort;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of instance port based on host subsystem.
+ * Basically, HostBasedInstancePort is just a wrapper of a host, which helps
+ * mapping between OpenStack port and the OVS port and retrieving information
+ * such as IP address, location, and so on.
+ */
+public final class HostBasedInstancePort implements InstancePort {
+
+    static final String ANNOTATION_NETWORK_ID = "networkId";
+    static final String ANNOTATION_PORT_ID = "portId";
+    static final String ANNOTATION_CREATE_TIME = "createTime";
+
+    private final Host host;
+
+    /**
+     * Default constructor.
+     *
+     * @param instance host object of this instance
+     */
+    private HostBasedInstancePort(Host instance) {
+        this.host = instance;
+    }
+
+    /**
+     * Returns new instance.
+     *
+     * @param host host object of this instance
+     * @return instance
+     */
+    public static HostBasedInstancePort of(Host host) {
+        checkNotNull(host);
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_NETWORK_ID)));
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_PORT_ID)));
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_CREATE_TIME)));
+
+        return new HostBasedInstancePort(host);
+    }
+
+    @Override
+    public String networkId() {
+        return host.annotations().value(ANNOTATION_NETWORK_ID);
+    }
+
+    @Override
+    public String portId() {
+        return host.annotations().value(ANNOTATION_PORT_ID);
+    }
+
+    @Override
+    public MacAddress macAddress() {
+        return host.mac();
+    }
+
+    @Override
+    public IpAddress ipAddress() {
+        Optional<IpAddress> ipAddr = host.ipAddresses().stream().findFirst();
+        return ipAddr.orElse(null);
+    }
+
+    @Override
+    public DeviceId deviceId() {
+        return host.location().deviceId();
+    }
+
+    @Override
+    public PortNumber portNumber() {
+        return host.location().port();
+    }
+
+    @Override
+    public String toString() {
+        return host.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof HostBasedInstancePort) {
+            HostBasedInstancePort that = (HostBasedInstancePort) obj;
+            if (Objects.equals(this.portId(), that.portId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(portId());
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java
new file mode 100644
index 0000000..207cd2b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_DETECTED;
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_UPDATED;
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_VANISHED;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing host based instance ports.
+ * It also provides instance port events for the hosts mapped to OpenStack VM interface.
+ */
+@Service
+@Component(immediate = true)
+public class HostBasedInstancePortManager
+        extends ListenerRegistry<InstancePortEvent, InstancePortListener>
+        implements InstancePortService {
+
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private final HostListener hostListener = new InternalHostListener();
+
+    @Activate
+    protected void activate() {
+        hostService.addListener(hostListener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        hostService.removeListener(hostListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public InstancePort instancePort(MacAddress macAddress) {
+        Host host = hostService.getHost(HostId.hostId(macAddress));
+        if (host == null || !isValidHost(host)) {
+            return null;
+        }
+        return HostBasedInstancePort.of(host);
+    }
+
+    @Override
+    public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
+        return Tools.stream(hostService.getHosts()).filter(this::isValidHost)
+                .map(HostBasedInstancePort::of)
+                .filter(instPort -> instPort.networkId().equals(osNetId))
+                .filter(instPort -> instPort.ipAddress().equals(ipAddress))
+                .findAny().orElse(null);
+    }
+
+    @Override
+    public InstancePort instancePort(String osPortId) {
+        return Tools.stream(hostService.getHosts()).filter(this::isValidHost)
+                .map(HostBasedInstancePort::of)
+                .filter(instPort -> instPort.portId().equals(osPortId))
+                .findAny().orElse(null);
+    }
+
+    @Override
+    public Set<InstancePort> instancePorts() {
+        Set<InstancePort> instPors = Tools.stream(hostService.getHosts())
+                .filter(this::isValidHost)
+                .map(HostBasedInstancePort::of)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(instPors);
+    }
+
+    @Override
+    public Set<InstancePort> instancePorts(String osNetId) {
+        Set<InstancePort> instPors = Tools.stream(hostService.getHosts())
+                .filter(this::isValidHost)
+                .map(HostBasedInstancePort::of)
+                .filter(instPort -> instPort.networkId().equals(osNetId))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(instPors);
+    }
+
+    private boolean isValidHost(Host host) {
+        return !host.ipAddresses().isEmpty() &&
+                host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
+                host.annotations().value(ANNOTATION_PORT_ID) != null;
+    }
+
+    private class InternalHostListener implements HostListener {
+
+        @Override
+        public boolean isRelevant(HostEvent event) {
+            Host host = event.subject();
+            if (!isValidHost(host)) {
+                log.debug("Invalid host detected, ignore it {}", host);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            InstancePort instPort = HostBasedInstancePort.of(event.subject());
+            InstancePortEvent instPortEvent;
+            switch (event.type()) {
+                case HOST_UPDATED:
+                    instPortEvent = new InstancePortEvent(
+                            OPENSTACK_INSTANCE_PORT_UPDATED,
+                            instPort);
+                    log.debug("Instance port is updated: {}", instPort);
+                    process(instPortEvent);
+                    break;
+                case HOST_ADDED:
+                    instPortEvent = new InstancePortEvent(
+                            OPENSTACK_INSTANCE_PORT_DETECTED,
+                            instPort);
+                    log.debug("Instance port is detected: {}", instPort);
+                    process(instPortEvent);
+                    break;
+                case HOST_REMOVED:
+                    instPortEvent = new InstancePortEvent(
+                            OPENSTACK_INSTANCE_PORT_VANISHED,
+                            instPort);
+                    log.debug("Instance port is disabled: {}", instPort);
+                    process(instPortEvent);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
new file mode 100644
index 0000000..d4a8b68
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Sets flow rules directly using FlowRuleService.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackFlowRuleManager implements OpenstackFlowRuleService {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final int DROP_PRIORITY = 0;
+    private static final int HIGH_PRIORITY = 30000;
+    private static final int TIMEOUT_SNAT_RULE = 60;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    private final ExecutorService deviceEventExecutor =
+            Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
+    private final OpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        osNodeService.addListener(internalNodeListener);
+
+        osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+                .forEach(node -> initializePipeline(node.intgBridge()));
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNodeService.removeListener(internalNodeListener);
+        deviceEventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+
+    @Override
+    public void setRule(ApplicationId appId,
+                               DeviceId deviceId,
+                               TrafficSelector selector,
+                               TrafficTreatment treatment,
+                               int priority,
+                               int tableType,
+                               boolean install) {
+
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(priority)
+                .fromApp(appId)
+                .forTable(tableType);
+
+        if (priority == Constants.PRIORITY_SNAT_RULE) {
+            flowRuleBuilder.makeTemporary(TIMEOUT_SNAT_RULE);
+        } else {
+            flowRuleBuilder.makePermanent();
+        }
+
+        applyRule(flowRuleBuilder.build(), install);
+    }
+
+    private void applyRule(FlowRule flowRule, boolean install) {
+        FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
+
+        flowOpsBuilder = install ? flowOpsBuilder.add(flowRule) : flowOpsBuilder.remove(flowRule);
+
+        flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
+            @Override
+            public void onSuccess(FlowRuleOperations ops) {
+                log.debug("Provisioned vni or forwarding table");
+            }
+
+            @Override
+            public void onError(FlowRuleOperations ops) {
+                log.debug("Failed to privision vni or forwarding table");
+            }
+        }));
+    }
+
+    private void initializePipeline(DeviceId deviceId) {
+        connectTables(deviceId, Constants.SRC_VNI_TABLE, Constants.ACL_TABLE);
+        connectTables(deviceId, Constants.ACL_TABLE, Constants.JUMP_TABLE);
+        setupJumpTable(deviceId);
+    }
+
+    @Override
+    public void connectTables(DeviceId deviceId, int fromTable, int toTable) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        treatment.transition(toTable);
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(fromTable)
+                .build();
+
+        applyRule(flowRule, true);
+    }
+
+    @Override
+    public void setUpTableMissEntry(DeviceId deviceId, int table) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        treatment.drop();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(table)
+                .build();
+
+        applyRule(flowRule, true);
+    }
+
+    private void setupJumpTable(DeviceId deviceId) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        selector.matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+        treatment.transition(Constants.ROUTING_TABLE);
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(HIGH_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(Constants.JUMP_TABLE)
+                .build();
+
+        applyRule(flowRule, true);
+
+        selector = DefaultTrafficSelector.builder();
+        treatment = DefaultTrafficTreatment.builder();
+
+        treatment.transition(Constants.FORWARDING_TABLE);
+
+        flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(Constants.JUMP_TABLE)
+                .build();
+
+        applyRule(flowRule, true);
+    }
+
+    private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode osNode = event.subject();
+            // TODO check leadership of the node and make only the leader process
+
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                    deviceEventExecutor.execute(() -> {
+                        log.info("COMPLETE node {} is detected", osNode.hostname());
+                        processCompleteNode(event.subject());
+                    });
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_REMOVED:
+                case OPENSTACK_NODE_INCOMPLETE:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processCompleteNode(OpenstackNode osNode) {
+            if (osNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
+                initializePipeline(osNode.intgBridge());
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
new file mode 100644
index 0000000..230e740
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack network,
+ * subnet, and port.
+ */
+
+@Service
+@Component(immediate = true)
+public class OpenstackNetworkManager
+        extends ListenerRegistry<OpenstackNetworkEvent, OpenstackNetworkListener>
+        implements OpenstackNetworkAdminService, OpenstackNetworkService {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String MSG_NETWORK  = "OpenStack network %s %s";
+    private static final String MSG_SUBNET  = "OpenStack subnet %s %s";
+    private static final String MSG_PORT = "OpenStack port %s %s";
+    private static final String MSG_CREATED = "created";
+    private static final String MSG_UPDATED = "updated";
+    private static final String MSG_REMOVED = "removed";
+
+    private static final String ERR_NULL_NETWORK  = "OpenStack network cannot be null";
+    private static final String ERR_NULL_NETWORK_ID  = "OpenStack network ID cannot be null";
+    private static final String ERR_NULL_NETWORK_NAME  = "OpenStack network name cannot be null";
+    private static final String ERR_NULL_SUBNET = "OpenStack subnet cannot be null";
+    private static final String ERR_NULL_SUBNET_ID = "OpenStack subnet ID cannot be null";
+    private static final String ERR_NULL_SUBNET_NET_ID = "OpenStack subnet network ID cannot be null";
+    private static final String ERR_NULL_SUBNET_CIDR = "OpenStack subnet CIDR cannot be null";
+    private static final String ERR_NULL_PORT = "OpenStack port cannot be null";
+    private static final String ERR_NULL_PORT_ID = "OpenStack port ID cannot be null";
+    private static final String ERR_NULL_PORT_NET_ID = "OpenStack port network ID cannot be null";
+
+    private static final String ERR_NOT_FOUND = " does not exist";
+    private static final String ERR_IN_USE = " still in use";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkStore osNetworkStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+
+    private final OpenstackNetworkStoreDelegate delegate = new InternalNetworkStoreDelegate();
+
+    private ConsistentMap<String, ExternalPeerRouter> externalPeerRouterMap;
+
+    private static final KryoNamespace SERIALIZER_EXTERNAL_PEER_ROUTER_MAP = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(ExternalPeerRouter.class)
+            .register(DefaultExternalPeerRouter.class)
+            .register(MacAddress.class)
+            .register(IpAddress.class)
+            .register(VlanId.class)
+            .build();
+
+    private ApplicationId appId;
+
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+
+        osNetworkStore.setDelegate(delegate);
+        log.info("Started");
+
+        externalPeerRouterMap = storageService.<String, ExternalPeerRouter>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_EXTERNAL_PEER_ROUTER_MAP))
+                .withName("external-routermap")
+                .withApplicationId(appId)
+                .build();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNetworkStore.unsetDelegate(delegate);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createNetwork(Network osNet) {
+        checkNotNull(osNet, ERR_NULL_NETWORK);
+        checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
+
+        osNetworkStore.createNetwork(osNet);
+        log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateNetwork(Network osNet) {
+        checkNotNull(osNet, ERR_NULL_NETWORK);
+        checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
+
+        osNetworkStore.updateNetwork(osNet);
+        log.info(String.format(MSG_NETWORK, osNet.getId(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removeNetwork(String netId) {
+        checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
+        synchronized (this) {
+            if (isNetworkInUse(netId)) {
+                final String error = String.format(MSG_NETWORK, netId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            Network osNet = osNetworkStore.removeNetwork(netId);
+            if (osNet != null) {
+                log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void createSubnet(Subnet osSubnet) {
+        checkNotNull(osSubnet, ERR_NULL_SUBNET);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
+
+        osNetworkStore.createSubnet(osSubnet);
+        log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateSubnet(Subnet osSubnet) {
+        checkNotNull(osSubnet, ERR_NULL_SUBNET);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
+        checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
+
+        osNetworkStore.updateSubnet(osSubnet);
+        log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removeSubnet(String subnetId) {
+        checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
+        synchronized (this) {
+            if (isSubnetInUse(subnetId)) {
+                final String error = String.format(MSG_SUBNET, subnetId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            Subnet osSubnet = osNetworkStore.removeSubnet(subnetId);
+            if (osSubnet != null) {
+                log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void createPort(Port osPort) {
+        checkNotNull(osPort, ERR_NULL_PORT);
+        checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
+        checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
+
+        osNetworkStore.createPort(osPort);
+        log.info(String.format(MSG_PORT, osPort.getId(), MSG_CREATED));
+    }
+
+    @Override
+    public void updatePort(Port osPort) {
+        checkNotNull(osPort, ERR_NULL_PORT);
+        checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
+        checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
+
+        osNetworkStore.updatePort(osPort);
+        log.info(String.format(MSG_PORT, osPort.getId(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removePort(String portId) {
+        checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
+        synchronized (this) {
+            if (isPortInUse(portId)) {
+                final String error = String.format(MSG_PORT, portId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            Port osPort = osNetworkStore.removePort(portId);
+            if (osPort != null) {
+                log.info(String.format(MSG_PORT, osPort.getId(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void clear() {
+        osNetworkStore.clear();
+    }
+
+    @Override
+    public Network network(String netId) {
+        checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
+        return osNetworkStore.network(netId);
+    }
+
+    @Override
+    public Set<Network> networks() {
+        return osNetworkStore.networks();
+    }
+
+    @Override
+    public Subnet subnet(String subnetId) {
+        checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
+        return osNetworkStore.subnet(subnetId);
+    }
+
+    @Override
+    public Set<Subnet> subnets() {
+        return osNetworkStore.subnets();
+    }
+
+    @Override
+    public Set<Subnet> subnets(String netId) {
+        Set<Subnet> osSubnets = osNetworkStore.subnets().stream()
+                .filter(subnet -> Objects.equals(subnet.getNetworkId(), netId))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osSubnets);
+    }
+
+    @Override
+    public Port port(String portId) {
+        checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
+        return osNetworkStore.port(portId);
+    }
+
+    @Override
+    public Port port(org.onosproject.net.Port port) {
+        String portName = port.annotations().value(PORT_NAME);
+        if (Strings.isNullOrEmpty(portName)) {
+            return null;
+        }
+        Optional<Port> osPort = osNetworkStore.ports()
+                .stream()
+                .filter(p -> p.getId().contains(portName.substring(3)))
+                .findFirst();
+        return osPort.orElse(null);
+    }
+
+    @Override
+    public Set<Port> ports() {
+        return ImmutableSet.copyOf(osNetworkStore.ports());
+    }
+
+    @Override
+    public Set<Port> ports(String netId) {
+        Set<Port> osPorts = osNetworkStore.ports().stream()
+                .filter(port -> Objects.equals(port.getNetworkId(), netId))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osPorts);
+    }
+
+    @Override
+    public ExternalPeerRouter externalPeerRouter(IpAddress ipAddress) {
+        if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+            return externalPeerRouterMap.get(ipAddress.toString()).value();
+        }
+        return null;
+    }
+
+    @Override
+    public void deriveExternalPeerRouterMac(ExternalGateway externalGateway, Router router) {
+        log.info("deriveExternalPeerRouterMac called");
+
+        IpAddress sourceIp = getExternalGatewaySourceIp(externalGateway, router);
+        IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+
+        if (sourceIp == null || targetIp == null) {
+            log.warn("Failed to derive external router mac address because source IP {} or target IP {} is null",
+                    sourceIp, targetIp);
+            return;
+        }
+
+        if (externalPeerRouterMap.containsKey(targetIp.toString()) &&
+                !externalPeerRouterMap.get(
+                        targetIp.toString()).value().externalPeerRouterMac().equals(MacAddress.NONE)) {
+            return;
+        }
+
+        MacAddress sourceMac = Constants.DEFAULT_GATEWAY_MAC;
+        Ethernet ethRequest = ARP.buildArpRequest(sourceMac.toBytes(),
+                sourceIp.toOctets(),
+                targetIp.toOctets(),
+                VlanId.NO_VID);
+
+        if (osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY).isEmpty()) {
+            log.warn("There's no complete gateway");
+            return;
+        }
+        OpenstackNode gatewayNode = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+                .stream()
+                .findFirst()
+                .orElse(null);
+
+        if (gatewayNode == null) {
+            return;
+        }
+
+        String upLinkPort = gatewayNode.uplinkPort();
+
+        org.onosproject.net.Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
+                .filter(p -> Objects.equals(p.annotations().value(PORT_NAME), upLinkPort))
+                .findAny().orElse(null);
+
+        if (port == null) {
+            log.warn("There's no uplink port for gateway node {}", gatewayNode.toString());
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(port.number())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                gatewayNode.intgBridge(),
+                treatment,
+                ByteBuffer.wrap(ethRequest.serialize())));
+
+        externalPeerRouterMap.put(
+                targetIp.toString(), new DefaultExternalPeerRouter(targetIp, MacAddress.NONE, VlanId.NONE));
+
+        log.info("Initializes external peer router map with peer router IP {}", targetIp.toString());
+    }
+
+    @Override
+    public void deleteExternalPeerRouter(ExternalGateway externalGateway) {
+        IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+        if (targetIp == null) {
+            return;
+        }
+
+        if (externalPeerRouterMap.containsKey(targetIp.toString())) {
+            externalPeerRouterMap.remove(targetIp.toString());
+        }
+    }
+
+    @Override
+    public void deleteExternalPeerRouter(String ipAddress) {
+        if (ipAddress == null) {
+            return;
+        }
+
+        if (externalPeerRouterMap.containsKey(ipAddress)) {
+            externalPeerRouterMap.remove(ipAddress);
+        }
+
+    }
+    private IpAddress getExternalGatewaySourceIp(ExternalGateway externalGateway, Router router) {
+        Port exGatewayPort = ports(externalGateway.getNetworkId())
+                .stream()
+                .filter(port -> Objects.equals(port.getDeviceId(), router.getId()))
+                .findAny().orElse(null);
+        if (exGatewayPort == null) {
+            log.warn("no external gateway port for router({})", router.getName());
+            return null;
+        }
+
+        IP ipAddress = exGatewayPort.getFixedIps().stream().findFirst().orElse(null);
+
+        return ipAddress == null ? null : IpAddress.valueOf(ipAddress.getIpAddress());
+    }
+
+    private IpAddress getExternalPeerRouterIp(ExternalGateway externalGateway) {
+        Optional<Subnet> externalSubnet = subnets(externalGateway.getNetworkId())
+                .stream()
+                .findFirst();
+
+        if (externalSubnet.isPresent()) {
+            return IpAddress.valueOf(externalSubnet.get().getGateway());
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void updateExternalPeerRouterMac(IpAddress ipAddress, MacAddress macAddress) {
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+                new DefaultExternalPeerRouter(ipAddress, macAddress, existing.externalPeerRouterVlanId()));
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+        log.info("Updated external peer router map {}",
+                externalPeerRouterMap.get(ipAddress.toString()).value().toString());
+    }
+
+
+    @Override
+    public void updateExternalPeerRouter(IpAddress ipAddress, MacAddress macAddress, VlanId vlanId) {
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+                new DefaultExternalPeerRouter(ipAddress, macAddress, vlanId));
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+    }
+
+    @Override
+    public MacAddress externalPeerRouterMac(ExternalGateway externalGateway) {
+        IpAddress ipAddress = getExternalPeerRouterIp(externalGateway);
+
+        if (ipAddress == null) {
+            return null;
+        }
+        if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+            return externalPeerRouterMap.get(ipAddress.toString()).value().externalPeerRouterMac();
+        } else {
+            throw new NoSuchElementException();
+        }
+    }
+
+    @Override
+    public void updateExternalPeerRouterVlan(IpAddress ipAddress, VlanId vlanId) {
+
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) -> {
+                return new DefaultExternalPeerRouter(ipAddress, existing.externalPeerRouterMac(), vlanId);
+            });
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+    }
+
+    @Override
+    public Set<ExternalPeerRouter> externalPeerRouters() {
+        Set<ExternalPeerRouter> externalPeerRouters = externalPeerRouterMap.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(externalPeerRouters);
+    }
+
+    private boolean isNetworkInUse(String netId) {
+        return !subnets(netId).isEmpty() && !ports(netId).isEmpty();
+    }
+
+    private boolean isSubnetInUse(String subnetId) {
+        // TODO add something if needed
+        return false;
+    }
+
+    private boolean isPortInUse(String portId) {
+        // TODO add something if needed
+        return false;
+    }
+
+    private class InternalNetworkStoreDelegate implements OpenstackNetworkStoreDelegate {
+
+        @Override
+        public void notify(OpenstackNetworkEvent event) {
+            if (event != null) {
+                log.trace("send oepnstack switching event {}", event);
+                process(event);
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java
new file mode 100644
index 0000000..c636a1c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStore;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStoreDelegate;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack router and
+ * floating IP address.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackRouterManager
+        extends ListenerRegistry<OpenstackRouterEvent, OpenstackRouterListener>
+        implements OpenstackRouterAdminService, OpenstackRouterService {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String MSG_ROUTER = "OpenStack router %s %s";
+    private static final String MSG_ROUTER_IFACE = "OpenStack router interface %s %s";
+    private static final String MSG_FLOATING_IP = "OpenStack floating IP %s %s";
+    private static final String MSG_CREATED = "created";
+    private static final String MSG_UPDATED = "updated";
+    private static final String MSG_REMOVED = "removed";
+
+    private static final String ERR_NULL_ROUTER = "OpenStack router cannot be null";
+    private static final String ERR_NULL_ROUTER_ID = "OpenStack router ID cannot be null";
+    private static final String ERR_NULL_ROUTER_NAME = "OpenStack router name cannot be null";
+    private static final String ERR_NULL_IFACE = "OpenStack router interface cannot be null";
+    private static final String ERR_NULL_IFACE_ROUTER_ID = "OpenStack router interface router ID cannot be null";
+    private static final String ERR_NULL_IFACE_PORT_ID = "OpenStack router interface port ID cannot be null";
+    private static final String ERR_NULL_FLOATING = "OpenStack floating IP cannot be null";
+    private static final String ERR_NULL_FLOATING_ID = "OpenStack floating IP cannot be null";
+
+    private static final String ERR_IN_USE = " still in use";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterStore osRouterStore;
+
+    private final OpenstackRouterStoreDelegate delegate = new InternalRouterStoreDelegate();
+
+    @Activate
+    protected void activate() {
+        coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+        osRouterStore.setDelegate(delegate);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osRouterStore.unsetDelegate(delegate);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createRouter(Router osRouter) {
+        checkNotNull(osRouter, ERR_NULL_ROUTER);
+        checkArgument(!Strings.isNullOrEmpty(osRouter.getId()), ERR_NULL_ROUTER_ID);
+        checkArgument(!Strings.isNullOrEmpty(osRouter.getName()), ERR_NULL_ROUTER_NAME);
+
+        osRouterStore.createRouter(osRouter);
+        log.info(String.format(MSG_ROUTER, osRouter.getName(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateRouter(Router osRouter) {
+        checkNotNull(osRouter, ERR_NULL_ROUTER);
+        checkArgument(!Strings.isNullOrEmpty(osRouter.getId()), ERR_NULL_ROUTER_ID);
+        checkArgument(!Strings.isNullOrEmpty(osRouter.getName()), ERR_NULL_ROUTER_NAME);
+
+        osRouterStore.updateRouter(osRouter);
+        log.info(String.format(MSG_ROUTER, osRouter.getId(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removeRouter(String routerId) {
+        checkArgument(!Strings.isNullOrEmpty(routerId), ERR_NULL_ROUTER_ID);
+        synchronized (this) {
+            if (isRouterInUse(routerId)) {
+                final String error = String.format(MSG_ROUTER, routerId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            Router osRouter = osRouterStore.removeRouter(routerId);
+            if (osRouter != null) {
+                log.info(String.format(MSG_ROUTER, osRouter.getName(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void addRouterInterface(RouterInterface osIface) {
+        checkNotNull(osIface, ERR_NULL_IFACE);
+        checkArgument(!Strings.isNullOrEmpty(osIface.getId()), ERR_NULL_IFACE_ROUTER_ID);
+        checkArgument(!Strings.isNullOrEmpty(osIface.getPortId()), ERR_NULL_IFACE_PORT_ID);
+
+        osRouterStore.addRouterInterface(osIface);
+        log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateRouterInterface(RouterInterface osIface) {
+        checkNotNull(osIface, ERR_NULL_IFACE);
+        checkArgument(!Strings.isNullOrEmpty(osIface.getId()), ERR_NULL_IFACE_ROUTER_ID);
+        checkArgument(!Strings.isNullOrEmpty(osIface.getPortId()), ERR_NULL_IFACE_PORT_ID);
+
+        osRouterStore.updateRouterInterface(osIface);
+        log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removeRouterInterface(String osIfaceId) {
+        checkArgument(!Strings.isNullOrEmpty(osIfaceId), ERR_NULL_IFACE_PORT_ID);
+        synchronized (this) {
+            if (isRouterIfaceInUse(osIfaceId)) {
+                final String error = String.format(MSG_ROUTER, osIfaceId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            RouterInterface osIface = osRouterStore.removeRouterInterface(osIfaceId);
+            if (osIface != null) {
+                log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void createFloatingIp(NetFloatingIP osFloatingIp) {
+        checkNotNull(osFloatingIp, ERR_NULL_FLOATING);
+        checkArgument(!Strings.isNullOrEmpty(osFloatingIp.getId()), ERR_NULL_FLOATING_ID);
+
+        osRouterStore.createFloatingIp(osFloatingIp);
+        log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateFloatingIp(NetFloatingIP osFloatingIp) {
+        checkNotNull(osFloatingIp, ERR_NULL_FLOATING);
+        checkArgument(!Strings.isNullOrEmpty(osFloatingIp.getId()), ERR_NULL_FLOATING_ID);
+
+        osRouterStore.updateFloatingIp(osFloatingIp);
+        log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_UPDATED));
+    }
+
+    @Override
+    public void removeFloatingIp(String floatingIpId) {
+        checkArgument(!Strings.isNullOrEmpty(floatingIpId), ERR_NULL_FLOATING_ID);
+        synchronized (this) {
+            if (isFloatingIpInUse(floatingIpId)) {
+                final String error = String.format(MSG_FLOATING_IP, floatingIpId, ERR_IN_USE);
+                throw new IllegalStateException(error);
+            }
+            NetFloatingIP osFloatingIp = osRouterStore.removeFloatingIp(floatingIpId);
+            if (osFloatingIp != null) {
+                log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_REMOVED));
+            }
+        }
+    }
+
+    @Override
+    public void clear() {
+        osRouterStore.clear();
+    }
+
+    @Override
+    public Router router(String routerId) {
+        checkArgument(!Strings.isNullOrEmpty(routerId), ERR_NULL_ROUTER_ID);
+        return osRouterStore.router(routerId);
+    }
+
+    @Override
+    public Set<Router> routers() {
+        return osRouterStore.routers();
+    }
+
+    @Override
+    public RouterInterface routerInterface(String osIfaceId) {
+        checkArgument(!Strings.isNullOrEmpty(osIfaceId), ERR_NULL_IFACE_PORT_ID);
+        return osRouterStore.routerInterface(osIfaceId);
+    }
+
+    @Override
+    public Set<RouterInterface> routerInterfaces() {
+        return osRouterStore.routerInterfaces();
+    }
+
+    @Override
+    public Set<RouterInterface> routerInterfaces(String routerId) {
+        Set<RouterInterface> osIfaces = osRouterStore.routerInterfaces().stream()
+                .filter(iface -> Objects.equals(iface.getId(), routerId))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osIfaces);
+    }
+
+    @Override
+    public NetFloatingIP floatingIp(String floatingIpId) {
+        checkArgument(!Strings.isNullOrEmpty(floatingIpId), ERR_NULL_FLOATING_ID);
+        return osRouterStore.floatingIp(floatingIpId);
+    }
+
+    @Override
+    public Set<NetFloatingIP> floatingIps() {
+        return osRouterStore.floatingIps();
+    }
+
+    @Override
+    public Set<NetFloatingIP> floatingIps(String routerId) {
+        Set<NetFloatingIP> osFloatingIps = osRouterStore.floatingIps().stream()
+                .filter(fip -> Objects.equals(fip.getRouterId(), routerId))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osFloatingIps);
+    }
+
+    private boolean isRouterInUse(String routerId) {
+        // TODO add checking
+        return false;
+    }
+
+    private boolean isRouterIfaceInUse(String osIfaceId) {
+        // TODO add checking
+        return false;
+    }
+
+    private boolean isFloatingIpInUse(String floatingIpId) {
+        // TODO add checking
+        return false;
+    }
+
+    private class InternalRouterStoreDelegate implements OpenstackRouterStoreDelegate {
+
+        @Override
+        public void notify(OpenstackRouterEvent event) {
+            if (event != null) {
+                log.trace("send oepnstack routing event {}", event);
+                process(event);
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
new file mode 100644
index 0000000..5dee134
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle ARP requests from gateway nodes.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingArpHandler {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String DEVICE_OWNER_ROUTER_GW = "network:router_gateway";
+    private static final String DEVICE_OWNER_FLOATING_IP = "network:floatingip";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService osRouterService;
+
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    @Activate
+    protected void activate() {
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        eventExecutor.shutdown();
+        log.info("Stopped");
+    }
+
+    private void processArpPacket(PacketContext context, Ethernet ethernet) {
+        ARP arp = (ARP) ethernet.getPayload();
+        if (arp.getOpCode() == ARP.OP_REQUEST) {
+            if (log.isTraceEnabled()) {
+                log.trace("ARP request received from {} for {}",
+                        Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
+                        Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
+            }
+
+            IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+
+            MacAddress targetMac = null;
+
+            NetFloatingIP floatingIP = osRouterService.floatingIps().stream()
+                    .filter(ip -> ip.getFloatingIpAddress().equals(targetIp.toString()))
+                    .findAny().orElse(null);
+
+            if (floatingIP != null && floatingIP.getPortId() != null) {
+                targetMac = MacAddress.valueOf(osNetworkService.port(floatingIP.getPortId()).getMacAddress());
+            }
+
+            if (isExternalGatewaySourceIp(targetIp.getIp4Address())) {
+                targetMac = Constants.DEFAULT_GATEWAY_MAC;
+            }
+
+            if (targetMac == null) {
+                log.trace("Unknown target ARP request for {}, ignore it", targetIp);
+                return;
+            }
+
+            Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
+                    targetMac, ethernet);
+
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(context.inPacket().receivedFrom().port())
+                    .build();
+
+            packetService.emit(new DefaultOutboundPacket(
+                    context.inPacket().receivedFrom().deviceId(),
+                    treatment,
+                    ByteBuffer.wrap(ethReply.serialize())));
+
+            context.block();
+        } else if (arp.getOpCode() == ARP.OP_REPLY) {
+            PortNumber receivedPortNum = context.inPacket().receivedFrom().port();
+            log.debug("ARP reply ip: {}, mac: {}",
+                    Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+                    MacAddress.valueOf(arp.getSenderHardwareAddress()));
+            try {
+                if (receivedPortNum.equals(
+                        osNodeService.node(context.inPacket().receivedFrom().deviceId()).uplinkPortNum())) {
+                    osNetworkService.updateExternalPeerRouterMac(
+                            Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+                            MacAddress.valueOf(arp.getSenderHardwareAddress()));
+                }
+            } catch (Exception e) {
+                log.error("Exception occurred because of {}", e.toString());
+            }
+        }
+
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
+                    .stream().map(OpenstackNode::intgBridge)
+                    .collect(Collectors.toSet());
+
+            if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet != null &&
+                    ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                eventExecutor.execute(() -> processArpPacket(context, ethernet));
+            }
+        }
+    }
+
+    private boolean isExternalGatewaySourceIp(IpAddress targetIp) {
+        return osNetworkService.ports().stream()
+                .filter(osPort -> Objects.equals(osPort.getDeviceOwner(),
+                        DEVICE_OWNER_ROUTER_GW))
+                .flatMap(osPort -> osPort.getFixedIps().stream())
+                .anyMatch(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(targetIp));
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
new file mode 100644
index 0000000..b34ae9c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_EXTERNAL;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_INTERNAL;
+import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Handles OpenStack floating IP events.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingFloatingIpHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
+    private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type %s";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService osRouterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackFlowRuleService osFlowRuleService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final OpenstackRouterListener floatingIpLisener = new InternalFloatingIpListener();
+    private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+        osRouterService.addListener(floatingIpLisener);
+        osNodeService.addListener(osNodeListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNodeService.removeListener(osNodeListener);
+        osRouterService.removeListener(floatingIpLisener);
+        leadershipService.withdraw(appId.name());
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private void setFloatingIpRules(NetFloatingIP floatingIp, Port osPort,
+                                    boolean install) {
+        Network osNet = osNetworkService.network(osPort.getNetworkId());
+        if (osNet == null) {
+            final String error = String.format(ERR_FLOW + "no network(%s) exists",
+                    floatingIp.getFloatingIpAddress(),
+                    osPort.getNetworkId());
+            throw new IllegalStateException(error);
+        }
+
+        MacAddress srcMac = MacAddress.valueOf(osPort.getMacAddress());
+        InstancePort instPort = instancePortService.instancePort(srcMac);
+        if (instPort == null) {
+            final String error = String.format(ERR_FLOW + "no host(MAC:%s) found",
+                    floatingIp.getFloatingIpAddress(), srcMac);
+            throw new IllegalStateException(error);
+        }
+
+        setComputeNodeToGateway(instPort, osNet, install);
+        setDownstreamRules(floatingIp, osNet, instPort, install);
+        setUpstreamRules(floatingIp, osNet, instPort, install);
+    }
+
+    private void setComputeNodeToGateway(InstancePort instPort, Network osNet, boolean install) {
+        TrafficTreatment treatment;
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(instPort.ipAddress().toIpPrefix())
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        switch (osNet.getNetworkType()) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(osNet.getProviderSegID()));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(osNet.getProviderSegID()));
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE,
+                        osNet.getNetworkType().toString());
+                throw new IllegalStateException(error);
+        }
+
+        OpenstackNode selectedGatewayNode = selectGatewayNode();
+        if (selectedGatewayNode == null) {
+            return;
+        }
+        treatment = DefaultTrafficTreatment.builder()
+                .extension(buildExtension(
+                        deviceService,
+                        instPort.deviceId(),
+                        selectedGatewayNode.dataIp().getIp4Address()),
+                        instPort.deviceId())
+                .setOutput(osNodeService.node(instPort.deviceId()).tunnelPortNum())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                sBuilder.build(),
+                treatment,
+                PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE,
+                ROUTING_TABLE,
+                install);
+    }
+
+    private OpenstackNode selectGatewayNode() {
+        //TODO support multiple loadbalancing options.
+        return osNodeService.completeNodes(GATEWAY).stream().findAny().orElse(null);
+    }
+
+    private void setDownstreamRules(NetFloatingIP floatingIp, Network osNet,
+                                    InstancePort instPort, boolean install) {
+        OpenstackNode cNode = osNodeService.node(instPort.deviceId());
+        if (cNode == null) {
+            final String error = String.format("Cannot find openstack node for device %s",
+                    instPort.deviceId());
+            throw new IllegalStateException(error);
+        }
+        if (osNet.getNetworkType() == NetworkType.VXLAN && cNode.dataIp() == null) {
+            final String error = String.format(ERR_FLOW +
+                    "VXLAN mode is not ready for %s", floatingIp, cNode.hostname());
+            throw new IllegalStateException(error);
+        }
+        if (osNet.getNetworkType() == NetworkType.VLAN && cNode.vlanIntf() == null) {
+            final String error = String.format(ERR_FLOW +
+                    "VLAN mode is not ready for %s", floatingIp, cNode.hostname());
+            throw new IllegalStateException(error);
+        }
+
+        IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
+        TrafficSelector externalSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floating.toIpPrefix())
+                .build();
+
+        osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+            TrafficTreatment.Builder externalBuilder = DefaultTrafficTreatment.builder()
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(instPort.macAddress())
+                    .setIpDst(instPort.ipAddress().getIp4Address());
+
+            switch (osNet.getNetworkType()) {
+                case VXLAN:
+                    externalBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
+                            .extension(buildExtension(
+                                    deviceService,
+                                    gNode.intgBridge(),
+                                    cNode.dataIp().getIp4Address()),
+                                    gNode.intgBridge())
+                            .setOutput(gNode.tunnelPortNum());
+                    break;
+                case VLAN:
+                    externalBuilder.pushVlan()
+                            .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
+                            .setOutput(gNode.vlanPortNum());
+                    break;
+                default:
+                    final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+                            osNet.getNetworkType());
+                    throw new IllegalStateException(error);
+            }
+
+            osFlowRuleService.setRule(
+                    appId,
+                    gNode.intgBridge(),
+                    externalSelector,
+                    externalBuilder.build(),
+                    PRIORITY_FLOATING_EXTERNAL,
+                    GW_COMMON_TABLE,
+                    install);
+
+            // access from one VM to the others via floating IP
+            TrafficSelector internalSelector = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(floating.toIpPrefix())
+                    .matchInPort(gNode.tunnelPortNum())
+                    .build();
+
+            TrafficTreatment.Builder internalBuilder = DefaultTrafficTreatment.builder()
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(instPort.macAddress())
+                    .setIpDst(instPort.ipAddress().getIp4Address());
+
+            switch (osNet.getNetworkType()) {
+                case VXLAN:
+                    internalBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
+                            .extension(buildExtension(
+                                    deviceService,
+                                    gNode.intgBridge(),
+                                    cNode.dataIp().getIp4Address()),
+                                    gNode.intgBridge())
+                            .setOutput(PortNumber.IN_PORT);
+                    break;
+                case VLAN:
+                    internalBuilder.pushVlan()
+                            .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
+                            .setOutput(PortNumber.IN_PORT);
+                    break;
+                default:
+                    final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+                            osNet.getNetworkType());
+                    throw new IllegalStateException(error);
+            }
+
+            osFlowRuleService.setRule(
+                    appId,
+                    gNode.intgBridge(),
+                    internalSelector,
+                    internalBuilder.build(),
+                    PRIORITY_FLOATING_INTERNAL,
+                    GW_COMMON_TABLE,
+                    install);
+        });
+    }
+
+    private void setUpstreamRules(NetFloatingIP floatingIp, Network osNet,
+                                  InstancePort instPort, boolean install) {
+        IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(instPort.ipAddress().toIpPrefix());
+
+        switch (osNet.getNetworkType()) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.valueOf(osNet.getProviderSegID()));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(osNet.getProviderSegID()));
+                break;
+            default:
+                final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+                        osNet.getNetworkType());
+                throw new IllegalStateException(error);
+        }
+
+        MacAddress externalPeerRouterMac = externalPeerRouterMac(osNet);
+        if (externalPeerRouterMac == null) {
+            return;
+        }
+
+
+        osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                    .setIpSrc(floating.getIp4Address())
+                    .setEthSrc(instPort.macAddress())
+                    .setEthDst(externalPeerRouterMac);
+
+            if (osNet.getNetworkType().equals(NetworkType.VLAN)) {
+                tBuilder.popVlan();
+            }
+
+            osFlowRuleService.setRule(
+                    appId,
+                    gNode.intgBridge(),
+                    sBuilder.build(),
+                    tBuilder.setOutput(gNode.uplinkPortNum()).build(),
+                    PRIORITY_FLOATING_EXTERNAL,
+                    GW_COMMON_TABLE,
+                    install);
+        });
+    }
+
+    private MacAddress externalPeerRouterMac(Network network) {
+        if (network == null) {
+            return null;
+        }
+
+        Subnet subnet = osNetworkService.subnets(network.getId()).stream().findAny().orElse(null);
+
+        if (subnet == null) {
+            return null;
+        }
+
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter == null) {
+            return null;
+        }
+        if (osRouter.getExternalGatewayInfo() == null) {
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+        return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+    }
+    private class InternalFloatingIpListener implements OpenstackRouterListener {
+
+        @Override
+        public boolean isRelevant(OpenstackRouterEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            if (!Objects.equals(localNodeId, leader)) {
+                return false;
+            }
+            return event.floatingIp() != null;
+        }
+
+        @Override
+        public void event(OpenstackRouterEvent event) {
+            switch (event.type()) {
+                case OPENSTACK_FLOATING_IP_ASSOCIATED:
+                    eventExecutor.execute(() -> {
+                        NetFloatingIP osFip = event.floatingIp();
+                        associateFloatingIp(osFip);
+                        log.info("Associated floating IP {}:{}",
+                                osFip.getFloatingIpAddress(), osFip.getFixedIpAddress());
+                    });
+                    break;
+                case OPENSTACK_FLOATING_IP_DISASSOCIATED:
+                    eventExecutor.execute(() -> {
+                        NetFloatingIP osFip = event.floatingIp();
+                        disassociateFloatingIp(osFip, event.portId());
+                        log.info("Disassociated floating IP {}:{}",
+                                osFip.getFloatingIpAddress(), osFip.getFixedIpAddress());
+                    });
+                    break;
+                case OPENSTACK_FLOATING_IP_REMOVED:
+                    eventExecutor.execute(() -> {
+                        NetFloatingIP osFip = event.floatingIp();
+                        if (!Strings.isNullOrEmpty(osFip.getPortId())) {
+                            disassociateFloatingIp(osFip, osFip.getPortId());
+                        }
+                        log.info("Removed floating IP {}", osFip.getFloatingIpAddress());
+                    });
+                    break;
+                case OPENSTACK_FLOATING_IP_CREATED:
+                    eventExecutor.execute(() -> {
+                        NetFloatingIP osFip = event.floatingIp();
+                        if (!Strings.isNullOrEmpty(osFip.getPortId())) {
+                            associateFloatingIp(event.floatingIp());
+                        }
+                        log.info("Created floating IP {}", osFip.getFloatingIpAddress());
+                    });
+                    break;
+                case OPENSTACK_FLOATING_IP_UPDATED:
+                case OPENSTACK_ROUTER_CREATED:
+                case OPENSTACK_ROUTER_UPDATED:
+                case OPENSTACK_ROUTER_REMOVED:
+                case OPENSTACK_ROUTER_INTERFACE_ADDED:
+                case OPENSTACK_ROUTER_INTERFACE_UPDATED:
+                case OPENSTACK_ROUTER_INTERFACE_REMOVED:
+                default:
+                    // do nothing for the other events
+                    break;
+            }
+        }
+
+        private void associateFloatingIp(NetFloatingIP osFip) {
+            Port osPort = osNetworkService.port(osFip.getPortId());
+            if (osPort == null) {
+                final String error = String.format(ERR_FLOW + "port(%s) not found",
+                        osFip.getFloatingIpAddress(), osFip.getPortId());
+                throw new IllegalStateException(error);
+            }
+            // set floating IP rules only if the port is associated to a VM
+            if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+                setFloatingIpRules(osFip, osPort, true);
+            }
+        }
+
+        private void disassociateFloatingIp(NetFloatingIP osFip, String portId) {
+            Port osPort = osNetworkService.port(portId);
+            if (osPort == null) {
+                // FIXME when a port with floating IP removed without
+                // disassociation step, it can reach here
+                return;
+            }
+            // set floating IP rules only if the port is associated to a VM
+            if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+                setFloatingIpRules(osFip, osPort, false);
+            }
+        }
+    }
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            if (!Objects.equals(localNodeId, leader)) {
+                return false;
+            }
+            return event.subject().type() == GATEWAY;
+        }
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                    eventExecutor.execute(() -> {
+                        for (NetFloatingIP fip : osRouterService.floatingIps()) {
+                            if (Strings.isNullOrEmpty(fip.getPortId())) {
+                                continue;
+                            }
+                            Port osPort = osNetworkService.port(fip.getPortId());
+                            if (osPort == null) {
+                                log.warn("Failed to set floating IP {}", fip.getId());
+                                continue;
+                            }
+                            setFloatingIpRules(fip, osPort, true);
+                        }
+                    });
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_REMOVED:
+                case OPENSTACK_NODE_INCOMPLETE:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
new file mode 100644
index 0000000..11b3247
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
@@ -0,0 +1,1058 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNode.NetworkMode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ICMP_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_INTERNAL_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SWITCHING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ADMIN_RULE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Handles OpenStack router events.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MSG_ENABLED = "Enabled ";
+    private static final String MSG_DISABLED = "Disabled ";
+    private static final String ERR_SET_FLOWS = "Failed to set flows for router %s:";
+    private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
+    private static final boolean USE_STATEFUL_SNAT = false;
+
+    @Property(name = "useStatefulSnat", boolValue = USE_STATEFUL_SNAT,
+            label = "Use Stateful SNAT for source NATing")
+    private boolean useStatefulSnat = USE_STATEFUL_SNAT;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService osRouterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackFlowRuleService osFlowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
+    private final OpenstackRouterListener osRouterListener = new InternalRouterEventListener();
+    private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+        osNodeService.addListener(osNodeListener);
+        osRouterService.addListener(osRouterListener);
+        instancePortService.addListener(instancePortListener);
+        configService.registerProperties(getClass());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osRouterService.removeListener(osRouterListener);
+        osNodeService.removeListener(osNodeListener);
+        instancePortService.removeListener(instancePortListener);
+        leadershipService.withdraw(appId.name());
+        configService.unregisterProperties(getClass(), false);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, "useStatefulSnat");
+        if (flag == null) {
+            log.info("useStatefulSnat is not configured, " +
+                    "using current value of {}", useStatefulSnat);
+        } else {
+            useStatefulSnat = flag;
+            log.info("Configured. useStatefulSnat is {}",
+                    useStatefulSnat ? "enabled" : "disabled");
+        }
+
+        resetSnatRules();
+    }
+
+    private void routerUpdated(Router osRouter) {
+        ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+        osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+            Network network = osNetworkService.network(osNetworkService.subnet(iface.getSubnetId())
+                    .getNetworkId());
+            setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), !osRouter.isAdminStateUp());
+        });
+
+        if (exGateway == null) {
+            osNetworkService.deleteExternalPeerRouter(exGateway);
+            osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+                setSourceNat(osRouter, iface, false);
+            });
+        } else {
+            osNetworkService.deriveExternalPeerRouterMac(exGateway, osRouter);
+            osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+                setSourceNat(osRouter, iface, exGateway.isEnableSnat());
+            });
+        }
+    }
+
+    private void routerRemove(Router osRouter) {
+        osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+            Network network = osNetworkService.network(osNetworkService.subnet(iface.getSubnetId())
+                    .getNetworkId());
+            setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), false);
+        });
+    }
+
+    private void routerIfaceAdded(Router osRouter, RouterInterface osRouterIface) {
+        Subnet osSubnet = osNetworkService.subnet(osRouterIface.getSubnetId());
+        if (osSubnet == null) {
+            final String error = String.format(
+                    ERR_SET_FLOWS + "subnet %s does not exist",
+                    osRouterIface.getId(),
+                    osRouterIface.getSubnetId());
+            throw new IllegalStateException(error);
+        }
+
+        if (!osRouter.isAdminStateUp()) {
+            Network network = osNetworkService.network(osSubnet.getNetworkId());
+            setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), true);
+        }
+
+        setInternalRoutes(osRouter, osSubnet, true);
+        setGatewayIcmp(osSubnet, true);
+        ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+        if (exGateway != null && exGateway.isEnableSnat()) {
+            setSourceNat(osRouter, osRouterIface, true);
+        }
+        log.info("Connected subnet({}) to {}", osSubnet.getCidr(), osRouter.getName());
+    }
+
+    private void routerIfaceRemoved(Router osRouter, RouterInterface osRouterIface) {
+        Subnet osSubnet = osNetworkService.subnet(osRouterIface.getSubnetId());
+        if (osSubnet == null) {
+            final String error = String.format(
+                    ERR_SET_FLOWS + "subnet %s does not exist",
+                    osRouterIface.getId(),
+                    osRouterIface.getSubnetId());
+            throw new IllegalStateException(error);
+        }
+
+        if (!osRouter.isAdminStateUp()) {
+            Network network = osNetworkService.network(osSubnet.getNetworkId());
+            setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), false);
+        }
+
+        setInternalRoutes(osRouter, osSubnet, false);
+        setGatewayIcmp(osSubnet, false);
+        ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+        if (exGateway != null && exGateway.isEnableSnat()) {
+            setSourceNat(osRouter, osRouterIface, false);
+        }
+        log.info("Disconnected subnet({}) from {}", osSubnet.getCidr(), osRouter.getName());
+    }
+
+    private void setSourceNat(Router osRouter, RouterInterface routerIface, boolean install) {
+        Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+        Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+        osNodeService.completeNodes(COMPUTE).forEach(cNode -> {
+            setRulesToGateway(cNode, osNet.getProviderSegID(),
+                    IpPrefix.valueOf(osSubnet.getCidr()), osNet.getNetworkType(),
+                    install);
+        });
+
+        if (useStatefulSnat) {
+            setStatefulSnatRules(routerIface, install);
+        } else {
+            setReactiveSnatRules(routerIface, install);
+        }
+
+        final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+        log.info(updateStr + "external access for subnet({})", osSubnet.getCidr());
+    }
+
+    private void setStatefulSnatRules(RouterInterface routerIface, boolean install) {
+        Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+        Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+        Optional<Router> osRouter = osRouterService.routers().stream()
+                .filter(router -> osRouterService.routerInterfaces(routerIface.getId()) != null)
+                .findAny();
+
+        if (!osRouter.isPresent()) {
+            log.error("Cannot find a router for router interface {} ", routerIface);
+            return;
+        }
+        IpAddress natAddress = getGatewayIpAddress(osRouter.get());
+        if (natAddress == null) {
+            return;
+        }
+        String netId = osNetworkService.subnet(routerIface.getSubnetId()).getNetworkId();
+
+        osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+                .forEach(gwNode -> {
+                        instancePortService.instancePorts(netId).stream()
+                            .forEach(port -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+                                    Long.parseLong(osNet.getProviderSegID()),
+                                    IpPrefix.valueOf(port.ipAddress(), 32),
+                                    port.deviceId(),
+                                    install));
+
+                        setOvsNatIngressRule(gwNode.intgBridge(),
+                                IpPrefix.valueOf(natAddress, 32),
+                                Constants.DEFAULT_EXTERNAL_ROUTER_MAC, install);
+                        setOvsNatEgressRule(gwNode.intgBridge(),
+                                natAddress, Long.parseLong(osNet.getProviderSegID()),
+                                gwNode.patchPortNum(), install);
+                });
+    }
+
+    private void setReactiveSnatRules(RouterInterface routerIface, boolean install) {
+        Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+        Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+        osNodeService.completeNodes(GATEWAY)
+                .forEach(gwNode -> setRulesToController(
+                        gwNode.intgBridge(),
+                        osNet.getProviderSegID(),
+                        IpPrefix.valueOf(osSubnet.getCidr()),
+                        osNet.getNetworkType(),
+                        install));
+    }
+
+    private IpAddress getGatewayIpAddress(Router osRouter) {
+
+        String extNetId = osNetworkService.network(osRouter.getExternalGatewayInfo().getNetworkId()).getId();
+        Optional<Subnet> extSubnet = osNetworkService.subnets().stream()
+                .filter(subnet -> subnet.getNetworkId().equals(extNetId))
+                .findAny();
+
+        if (!extSubnet.isPresent()) {
+            log.error("Cannot find externel subnet for the router");
+            return null;
+        }
+
+        return IpAddress.valueOf(extSubnet.get().getGateway());
+    }
+
+    private void resetSnatRules() {
+        if (useStatefulSnat) {
+            osRouterService.routerInterfaces().forEach(
+                    routerIface -> {
+                        setReactiveSnatRules(routerIface, false);
+                        setStatefulSnatRules(routerIface, true);
+                    }
+            );
+        } else {
+            osRouterService.routerInterfaces().forEach(
+                    routerIface -> {
+                        setStatefulSnatRules(routerIface, false);
+                        setReactiveSnatRules(routerIface, true);
+                    }
+            );
+        }
+    }
+
+    private void setGatewayIcmp(Subnet osSubnet, boolean install) {
+        OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+        if (sourceNatGateway == null) {
+            return;
+        }
+
+        if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+            // do nothing if no gateway is set
+            return;
+        }
+
+        // take ICMP request to a subnet gateway through gateway node group
+        Network network = osNetworkService.network(osSubnet.getNetworkId());
+        switch (network.getNetworkType()) {
+            case VXLAN:
+                osNodeService.completeNodes(COMPUTE).stream()
+                        .filter(cNode -> cNode.dataIp() != null)
+                        .forEach(cNode -> setRulesToGatewayWithDstIp(
+                                cNode,
+                                sourceNatGateway,
+                                network.getProviderSegID(),
+                                IpAddress.valueOf(osSubnet.getGateway()),
+                                NetworkMode.VXLAN,
+                                install));
+                break;
+            case VLAN:
+                osNodeService.completeNodes(COMPUTE).stream()
+                        .filter(cNode -> cNode.vlanPortNum() != null)
+                        .forEach(cNode -> setRulesToGatewayWithDstIp(
+                                cNode,
+                                sourceNatGateway,
+                                network.getProviderSegID(),
+                                IpAddress.valueOf(osSubnet.getGateway()),
+                                NetworkMode.VLAN,
+                                install));
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        network.getNetworkType().toString());
+                throw new IllegalStateException(error);
+        }
+
+        IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+        osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+            setGatewayIcmpRule(
+                    gatewayIp,
+                    gNode.intgBridge(),
+                    install
+            );
+        });
+
+        final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+        log.debug(updateStr + "ICMP to {}", osSubnet.getGateway());
+    }
+
+    private void setInternalRoutes(Router osRouter, Subnet updatedSubnet, boolean install) {
+        Network updatedNetwork = osNetworkService.network(updatedSubnet.getNetworkId());
+        Set<Subnet> routableSubnets = routableSubnets(osRouter, updatedSubnet.getId());
+        String updatedSegmendId = getSegmentId(updatedSubnet);
+
+        // installs rule from/to my subnet intentionally to fix ICMP failure
+        // to my subnet gateway if no external gateway added to the router
+        osNodeService.completeNodes(COMPUTE).forEach(cNode -> {
+            setInternalRouterRules(
+                    cNode.intgBridge(),
+                    updatedSegmendId,
+                    updatedSegmendId,
+                    IpPrefix.valueOf(updatedSubnet.getCidr()),
+                    IpPrefix.valueOf(updatedSubnet.getCidr()),
+                    updatedNetwork.getNetworkType(),
+                    install
+            );
+
+            routableSubnets.forEach(subnet -> {
+                setInternalRouterRules(
+                        cNode.intgBridge(),
+                        updatedSegmendId,
+                        getSegmentId(subnet),
+                        IpPrefix.valueOf(updatedSubnet.getCidr()),
+                        IpPrefix.valueOf(subnet.getCidr()),
+                        updatedNetwork.getNetworkType(),
+                        install
+                );
+                setInternalRouterRules(
+                        cNode.intgBridge(),
+                        getSegmentId(subnet),
+                        updatedSegmendId,
+                        IpPrefix.valueOf(subnet.getCidr()),
+                        IpPrefix.valueOf(updatedSubnet.getCidr()),
+                        updatedNetwork.getNetworkType(),
+                        install
+                );
+            });
+        });
+
+
+        final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+        routableSubnets.forEach(subnet -> log.debug(
+                updateStr + "route between subnet:{} and subnet:{}",
+                subnet.getCidr(),
+                updatedSubnet.getCidr()));
+    }
+
+    private Set<Subnet> routableSubnets(Router osRouter, String osSubnetId) {
+        Set<Subnet> osSubnets = osRouterService.routerInterfaces(osRouter.getId())
+                .stream()
+                .filter(iface -> !Objects.equals(iface.getSubnetId(), osSubnetId))
+                .map(iface -> osNetworkService.subnet(iface.getSubnetId()))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osSubnets);
+    }
+
+    private String getSegmentId(Subnet osSubnet) {
+        return osNetworkService.network(osSubnet.getNetworkId()).getProviderSegID();
+    }
+
+    private void setGatewayIcmpRule(IpAddress gatewayIp, DeviceId deviceId, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIPDst(gatewayIp.getIp4Address().toIpPrefix())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_ICMP_RULE,
+                Constants.GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setInternalRouterRules(DeviceId deviceId, String srcSegmentId, String dstSegmentId,
+                                        IpPrefix srcSubnet, IpPrefix dstSubnet,
+                                        NetworkType networkType, boolean install) {
+        TrafficSelector selector;
+        TrafficTreatment treatment;
+        switch (networkType) {
+            case VXLAN:
+                selector = DefaultTrafficSelector.builder()
+                        .matchEthType(Ethernet.TYPE_IPV4)
+                        .matchTunnelId(Long.parseLong(srcSegmentId))
+                        .matchIPSrc(srcSubnet.getIp4Prefix())
+                        .matchIPDst(dstSubnet.getIp4Prefix())
+                        .build();
+
+                treatment = DefaultTrafficTreatment.builder()
+                        .setTunnelId(Long.parseLong(dstSegmentId))
+                        .transition(FORWARDING_TABLE)
+                        .build();
+
+                osFlowRuleService.setRule(
+                        appId,
+                        deviceId,
+                        selector,
+                        treatment,
+                        PRIORITY_INTERNAL_ROUTING_RULE,
+                        ROUTING_TABLE,
+                        install);
+
+                selector = DefaultTrafficSelector.builder()
+                        .matchEthType(Ethernet.TYPE_IPV4)
+                        .matchTunnelId(Long.parseLong(dstSegmentId))
+                        .matchIPSrc(srcSubnet.getIp4Prefix())
+                        .matchIPDst(dstSubnet.getIp4Prefix())
+                        .build();
+
+                treatment = DefaultTrafficTreatment.builder()
+                        .setTunnelId(Long.parseLong(dstSegmentId))
+                        .transition(FORWARDING_TABLE)
+                        .build();
+
+                osFlowRuleService.setRule(
+                        appId,
+                        deviceId,
+                        selector,
+                        treatment,
+                        PRIORITY_INTERNAL_ROUTING_RULE,
+                        ROUTING_TABLE,
+                        install);
+                break;
+            case VLAN:
+                selector = DefaultTrafficSelector.builder()
+                        .matchEthType(Ethernet.TYPE_IPV4)
+                        .matchVlanId(VlanId.vlanId(srcSegmentId))
+                        .matchIPSrc(srcSubnet.getIp4Prefix())
+                        .matchIPDst(dstSubnet.getIp4Prefix())
+                        .build();
+
+                treatment = DefaultTrafficTreatment.builder()
+                        .setVlanId(VlanId.vlanId(dstSegmentId))
+                        .transition(FORWARDING_TABLE)
+                        .build();
+
+                osFlowRuleService.setRule(
+                        appId,
+                        deviceId,
+                        selector,
+                        treatment,
+                        PRIORITY_INTERNAL_ROUTING_RULE,
+                        ROUTING_TABLE,
+                        install);
+
+                selector = DefaultTrafficSelector.builder()
+                        .matchEthType(Ethernet.TYPE_IPV4)
+                        .matchVlanId(VlanId.vlanId(dstSegmentId))
+                        .matchIPSrc(srcSubnet.getIp4Prefix())
+                        .matchIPDst(dstSubnet.getIp4Prefix())
+                        .build();
+
+                treatment = DefaultTrafficTreatment.builder()
+                        .setVlanId(VlanId.vlanId(dstSegmentId))
+                        .transition(FORWARDING_TABLE)
+                        .build();
+
+                osFlowRuleService.setRule(
+                        appId,
+                        deviceId,
+                        selector,
+                        treatment,
+                        PRIORITY_INTERNAL_ROUTING_RULE,
+                        ROUTING_TABLE,
+                        install);
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+    }
+
+    private void setRulesToGateway(OpenstackNode osNode, String segmentId, IpPrefix srcSubnet,
+                                   NetworkType networkType, boolean install) {
+        TrafficTreatment treatment;
+        OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+        if (sourceNatGateway == null) {
+            return;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcSubnet.getIp4Prefix())
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        switch (networkType) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(segmentId));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+        treatment = DefaultTrafficTreatment.builder()
+                .extension(buildExtension(
+                        deviceService,
+                        osNode.intgBridge(),
+                        sourceNatGateway.dataIp().getIp4Address()),
+                        osNode.intgBridge())
+                .setOutput(osNode.tunnelPortNum())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                osNode.intgBridge(),
+                sBuilder.build(),
+                treatment,
+                PRIORITY_EXTERNAL_ROUTING_RULE,
+                ROUTING_TABLE,
+                install);
+    }
+
+    private void setRulesForSnatIngressRule(DeviceId deviceId, Long vni, IpPrefix destVmIp,
+                                            DeviceId dstDeviceId, boolean install) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(destVmIp)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setTunnelId(vni)
+                .extension(buildExtension(
+                        deviceService,
+                        deviceId,
+                        osNodeService.node(dstDeviceId).dataIp().getIp4Address()),
+                        deviceId)
+                .setOutput(osNodeService.node(deviceId).tunnelPortNum())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_EXTERNAL_ROUTING_RULE,
+                Constants.GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setRulesToGatewayWithDstIp(OpenstackNode osNode, OpenstackNode sourceNatGateway,
+                                            String segmentId, IpAddress dstIp,
+                                            NetworkMode networkMode, boolean install) {
+        TrafficSelector selector;
+        if (networkMode.equals(NetworkMode.VXLAN)) {
+            selector = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchTunnelId(Long.valueOf(segmentId))
+                    .matchIPDst(dstIp.getIp4Address().toIpPrefix())
+                    .build();
+        } else {
+            selector = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchVlanId(VlanId.vlanId(segmentId))
+                    .matchIPDst(dstIp.getIp4Address().toIpPrefix())
+                    .build();
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .extension(buildExtension(
+                        deviceService,
+                        osNode.intgBridge(),
+                        sourceNatGateway.dataIp().getIp4Address()),
+                        osNode.intgBridge())
+                .setOutput(osNode.tunnelPortNum())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                osNode.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_SWITCHING_RULE,
+                ROUTING_TABLE,
+                install);
+    }
+
+    private void setOvsNatIngressRule(DeviceId deviceId, IpPrefix cidr, MacAddress dstMac, boolean install) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(cidr)
+                .build();
+
+        ExtensionTreatment natTreatment = RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId)
+                .commit(false)
+                .natAction(true)
+                .table((short) 0)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(dstMac)
+                .extension(natTreatment, deviceId)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_STATEFUL_SNAT_RULE,
+                GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setOvsNatEgressRule(DeviceId deviceId, IpAddress natAddress, long vni, PortNumber output,
+                                     boolean install) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchEthDst(DEFAULT_GATEWAY_MAC)
+                .matchTunnelId(vni)
+                .build();
+
+        ExtensionTreatment natTreatment = RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId)
+                .commit(true)
+                .natAction(true)
+                .natIp(natAddress)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .extension(natTreatment, deviceId)
+                .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
+                .setEthSrc(DEFAULT_GATEWAY_MAC)
+                .setOutput(output)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_STATEFUL_SNAT_RULE,
+                GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setRulesToController(DeviceId deviceId, String segmentId, IpPrefix srcSubnet,
+                                      NetworkType networkType, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcSubnet);
+
+        switch (networkType) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(segmentId))
+                        .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(segmentId))
+                        .matchEthDst(osNodeService.node(deviceId).vlanPortMac());
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                .setEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        if (networkType.equals(NetworkType.VLAN)) {
+            tBuilder.popVlan();
+        }
+
+        tBuilder.setOutput(PortNumber.CONTROLLER);
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_EXTERNAL_ROUTING_RULE,
+                GW_COMMON_TABLE,
+                install);
+
+
+        // Sends ICMP response to controller for SNATing ingress traffic
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_INTERNAL_ROUTING_RULE,
+                GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setRouterAdminRules(String segmentId, NetworkType networkType, boolean install) {
+        TrafficTreatment treatment;
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4);
+
+        switch (networkType) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(segmentId));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+        treatment = DefaultTrafficTreatment.builder()
+                .drop()
+                .build();
+
+        osNodeService.completeNodes().stream()
+                .filter(osNode -> osNode.type() == COMPUTE)
+                .forEach(osNode -> {
+                    osFlowRuleService.setRule(
+                            appId,
+                            osNode.intgBridge(),
+                            sBuilder.build(),
+                            treatment,
+                            PRIORITY_ADMIN_RULE,
+                            ROUTING_TABLE,
+                            install);
+                });
+    }
+
+    private class InternalRouterEventListener implements OpenstackRouterListener {
+
+        @Override
+        public boolean isRelevant(OpenstackRouterEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNodeId, leader);
+        }
+
+        // FIXME only one leader in the cluster should process
+        @Override
+        public void event(OpenstackRouterEvent event) {
+            switch (event.type()) {
+                case OPENSTACK_ROUTER_CREATED:
+                    log.debug("Router(name:{}, ID:{}) is created",
+                            event.subject().getName(),
+                            event.subject().getId());
+                    eventExecutor.execute(() -> routerUpdated(event.subject()));
+                    break;
+                case OPENSTACK_ROUTER_UPDATED:
+                    log.debug("Router(name:{}, ID:{}) is updated",
+                            event.subject().getName(),
+                            event.subject().getId());
+                    eventExecutor.execute(() -> routerUpdated(event.subject()));
+                    break;
+                case OPENSTACK_ROUTER_REMOVED:
+                    log.debug("Router(name:{}, ID:{}) is removed",
+                            event.subject().getName(),
+                            event.subject().getId());
+                    eventExecutor.execute(() -> routerRemove(event.subject()));
+                    break;
+                case OPENSTACK_ROUTER_INTERFACE_ADDED:
+                    log.debug("Router interface {} added to router {}",
+                            event.routerIface().getPortId(),
+                            event.routerIface().getId());
+                    eventExecutor.execute(() -> routerIfaceAdded(
+                            event.subject(),
+                            event.routerIface()));
+                    break;
+                case OPENSTACK_ROUTER_INTERFACE_UPDATED:
+                    log.debug("Router interface {} on {} updated",
+                            event.routerIface().getPortId(),
+                            event.routerIface().getId());
+                    break;
+                case OPENSTACK_ROUTER_INTERFACE_REMOVED:
+                    log.debug("Router interface {} removed from router {}",
+                            event.routerIface().getPortId(),
+                            event.routerIface().getId());
+                    eventExecutor.execute(() -> routerIfaceRemoved(
+                            event.subject(),
+                            event.routerIface()));
+                    break;
+                case OPENSTACK_ROUTER_GATEWAY_ADDED:
+                    log.debug("Router external gateway {} added", event.externalGateway().getNetworkId());
+                    break;
+                case OPENSTACK_ROUTER_GATEWAY_REMOVED:
+                    log.debug("Router external gateway {} removed", event.externalGateway().getNetworkId());
+                    break;
+                case OPENSTACK_FLOATING_IP_CREATED:
+                case OPENSTACK_FLOATING_IP_UPDATED:
+                case OPENSTACK_FLOATING_IP_REMOVED:
+                case OPENSTACK_FLOATING_IP_ASSOCIATED:
+                case OPENSTACK_FLOATING_IP_DISASSOCIATED:
+                default:
+                    // do nothing for the other events
+                    break;
+            }
+        }
+    }
+
+    private class InternalNodeEventListener implements OpenstackNodeListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNodeId, leader);
+        }
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode osNode = event.subject();
+
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                case OPENSTACK_NODE_INCOMPLETE:
+                    eventExecutor.execute(() -> {
+                        log.info("Reconfigure routers for {}", osNode.hostname());
+                        reconfigureRouters();
+                    });
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_REMOVED:
+                default:
+                    break;
+            }
+        }
+
+        private void reconfigureRouters() {
+            osRouterService.routers().forEach(osRouter -> {
+                routerUpdated(osRouter);
+                osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+                    routerIfaceAdded(osRouter, iface);
+                });
+            });
+        }
+    }
+
+    private class InternalInstancePortListener implements InstancePortListener {
+
+        @Override
+        public boolean isRelevant(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            return mastershipService.isLocalMaster(instPort.deviceId());
+        }
+
+        @Override
+        public void event(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            switch (event.type()) {
+                case OPENSTACK_INSTANCE_PORT_UPDATED:
+                case OPENSTACK_INSTANCE_PORT_DETECTED:
+                    eventExecutor.execute(() -> {
+                        log.info("RoutingHandler: Instance port detected MAC:{} IP:{}",
+                                instPort.macAddress(),
+                                instPort.ipAddress());
+                        instPortDetected(event.subject());
+                    });
+                    break;
+                case OPENSTACK_INSTANCE_PORT_VANISHED:
+                    eventExecutor.execute(() -> {
+                        log.info("RoutingHandler: Instance port vanished MAC:{} IP:{}",
+                                instPort.macAddress(),
+                                instPort.ipAddress());
+                        instPortRemoved(event.subject());
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void instPortDetected(InstancePort instPort) {
+            osNodeService.completeNodes(GATEWAY)
+                    .forEach(gwNode -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+                                    Long.parseLong(osNetworkService.network(instPort.networkId()).getProviderSegID()),
+                                    IpPrefix.valueOf(instPort.ipAddress(), 32),
+                                    instPort.deviceId(),
+                                    true));
+        }
+
+        private void instPortRemoved(InstancePort instPort) {
+            osNodeService.completeNodes(GATEWAY)
+                    .forEach(gwNode -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+                                    Long.parseLong(osNetworkService.network(instPort.networkId()).getProviderSegID()),
+                                    IpPrefix.valueOf(instPort.ipAddress(), 32),
+                                    instPort.deviceId(),
+                                    false));
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
new file mode 100644
index 0000000..13c9e18
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+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.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronIP;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Handles ICMP packet received from a gateway node.
+ * For a request for virtual network subnet gateway, it generates fake ICMP reply.
+ * For a request for the external network, it does source NAT with the public IP and
+ * forward the request to the external only if the requested virtual subnet has
+ * external connectivity.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingIcmpHandler {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String ERR_REQ = "Failed to handle ICMP request: ";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService osRouterService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+    private ConsistentMap<String, InstancePort> icmpInfoMap;
+
+    private static final KryoNamespace SERIALIZER_ICMP_MAP = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(InstancePort.class)
+            .register(HostBasedInstancePort.class)
+            .build();
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+
+        icmpInfoMap = storageService.<String, InstancePort>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_ICMP_MAP))
+                .withName("openstack-icmpmap")
+                .withApplicationId(appId)
+                .build();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
+        IPv4 ipPacket = (IPv4) ethernet.getPayload();
+        ICMP icmp = (ICMP) ipPacket.getPayload();
+        log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
+                        "dest MAC:{}, dest IP:{}",
+                ethernet.getSourceMAC(),
+                IpAddress.valueOf(ipPacket.getSourceAddress()),
+                ethernet.getDestinationMAC(),
+                IpAddress.valueOf(ipPacket.getDestinationAddress()));
+
+        switch (icmp.getIcmpType()) {
+            case ICMP.TYPE_ECHO_REQUEST:
+                handleEchoRequest(
+                        context.inPacket().receivedFrom().deviceId(),
+                        ethernet.getSourceMAC(),
+                        ipPacket,
+                        icmp);
+                context.block();
+                break;
+            case ICMP.TYPE_ECHO_REPLY:
+                handleEchoReply(ipPacket, icmp);
+                context.block();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
+                                   ICMP icmp) {
+        InstancePort instPort = instancePortService.instancePort(srcMac);
+        if (instPort == null) {
+            log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
+            return;
+        }
+
+        IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
+        Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
+        if (srcSubnet == null) {
+            log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
+            return;
+        }
+        if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
+            log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
+                    srcSubnet.getId(), srcSubnet.getCidr());
+            return;
+        }
+
+        MacAddress externalPeerRouterMac = externalPeerRouterMac(srcSubnet);
+        if (externalPeerRouterMac == null) {
+            log.trace(ERR_REQ + "failed to get external peer router mac");
+            return;
+        }
+
+        if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
+                srcSubnet)) {
+            // this is a request for the subnet gateway
+            processRequestForGateway(ipPacket, instPort);
+        } else {
+            // this is a request for the external network
+            IpAddress externalIp = getExternalIp(srcSubnet);
+            if (externalIp == null) {
+                return;
+            }
+
+            sendRequestForExternal(ipPacket, srcDevice, externalIp, externalPeerRouterMac(srcSubnet));
+            String icmpInfoKey = String.valueOf(getIcmpId(icmp))
+                    .concat(String.valueOf(externalIp.getIp4Address().toInt()))
+                    .concat(String.valueOf(ipPacket.getDestinationAddress()));
+            try {
+                icmpInfoMap.compute(icmpInfoKey, (id, existing) -> {
+                    checkArgument(existing == null, ERR_DUPLICATE);
+                    return instPort;
+                });
+            } catch (IllegalArgumentException e) {
+                log.warn("Exception occurred because of {}", e.toString());
+            }
+
+        }
+    }
+
+    private MacAddress externalPeerRouterMac(Subnet subnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter == null) {
+            return null;
+        }
+        if (osRouter.getExternalGatewayInfo() == null) {
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+        return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+    }
+
+    private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
+        String icmpInfoKey = String.valueOf(getIcmpId(icmp))
+                .concat(String.valueOf(ipPacket.getDestinationAddress()))
+                .concat(String.valueOf(ipPacket.getSourceAddress()));
+
+        if (icmpInfoMap.get(icmpInfoKey) != null) {
+            processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey).value());
+            icmpInfoMap.remove(icmpInfoKey);
+        } else {
+            log.warn("No ICMP Info for ICMP packet");
+        }
+    }
+
+    private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
+        Port osPort = osNetworkService.port(instance.portId());
+        IP fixedIp = osPort.getFixedIps().stream()
+                .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
+                .findAny().orElse(null);
+        if (fixedIp == null) {
+            return null;
+        }
+        return osNetworkService.subnet(fixedIp.getSubnetId());
+    }
+
+    private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
+                    srcSubnet.getId(), srcSubnet.getCidr());
+            return false;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
+                .stream()
+                .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
+                .map(IpAddress::valueOf)
+                .collect(Collectors.toSet());
+
+        return routableGateways.contains(dstIp);
+    }
+
+    private IpAddress getExternalIp(Subnet srcSubnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            final String error = String.format(ERR_REQ +
+                    "subnet(ID:%s, CIDR:%s) is not connected to any router",
+                    srcSubnet.getId(), srcSubnet.getCidr());
+            throw new IllegalStateException(error);
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter.getExternalGatewayInfo() == null) {
+            final String error = String.format(ERR_REQ +
+                    "router(ID:%s, name:%s) does not have external gateway",
+                    osRouter.getId(), osRouter.getName());
+            throw new IllegalStateException(error);
+        }
+
+        // TODO fix openstack4j for ExternalGateway provides external fixed IP list
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+        Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
+                .stream()
+                .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
+                .findAny().orElse(null);
+        if (exGatewayPort == null) {
+            final String error = String.format(ERR_REQ +
+                    "no external gateway port for router (ID:%s, name:%s)",
+                    osRouter.getId(), osRouter.getName());
+            throw new IllegalStateException(error);
+        }
+        Optional<NeutronIP> externalIpAddress = (Optional<NeutronIP>) exGatewayPort.getFixedIps().stream().findFirst();
+        if (!externalIpAddress.isPresent() || externalIpAddress.get().getIpAddress() == null) {
+            final String error = String.format(ERR_REQ +
+                            "no external gateway IP address for router (ID:%s, name:%s)",
+                    osRouter.getId(), osRouter.getName());
+            throw new IllegalStateException(error);
+        }
+
+        return IpAddress.valueOf(externalIpAddress.get().getIpAddress());
+    }
+
+    private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
+        ICMP icmpReq = (ICMP) ipPacket.getPayload();
+        icmpReq.setChecksum((short) 0);
+        icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
+
+        int destinationAddress = ipPacket.getSourceAddress();
+
+        ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
+                .setDestinationAddress(destinationAddress)
+                .resetChecksum();
+
+        ipPacket.setPayload(icmpReq);
+        Ethernet icmpReply = new Ethernet();
+        icmpReply.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(instPort.macAddress())
+                .setPayload(ipPacket);
+
+        sendReply(icmpReply, instPort);
+    }
+
+    private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice,
+                                        IpAddress srcNatIp, MacAddress externalRouterMac) {
+        ICMP icmpReq = (ICMP) ipPacket.getPayload();
+        icmpReq.resetChecksum();
+        ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
+        ipPacket.setPayload(icmpReq);
+
+        Ethernet icmpRequestEth = new Ethernet();
+        icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(externalRouterMac)
+                .setPayload(ipPacket);
+
+        OpenstackNode osNode = osNodeService.node(srcDevice);
+        if (osNode == null) {
+            final String error = String.format("Cannot find openstack node for %s",
+                    srcDevice);
+            throw new IllegalStateException(error);
+        }
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(osNode.uplinkPortNum())
+                .build();
+
+        OutboundPacket packet = new DefaultOutboundPacket(
+                srcDevice,
+                treatment,
+                ByteBuffer.wrap(icmpRequestEth.serialize()));
+
+        packetService.emit(packet);
+    }
+
+    private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
+        ICMP icmpReply = (ICMP) ipPacket.getPayload();
+        icmpReply.resetChecksum();
+
+        ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
+                .resetChecksum();
+        ipPacket.setPayload(icmpReply);
+
+        Ethernet icmpResponseEth = new Ethernet();
+        icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(instPort.macAddress())
+                .setPayload(ipPacket);
+
+        sendReply(icmpResponseEth, instPort);
+    }
+
+    private void sendReply(Ethernet icmpReply, InstancePort instPort) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(instPort.portNumber())
+                .build();
+
+        OutboundPacket packet = new DefaultOutboundPacket(
+                instPort.deviceId(),
+                treatment,
+                ByteBuffer.wrap(icmpReply.serialize()));
+
+        packetService.emit(packet);
+    }
+
+    private short getIcmpId(ICMP icmp) {
+        return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
+                    .stream().map(OpenstackNode::intgBridge)
+                    .collect(Collectors.toSet());
+
+            if (context.isHandled()) {
+                return;
+            } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                return;
+            }
+
+            IPv4 iPacket = (IPv4) ethernet.getPayload();
+            if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
+                eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
new file mode 100644
index 0000000..5ea8442
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TCP;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SNAT_RULE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle packets needs SNAT.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingSnatHandler {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String ERR_PACKETIN = "Failed to handle packet in: ";
+    private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
+    private static final long TIME_OUT_SNAT_PORT_MS = 120L * 1000L;
+    private static final int TP_PORT_MINIMUM_NUM = 65000;
+    private static final int TP_PORT_MAXIMUM_NUM = 65535;
+
+    private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService osRouterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackFlowRuleService osFlowRuleService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    private ConsistentMap<Integer, Long> allocatedPortNumMap;
+    private DistributedSet<Integer> unUsedPortNumSet;
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+        allocatedPortNumMap = storageService.<Integer, Long>consistentMapBuilder()
+                .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
+                .withName("openstackrouting-allocatedportnummap")
+                .withApplicationId(appId)
+                .build();
+
+        unUsedPortNumSet = storageService.<Integer>setBuilder()
+                .withName("openstackrouting-unusedportnumset")
+                .withSerializer(Serializer.using(KryoNamespaces.API))
+                .build()
+                .asDistributedSet();
+
+        initializeUnusedPortNumSet();
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
+    }
+
+    private void initializeUnusedPortNumSet() {
+        for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
+            if (!allocatedPortNumMap.containsKey(i)) {
+                unUsedPortNumSet.add(i);
+            }
+        }
+
+        clearPortNumMap();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        eventExecutor.shutdown();
+        log.info("Stopped");
+    }
+
+    private void processSnatPacket(PacketContext context, Ethernet eth) {
+        IPv4 iPacket = (IPv4) eth.getPayload();
+        InboundPacket packetIn = context.inPacket();
+
+        int patPort = getPortNum();
+
+        InstancePort srcInstPort = instancePortService.instancePort(eth.getSourceMAC());
+        if (srcInstPort == null) {
+            log.error(ERR_PACKETIN + "source host(MAC:{}) does not exist",
+                    eth.getSourceMAC());
+            return;
+        }
+
+        IpAddress srcIp = IpAddress.valueOf(iPacket.getSourceAddress());
+        Subnet srcSubnet = getSourceSubnet(srcInstPort, srcIp);
+        IpAddress externalGatewayIp = getExternalIp(srcSubnet);
+
+        if (externalGatewayIp == null) {
+            return;
+        }
+
+        MacAddress externalPeerRouterMac = externalPeerRouterMac(srcSubnet);
+        if (externalPeerRouterMac == null) {
+            return;
+        }
+
+        populateSnatFlowRules(context.inPacket(),
+                srcInstPort,
+                TpPort.tpPort(patPort),
+                externalGatewayIp, externalPeerRouterMac);
+
+        packetOut(eth.duplicate(),
+                packetIn.receivedFrom().deviceId(),
+                patPort,
+                externalGatewayIp, externalPeerRouterMac);
+    }
+
+    private MacAddress externalPeerRouterMac(Subnet subnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter == null) {
+            return null;
+        }
+        if (osRouter.getExternalGatewayInfo() == null) {
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+        return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+    }
+
+    private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
+        Port osPort = osNetworkService.port(instance.portId());
+        IP fixedIp = osPort.getFixedIps().stream()
+                .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
+                .findAny().orElse(null);
+        if (fixedIp == null) {
+            return null;
+        }
+        return osNetworkService.subnet(fixedIp.getSubnetId());
+    }
+
+    private IpAddress getExternalIp(Subnet srcSubnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            // this subnet is not connected to the router
+            log.trace(ERR_PACKETIN + "source subnet(ID:{}, CIDR:{}) has no router",
+                    srcSubnet.getId(), srcSubnet.getCidr());
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter.getExternalGatewayInfo() == null) {
+            // this router does not have external connectivity
+            log.trace(ERR_PACKETIN + "router({}) has no external gateway",
+                    osRouter.getName());
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+        if (!exGatewayInfo.isEnableSnat()) {
+            // SNAT is disabled in this router
+            log.trace(ERR_PACKETIN + "router({}) SNAT is disabled", osRouter.getName());
+            return null;
+        }
+
+        // TODO fix openstack4j for ExternalGateway provides external fixed IP list
+        Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
+                .stream()
+                .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
+                .findAny().orElse(null);
+        if (exGatewayPort == null) {
+            log.trace(ERR_PACKETIN + "no external gateway port for router({})",
+                    osRouter.getName());
+            return null;
+        }
+
+        return IpAddress.valueOf(exGatewayPort.getFixedIps().stream()
+                .findFirst().get().getIpAddress());
+    }
+
+    private void populateSnatFlowRules(InboundPacket packetIn, InstancePort srcInstPort,
+                                       TpPort patPort, IpAddress externalIp, MacAddress externalPeerRouterMac) {
+        Network osNet = osNetworkService.network(srcInstPort.networkId());
+        if (osNet == null) {
+            final String error = String.format(ERR_PACKETIN + "network %s not found",
+                    srcInstPort.networkId());
+            throw new IllegalStateException(error);
+        }
+
+        setDownstreamRules(srcInstPort,
+                osNet.getProviderSegID(),
+                osNet.getNetworkType(),
+                externalIp,
+                patPort,
+                packetIn);
+
+        setUpstreamRules(osNet.getProviderSegID(),
+                osNet.getNetworkType(),
+                externalIp,
+                externalPeerRouterMac,
+                patPort,
+                packetIn);
+    }
+
+    private void setDownstreamRules(InstancePort srcInstPort, String segmentId,
+                                    NetworkType networkType,
+                                    IpAddress externalIp,
+                                    TpPort patPort,
+                                    InboundPacket packetIn) {
+        IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
+        IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchIPDst(IpPrefix.valueOf(externalIp.getIp4Address(), 32))
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                .setEthDst(packetIn.parsed().getSourceMAC())
+                .setIpDst(internalIp);
+
+        switch (networkType) {
+            case VXLAN:
+                tBuilder.setTunnelId(Long.parseLong(segmentId));
+                break;
+            case VLAN:
+                tBuilder.pushVlan()
+                        .setVlanId(VlanId.vlanId(segmentId))
+                        .setEthSrc(DEFAULT_GATEWAY_MAC);
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
+                        .matchTcpDst(patPort);
+                tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
+                        .matchUdpDst(patPort);
+                tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
+                break;
+            default:
+                break;
+        }
+
+        OpenstackNode srcNode = osNodeService.node(srcInstPort.deviceId());
+        osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+            TrafficTreatment.Builder tmpBuilder =
+                    DefaultTrafficTreatment.builder(tBuilder.build());
+            switch (networkType) {
+                case VXLAN:
+                    tmpBuilder.extension(RulePopulatorUtil.buildExtension(
+                            deviceService,
+                            gNode.intgBridge(),
+                            srcNode.dataIp().getIp4Address()), gNode.intgBridge())
+                            .setOutput(gNode.tunnelPortNum());
+                    break;
+                case VLAN:
+                    tmpBuilder.setOutput(gNode.vlanPortNum());
+                    break;
+                default:
+                    final String error = String.format(ERR_UNSUPPORTED_NET_TYPE + "%s",
+                            networkType.toString());
+                    throw new IllegalStateException(error);
+            }
+
+            osFlowRuleService.setRule(
+                    appId,
+                    gNode.intgBridge(),
+                    sBuilder.build(),
+                    tmpBuilder.build(),
+                    PRIORITY_SNAT_RULE,
+                    GW_COMMON_TABLE,
+                    true);
+        });
+    }
+
+    private void setUpstreamRules(String segmentId, NetworkType networkType,
+                                  IpAddress externalIp, MacAddress externalPeerRouterMac,
+                                  TpPort patPort,
+                                  InboundPacket packetIn) {
+        IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
+
+        TrafficSelector.Builder sBuilder =  DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
+                .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        switch (networkType) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(segmentId));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+                tBuilder.popVlan();
+                break;
+            default:
+                final String error = String.format(ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
+                        .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
+                tBuilder.setTcpSrc(patPort)
+                        .setEthDst(externalPeerRouterMac);
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
+                        .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
+                tBuilder.setUdpSrc(patPort)
+                        .setEthDst(externalPeerRouterMac);
+                break;
+            default:
+                log.debug("Unsupported IPv4 protocol {}");
+                break;
+        }
+
+        tBuilder.setIpSrc(externalIp);
+        osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+            TrafficTreatment.Builder tmpBuilder =
+                    DefaultTrafficTreatment.builder(tBuilder.build());
+            tmpBuilder.setOutput(gNode.uplinkPortNum());
+
+            osFlowRuleService.setRule(
+                    appId,
+                    gNode.intgBridge(),
+                    sBuilder.build(),
+                    tmpBuilder.build(),
+                    PRIORITY_SNAT_RULE,
+                    GW_COMMON_TABLE,
+                    true);
+        });
+    }
+
+    private void packetOut(Ethernet ethPacketIn, DeviceId srcDevice, int patPort,
+                           IpAddress externalIp, MacAddress externalPeerRouterMac) {
+        IPv4 iPacket = (IPv4) ethPacketIn.getPayload();
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                tcpPacket.setSourcePort(patPort);
+                tcpPacket.resetChecksum();
+                tcpPacket.setParent(iPacket);
+                iPacket.setPayload(tcpPacket);
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                udpPacket.setSourcePort(patPort);
+                udpPacket.resetChecksum();
+                udpPacket.setParent(iPacket);
+                iPacket.setPayload(udpPacket);
+                break;
+            default:
+                log.trace("Temporally, this method can process UDP and TCP protocol.");
+                return;
+        }
+
+        iPacket.setSourceAddress(externalIp.toString());
+        iPacket.resetChecksum();
+        iPacket.setParent(ethPacketIn);
+        ethPacketIn.setDestinationMACAddress(externalPeerRouterMac);
+        ethPacketIn.setPayload(iPacket);
+        ethPacketIn.resetChecksum();
+
+        OpenstackNode srcNode = osNodeService.node(srcDevice);
+        if (srcNode == null) {
+            final String error = String.format("Cannot find openstack node for %s",
+                    srcDevice);
+            throw new IllegalStateException(error);
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(srcNode.uplinkPortNum()).build();
+        packetService.emit(new DefaultOutboundPacket(
+                srcDevice,
+                treatment,
+                ByteBuffer.wrap(ethPacketIn.serialize())));
+    }
+
+    private int getPortNum() {
+        if (unUsedPortNumSet.isEmpty()) {
+            clearPortNumMap();
+        }
+        int portNum = findUnusedPortNum();
+        if (portNum != 0) {
+            unUsedPortNumSet.remove(portNum);
+            allocatedPortNumMap.put(portNum, System.currentTimeMillis());
+        }
+        return portNum;
+    }
+
+    private int findUnusedPortNum() {
+        return unUsedPortNumSet.stream().findAny().orElse(0);
+    }
+
+    private void clearPortNumMap() {
+        allocatedPortNumMap.entrySet().forEach(e -> {
+            if (System.currentTimeMillis() - e.getValue().value() > TIME_OUT_SNAT_PORT_MS) {
+                allocatedPortNumMap.remove(e.getKey());
+                unUsedPortNumSet.add(e.getKey());
+            }
+        });
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            Set<DeviceId> gateways = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+                    .stream().map(OpenstackNode::intgBridge)
+                    .collect(Collectors.toSet());
+            if (context.isHandled()) {
+                return;
+            } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet eth = pkt.parsed();
+            if (eth == null || eth.getEtherType() == Ethernet.TYPE_ARP) {
+                return;
+            }
+
+            IPv4 iPacket = (IPv4) eth.getPayload();
+            switch (iPacket.getProtocol()) {
+                case IPv4.PROTOCOL_ICMP:
+                    break;
+                case IPv4.PROTOCOL_UDP:
+                    UDP udpPacket = (UDP) iPacket.getPayload();
+                    if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
+                            udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
+                        // don't process DHCP
+                        break;
+                    }
+                default:
+                    eventExecutor.execute(() -> processSnatPacket(context, eth));
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
new file mode 100644
index 0000000..23beaf9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
@@ -0,0 +1,794 @@
+/*
+* Copyright 2017-present Open Networking Foundation
+*
+* 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.JUMP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.CT_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.ERROR_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_DROP_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_HOOK_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_RULE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Populates flow rules to handle OpenStack SecurityGroups.
+ */
+@Component(immediate = true)
+public class OpenstackSecurityGroupHandler {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final boolean USE_SECURITY_GROUP = false;
+
+    @Property(name = "useSecurityGroup", boolValue = USE_SECURITY_GROUP,
+            label = "Apply OpenStack security group rule for VM traffic")
+    private boolean useSecurityGroup = USE_SECURITY_GROUP;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackSecurityGroupService securityGroupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackFlowRuleService osFlowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+    private final OpenstackNetworkListener portListener = new InternalOpenstackPortListener();
+    private final OpenstackSecurityGroupListener securityGroupListener = new InternalSecurityGroupListener();
+    private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private static final String PROTO_ICMP = "ICMP";
+    private static final String PROTO_TCP = "TCP";
+    private static final String PROTO_UDP = "UDP";
+    private static final String ETHTYPE_IPV4 = "IPV4";
+    private static final String EGRESS = "EGRESS";
+    private static final String INGRESS = "INGRESS";
+    private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
+
+    // We expose pipeline structure to SONA application considering removing pipeline soon.
+    private static final int GOTO_CONNTRACK_TABLE = 2;
+    private static final int GOTO_JUMP_TABLE = 3;
+
+    private static final int CT_COMMIT = 0;
+    private static final int CT_NO_COMMIT = 1;
+    private static final short CT_NO_RECIRC = -1;
+
+    private static final int ACTION_NONE = 0;
+    private static final int ACTION_DROP = -1;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        instancePortService.addListener(instancePortListener);
+        securityGroupService.addListener(securityGroupListener);
+        osNetService.addListener(portListener);
+        configService.registerProperties(getClass());
+        osNodeService.addListener(osNodeListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        instancePortService.removeListener(instancePortListener);
+        securityGroupService.removeListener(securityGroupListener);
+        osNetService.removeListener(portListener);
+        configService.unregisterProperties(getClass(), false);
+        osNodeService.removeListener(osNodeListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, "useSecurityGroup");
+        if (flag == null) {
+            log.info("useSecurityGroup is not configured, " +
+                    "using current value of {}", useSecurityGroup);
+        } else {
+            useSecurityGroup = flag;
+            log.info("Configured. useSecurityGroup is {}",
+                    useSecurityGroup ? "enabled" : "disabled");
+        }
+
+        securityGroupService.setSecurityGroupEnabled(useSecurityGroup);
+        resetSecurityGroupRules();
+    }
+
+    private void initializeConnTrackTable(DeviceId deviceId, boolean install) {
+
+        //table=1,ip,ct_state=-trk, actions=ct(table:2)
+        long ctState = RulePopulatorUtil.computeCtStateFlag(false, false, false);
+        long ctMask = RulePopulatorUtil.computeCtMaskFlag(true, false, false);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, (short) GOTO_CONNTRACK_TABLE,
+                ACTION_NONE, PRIORITY_CT_HOOK_RULE, install);
+
+        //table=2,ip,nw_dst=10.10.0.2,ct_state=+trk+est,action=goto_table:3
+        ctState = RulePopulatorUtil.computeCtStateFlag(true, false, true);
+        ctMask = RulePopulatorUtil.computeCtMaskFlag(true, false, true);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+                GOTO_JUMP_TABLE, PRIORITY_CT_RULE, install);
+
+        //table=2,ip,nw_dst=10.10.0.2,ct_state=+trk+new,action=drop
+        ctState = RulePopulatorUtil.computeCtStateFlag(true, true, false);
+        ctMask = RulePopulatorUtil.computeCtMaskFlag(true, true, false);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+                ACTION_DROP, PRIORITY_CT_DROP_RULE, install);
+    }
+
+    private void setSecurityGroupRules(InstancePort instPort, Port port, boolean install) {
+        port.getSecurityGroups().forEach(sgId -> {
+            SecurityGroup sg = securityGroupService.securityGroup(sgId);
+            if (sg == null) {
+                log.error("Security Group Not Found : {}", sgId);
+                return;
+            }
+            sg.getRules().forEach(sgRule -> updateSecurityGroupRule(instPort, port, sgRule, install));
+            final String action = install ? "Installed " : "Removed ";
+            log.debug(action + "security group rule ID : " + sgId);
+        });
+    }
+
+    private void updateSecurityGroupRule(InstancePort instPort, Port port, SecurityGroupRule sgRule, boolean install) {
+
+        if (sgRule.getRemoteGroupId() != null && !sgRule.getRemoteGroupId().isEmpty()) {
+            getRemoteInstPorts(port.getTenantId(), sgRule.getRemoteGroupId())
+                .forEach(rInstPort -> {
+                    populateSecurityGroupRule(sgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
+                    populateSecurityGroupRule(sgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
+
+                    SecurityGroupRule rSgRule = new NeutronSecurityGroupRule.SecurityGroupRuleConcreteBuilder()
+                            .from(sgRule)
+                            .direction(sgRule.getDirection().toUpperCase().equals(EGRESS) ? INGRESS : EGRESS).build();
+                    populateSecurityGroupRule(rSgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
+                    populateSecurityGroupRule(rSgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
+                });
+        } else {
+            populateSecurityGroupRule(sgRule, instPort, sgRule.getRemoteIpPrefix() == null ? IP_PREFIX_ANY :
+                    IpPrefix.valueOf(sgRule.getRemoteIpPrefix()), install);
+        }
+    }
+
+    private void populateSecurityGroupRule(SecurityGroupRule sgRule, InstancePort instPort,
+                                           IpPrefix remoteIp, boolean install) {
+        Set<TrafficSelector> selectors = buildSelectors(sgRule,
+                Ip4Address.valueOf(instPort.ipAddress().toInetAddress()), remoteIp);
+        if (selectors == null || selectors.isEmpty()) {
+            return;
+        }
+
+        selectors.forEach(selector -> {
+            osFlowRuleService.setRule(appId,
+                    instPort.deviceId(),
+                    selector,
+                    DefaultTrafficTreatment.builder().transition(JUMP_TABLE).build(),
+                    PRIORITY_ACL_RULE,
+                    ACL_TABLE,
+                    install);
+        });
+    }
+
+    /**
+     * Sets connection tracking rule using OVS extension commands.
+     * It is not so graceful, but I don't want to make it more general because it is going to be used
+     * only here. The following is the usage of the function.
+     *
+     * @param deviceId Device ID
+     * @param ctState ctState: please use RulePopulatorUtil.computeCtStateFlag() to build the value
+     * @param ctMask crMask: please use RulePopulatorUtil.computeCtMaskFlag() to build the value
+     * @param commit CT_COMMIT for commit action, CT_NO_COMMIT otherwise
+     * @param recircTable table number for recirculation after CT actions. CT_NO_RECIRC with no recirculation
+     * @param action Additional actions. ACTION_DROP, ACTION_NONE, GOTO_XXX_TABLE are supported.
+     * @param priority priority value for the rule
+     * @param install true for insertion, false for removal
+     */
+    private void setConnTrackRule(DeviceId deviceId, long ctState, long ctMask,
+                                  int commit, short recircTable,
+                                  int action, int priority, boolean install) {
+
+        ExtensionSelector esCtSate = RulePopulatorUtil.buildCtExtensionSelector(driverService, deviceId,
+                ctState, ctMask);
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .extension(esCtSate, deviceId)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .build();
+
+        TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
+
+        if (commit == CT_COMMIT || recircTable > 0) {
+            RulePopulatorUtil.NiriraConnTrackTreatmentBuilder natTreatmentBuilder =
+                    RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId);
+            natTreatmentBuilder.natAction(false);
+            if (commit == CT_COMMIT) {
+                natTreatmentBuilder.commit(true);
+            } else {
+                natTreatmentBuilder.commit(false);
+            }
+            if (recircTable > 0) {
+                natTreatmentBuilder.table(recircTable);
+            }
+            tb.extension(natTreatmentBuilder.build(), deviceId);
+        } else if (action == ACTION_DROP) {
+            tb.drop();
+        }
+
+        if (action != ACTION_NONE) {
+            tb.transition(action);
+        }
+
+        int tableType = ERROR_TABLE;
+        if (priority == PRIORITY_CT_RULE || priority == PRIORITY_CT_DROP_RULE) {
+            tableType = CT_TABLE;
+        } else if (priority == PRIORITY_CT_HOOK_RULE) {
+            tableType = ACL_TABLE;
+        } else {
+            log.error("Cannot an appropriate table for the conn track rule.");
+        }
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                tb.build(),
+                priority,
+                tableType,
+                install);
+    }
+
+    /**
+     * Returns a set of host IP addresses engaged with supplied security group ID.
+     * It only searches a VM in the same tenant boundary.
+     *
+     * @param tenantId tenant id
+     * @param sgId security group id
+     * @return set of ip addresses
+     */
+    private Set<InstancePort> getRemoteInstPorts(String tenantId, String sgId) {
+        Set<InstancePort> remoteInstPorts;
+
+        remoteInstPorts = osNetService.ports().stream()
+                .filter(port -> port.getTenantId().equals(tenantId))
+                .filter(port -> port.getSecurityGroups().contains(sgId))
+                .map(port -> instancePortService.instancePort(port.getId()))
+                .filter(instPort -> instPort != null && instPort.ipAddress() != null)
+                .collect(Collectors.toSet());
+
+        return Collections.unmodifiableSet(remoteInstPorts);
+    }
+
+    private Set<TrafficSelector> buildSelectors(SecurityGroupRule sgRule,
+                                                Ip4Address vmIp,
+                                                IpPrefix remoteIp) {
+        if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
+            // do nothing if the remote IP is my IP
+            return null;
+        }
+
+        Set<TrafficSelector> selectorSet = Sets.newHashSet();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+        if (sgRule.getPortRangeMax() != null && sgRule.getPortRangeMin() != null &&
+                sgRule.getPortRangeMin() < sgRule.getPortRangeMax()) {
+            Map<TpPort, TpPort> portRangeMatchMap = buildPortRangeMatches(sgRule.getPortRangeMin(),
+                    sgRule.getPortRangeMax());
+            portRangeMatchMap.entrySet().forEach(entry -> {
+
+                if (sgRule.getProtocol().toUpperCase().equals(PROTO_TCP)) {
+                    if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+                        sBuilder.matchTcpSrcMasked(entry.getKey(), entry.getValue());
+                    } else {
+                        sBuilder.matchTcpDstMasked(entry.getKey(), entry.getValue());
+                    }
+                } else if (sgRule.getProtocol().toUpperCase().equals(PROTO_UDP)) {
+                    if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+                        sBuilder.matchUdpSrcMasked(entry.getKey(), entry.getValue());
+                    } else {
+                        sBuilder.matchUdpDstMasked(entry.getKey(), entry.getValue());
+                    }
+                }
+
+                selectorSet.add(sBuilder.build());
+                }
+            );
+        } else {
+            selectorSet.add(sBuilder.build());
+        }
+
+        return selectorSet;
+    }
+
+    private void buildMatchs(TrafficSelector.Builder sBuilder, SecurityGroupRule sgRule,
+                             Ip4Address vmIp, IpPrefix remoteIp) {
+        buildMatchEthType(sBuilder, sgRule.getEtherType());
+        buildMatchDirection(sBuilder, sgRule.getDirection(), vmIp);
+        buildMatchProto(sBuilder, sgRule.getProtocol());
+        buildMatchPort(sBuilder, sgRule.getProtocol(), sgRule.getDirection(),
+                sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin(),
+                sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax());
+        buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
+        if (sgRule.getRemoteGroupId() != null && sgRule.getRemoteGroupId().isEmpty()) {
+            buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
+        }
+    }
+
+    private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+                                     String direction,
+                                     Ip4Address vmIp) {
+        if (direction.toUpperCase().equals(EGRESS)) {
+            sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
+        } else {
+            sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
+        }
+    }
+
+    private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
+        // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+        if (etherType != null && !Objects.equals(etherType, "null") &&
+                !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
+            log.debug("EthType {} is not supported yet in Security Group", etherType);
+        }
+    }
+
+    private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix, String direction) {
+        if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+            if (direction.toUpperCase().equals(EGRESS)) {
+                sBuilder.matchIPDst(remoteIpPrefix);
+            } else {
+                sBuilder.matchIPSrc(remoteIpPrefix);
+            }
+        }
+    }
+
+    private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
+        if (protocol != null) {
+            switch (protocol.toUpperCase()) {
+                case PROTO_ICMP:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
+                    break;
+                case PROTO_TCP:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
+                    break;
+                case PROTO_UDP:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
+                    break;
+                default:
+            }
+        }
+    }
+
+    private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol, String direction,
+                                int portMin, int portMax) {
+        if (portMin > 0 && portMax > 0 && portMin == portMax) {
+            if (protocol.toUpperCase().equals(PROTO_TCP)) {
+                if (direction.toUpperCase().equals(EGRESS)) {
+                    sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+                }
+            } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
+                if (direction.toUpperCase().equals(EGRESS)) {
+                    sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+                }
+            }
+        }
+    }
+
+    private void resetSecurityGroupRules() {
+
+        if (useSecurityGroup) {
+            osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+                    .forEach(node -> osFlowRuleService.setUpTableMissEntry(node.intgBridge(), ACL_TABLE));
+            securityGroupService.securityGroups().forEach(securityGroup ->
+                    securityGroup.getRules().forEach(this::securityGroupRuleAdded));
+            osNodeService.nodes().stream()
+                    .filter(node -> node.type().equals(OpenstackNode.NodeType.COMPUTE))
+                    .forEach(node -> initializeConnTrackTable(node .intgBridge(), true));
+        } else {
+            osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+                    .forEach(node -> osFlowRuleService.connectTables(node.intgBridge(), ACL_TABLE, JUMP_TABLE));
+            securityGroupService.securityGroups().forEach(securityGroup ->
+                    securityGroup.getRules().forEach(this::securityGroupRuleRemoved));
+            osNodeService.nodes().stream()
+                    .filter(node -> node.type().equals(OpenstackNode.NodeType.COMPUTE))
+                    .forEach(node -> initializeConnTrackTable(node.intgBridge(), false));
+        }
+
+        log.info("Reset security group info " + (useSecurityGroup ? " with " : " without") + " Security Group");
+    }
+
+    private void securityGroupRuleAdded(SecurityGroupRule sgRule) {
+        osNetService.ports().stream()
+                .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
+                .forEach(port -> {
+                    updateSecurityGroupRule(
+                            instancePortService.instancePort(port.getId()),
+                            port, sgRule, true);
+                    log.debug("Applied security group rule {} to port {}",
+                            sgRule.getId(), port.getId());
+                });
+    }
+
+    private void securityGroupRuleRemoved(SecurityGroupRule sgRule) {
+        osNetService.ports().stream()
+                .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
+                .forEach(port -> {
+                    updateSecurityGroupRule(
+                            instancePortService.instancePort(port.getId()),
+                            port, sgRule, false);
+                    log.debug("Removed security group rule {} from port {}",
+                            sgRule.getId(), port.getId());
+                });
+    }
+
+    private int binLower(String binStr, int bits) {
+        String outBin = binStr.substring(0, 16 - bits);
+        for (int i = 0; i < bits; i++) {
+            outBin += "0";
+        }
+
+        return Integer.parseInt(outBin, 2);
+    }
+
+    private int binHigher(String binStr, int bits) {
+        String outBin = binStr.substring(0, 16 - bits);
+        for (int i = 0; i < bits; i++) {
+            outBin += "1";
+        }
+
+        return Integer.parseInt(outBin, 2);
+    }
+
+    private int testMasks(String binStr, int start, int end) {
+        int mask = 0;
+        for (; mask <= 16; mask++) {
+            int maskStart = binLower(binStr, mask);
+            int maskEnd = binHigher(binStr, mask);
+            if (maskStart < start || maskEnd > end) {
+                return mask - 1;
+            }
+        }
+
+        return mask;
+    }
+
+    private String getMask(int bits) {
+        switch (bits) {
+            case 0:  return "ffff";
+            case 1:  return "fffe";
+            case 2:  return "fffc";
+            case 3:  return "fff8";
+            case 4:  return "fff0";
+            case 5:  return "ffe0";
+            case 6:  return "ffc0";
+            case 7:  return "ff80";
+            case 8:  return "ff00";
+            case 9:  return "fe00";
+            case 10: return "fc00";
+            case 11: return "f800";
+            case 12: return "f000";
+            case 13: return "e000";
+            case 14: return "c000";
+            case 15: return "8000";
+            case 16: return "0000";
+            default: return null;
+        }
+    }
+
+    private Map<TpPort, TpPort> buildPortRangeMatches(int portMin, int portMax) {
+
+        boolean processing = true;
+        int start = portMin;
+        Map<TpPort, TpPort> portMaskMap = Maps.newHashMap();
+        while (processing) {
+            String minStr = Integer.toBinaryString(start);
+            String binStrMinPadded = "0000000000000000".substring(minStr.length()) + minStr;
+
+            int mask = testMasks(binStrMinPadded, start, portMax);
+            int maskStart = binLower(binStrMinPadded, mask);
+            int maskEnd = binHigher(binStrMinPadded, mask);
+
+            log.debug("start : {} port/mask = {} / {} ", start, getMask(mask), maskStart);
+            portMaskMap.put(TpPort.tpPort(maskStart), TpPort.tpPort(Integer.parseInt(getMask(mask), 16)));
+
+            start = maskEnd + 1;
+            if (start > portMax) {
+                processing = false;
+            }
+        }
+
+        return portMaskMap;
+    }
+
+    private class InternalInstancePortListener implements InstancePortListener {
+
+        @Override
+        public boolean isRelevant(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            if (!useSecurityGroup) {
+                return false;
+            }
+            return mastershipService.isLocalMaster(instPort.deviceId());
+        }
+
+        @Override
+        public void event(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            switch (event.type()) {
+                case OPENSTACK_INSTANCE_PORT_UPDATED:
+                case OPENSTACK_INSTANCE_PORT_DETECTED:
+                    log.debug("Instance port detected MAC:{} IP:{}",
+                            instPort.macAddress(),
+                            instPort.ipAddress());
+                    eventExecutor.execute(() -> {
+                        setSecurityGroupRules(instPort,
+                                osNetService.port(event.subject().portId()),
+                                true);
+                    });
+                    break;
+                case OPENSTACK_INSTANCE_PORT_VANISHED:
+                    log.debug("Instance port vanished MAC:{} IP:{}",
+                            instPort.macAddress(),
+                            instPort.ipAddress());
+                    eventExecutor.execute(() -> {
+                        setSecurityGroupRules(instPort,
+                                osNetService.port(event.subject().portId()),
+                                false);
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalOpenstackPortListener implements OpenstackNetworkListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNetworkEvent event) {
+            if (event.port() == null || !Strings.isNullOrEmpty(event.port().getId())) {
+                return false;
+            }
+            if (event.securityGroupId() == null ||
+                    securityGroupService.securityGroup(event.securityGroupId()) == null) {
+                return false;
+            }
+            if (instancePortService.instancePort(event.port().getId()) == null) {
+                return false;
+            }
+            if (!useSecurityGroup) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void event(OpenstackNetworkEvent event) {
+            Port osPort = event.port();
+            InstancePort instPort = instancePortService.instancePort(osPort.getId());
+            SecurityGroup osSg = securityGroupService.securityGroup(event.securityGroupId());
+
+            switch (event.type()) {
+                case OPENSTACK_PORT_SECURITY_GROUP_ADDED:
+                    eventExecutor.execute(() -> {
+                        osSg.getRules().forEach(sgRule -> {
+                            updateSecurityGroupRule(instPort, osPort, sgRule, true);
+                        });
+                        log.info("Added security group {} to port {}",
+                                event.securityGroupId(), event.port().getId());
+                    });
+                    break;
+                case OPENSTACK_PORT_SECURITY_GROUP_REMOVED:
+                    eventExecutor.execute(() -> {
+                        osSg.getRules().forEach(sgRule -> {
+                            updateSecurityGroupRule(instPort, osPort, sgRule, false);
+                        });
+                        log.info("Removed security group {} from port {}",
+                                event.securityGroupId(), event.port().getId());
+                    });
+                    break;
+                default:
+                    // do nothing for the other events
+                    break;
+            }
+        }
+    }
+
+    private class InternalSecurityGroupListener implements OpenstackSecurityGroupListener {
+
+        @Override
+        public boolean isRelevant(OpenstackSecurityGroupEvent event) {
+            if (!useSecurityGroup) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void event(OpenstackSecurityGroupEvent event) {
+            switch (event.type()) {
+                case OPENSTACK_SECURITY_GROUP_RULE_CREATED:
+                    SecurityGroupRule securityGroupRuleToAdd = event.securityGroupRule();
+                    eventExecutor.execute(() -> {
+                        securityGroupRuleAdded(securityGroupRuleToAdd);
+                        log.info("Applied new security group rule {} to ports",
+                                securityGroupRuleToAdd.getId());
+                    });
+                    break;
+
+                case OPENSTACK_SECURITY_GROUP_RULE_REMOVED:
+                    SecurityGroupRule securityGroupRuleToRemove = event.securityGroupRule();
+                    eventExecutor.execute(() -> {
+                        securityGroupRuleRemoved(securityGroupRuleToRemove);
+                        log.info("Removed security group rule {} from ports",
+                                securityGroupRuleToRemove.getId());
+                    });
+                    break;
+                case OPENSTACK_SECURITY_GROUP_CREATED:
+                case OPENSTACK_SECURITY_GROUP_REMOVED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            if (!Objects.equals(localNodeId, leader)) {
+                return false;
+            }
+            return event.subject().type() == COMPUTE;
+        }
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode osNode = event.subject();
+
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                    eventExecutor.execute(() -> {
+                        try {
+                            if (useSecurityGroup) {
+                                initializeConnTrackTable(osNode.intgBridge(), true);
+                                log.warn("SG table initialization : {} is done", osNode.intgBridge());
+                            }
+                        } catch (IllegalArgumentException e) {
+                            log.error("ACL table initialization error : {}", e.getMessage());
+                        }
+                    });
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_REMOVED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_INCOMPLETE:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
new file mode 100644
index 0000000..f6a4de7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStore;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStoreDelegate;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack security
+ * groups.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackSecurityGroupManager
+        extends ListenerRegistry<OpenstackSecurityGroupEvent, OpenstackSecurityGroupListener>
+        implements OpenstackSecurityGroupAdminService, OpenstackSecurityGroupService {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String MSG_SG = "OpenStack security group %s %s";
+    private static final String MSG_SG_RULE = "OpenStack security group rule %s %s";
+
+    private static final String MSG_CREATED = "created";
+    private static final String MSG_REMOVED = "removed";
+
+    private static final String ERR_NULL_SG = "OpenStack security group cannot be null";
+    private static final String ERR_NULL_SG_ID = "OpenStack security group ID cannot be null";
+    private static final String ERR_NULL_SG_RULE = "OpenStack security group rule cannot be null";
+    private static final String ERR_NULL_SG_RULE_ID = "OpenStack security group rule ID cannot be null";
+    private static final String ERR_NOT_FOUND = "not found";
+    private static final String ERR_DUPLICATE = "already exist";
+
+    private boolean useSecurityGroup = false;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackSecurityGroupStore osSecurityGroupStore;
+
+    private final OpenstackSecurityGroupStoreDelegate delegate = new InternalSecurityGroupStoreDelegate();
+
+    @Activate
+    protected void activate() {
+        coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+        osSecurityGroupStore.setDelegate(delegate);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osSecurityGroupStore.unsetDelegate(delegate);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createSecurityGroup(SecurityGroup sg) {
+        checkNotNull(sg, ERR_NULL_SG);
+        checkArgument(!Strings.isNullOrEmpty(sg.getId()), ERR_NULL_SG_ID);
+
+        osSecurityGroupStore.createSecurityGroup(sg);
+        log.info(String.format(MSG_SG, sg.getId(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateSecurityGroup(SecurityGroup sg) {
+        checkNotNull(sg, ERR_NULL_SG);
+        checkArgument(!Strings.isNullOrEmpty(sg.getId()), ERR_NULL_SG_ID);
+
+        osSecurityGroupStore.updateSecurityGroup(sg);
+    }
+
+    @Override
+    public void removeSecurityGroup(String sgId) {
+        checkNotNull(sgId, ERR_NULL_SG_ID);
+
+        osSecurityGroupStore.removeSecurityGroup(sgId);
+        log.info(String.format(MSG_SG, sgId, MSG_REMOVED));
+    }
+
+    @Override
+    public void createSecurityGroupRule(SecurityGroupRule sgRule) {
+        checkNotNull(sgRule, ERR_NULL_SG_RULE);
+        checkArgument(!Strings.isNullOrEmpty(sgRule.getId()), ERR_NULL_SG_RULE_ID);
+        checkArgument(!Strings.isNullOrEmpty(sgRule.getSecurityGroupId()), ERR_NULL_SG_ID);
+
+        synchronized (this) {
+            SecurityGroup sg = securityGroup(sgRule.getSecurityGroupId());
+            if (sg == null) {
+                final String error = String.format(MSG_SG, sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+                throw new IllegalStateException(error);
+            }
+            if (sg.getRules().stream().anyMatch(rule -> Objects.equals(rule.getId(), sgRule.getId()))) {
+                final String error = String.format(MSG_SG_RULE,
+                        sgRule.getSecurityGroupId(), ERR_DUPLICATE);
+                throw new IllegalStateException(error);
+            }
+
+            // FIXME we cannot add element to extend list
+            List updatedSgRules = sg.getRules();
+            updatedSgRules.add(sgRule);
+            SecurityGroup updatedSg = NeutronSecurityGroup.builder().from(sg).build();
+            osSecurityGroupStore.updateSecurityGroup(updatedSg);
+        }
+
+        log.info(String.format(MSG_SG_RULE, sgRule.getId(), MSG_CREATED));
+    }
+
+    @Override
+    public void removeSecurityGroupRule(String sgRuleId) {
+        checkArgument(!Strings.isNullOrEmpty(sgRuleId), ERR_NULL_SG_RULE_ID);
+
+        synchronized (this) {
+            SecurityGroupRule sgRule = securityGroupRule(sgRuleId);
+            if (sgRule == null) {
+                final String error = String.format(MSG_SG_RULE, sgRuleId, ERR_NOT_FOUND);
+                throw new IllegalStateException(error);
+            }
+
+            SecurityGroup sg = securityGroup(sgRule.getSecurityGroupId());
+            if (sg == null) {
+                final String error = String.format(MSG_SG, sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+                throw new IllegalStateException(error);
+            }
+
+            if (sg.getRules().stream().noneMatch(rule -> Objects.equals(rule.getId(), sgRule.getId()))) {
+                final String error = String.format(MSG_SG_RULE,
+                        sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+                throw new IllegalStateException(error);
+            }
+
+            // FIXME we cannot handle the element of extend list as a specific class object
+            List updatedSgRules = sg.getRules();
+            updatedSgRules.removeIf(r -> ((SecurityGroupRule) r).getId().equals(sgRuleId));
+            SecurityGroup updatedSg = NeutronSecurityGroup.builder().from(sg).build();
+            osSecurityGroupStore.updateSecurityGroup(updatedSg);
+        }
+
+        log.info(String.format(MSG_SG_RULE, sgRuleId, MSG_REMOVED));
+    }
+
+    @Override
+    public Set<SecurityGroup> securityGroups() {
+        return osSecurityGroupStore.securityGroups();
+    }
+
+    @Override
+    public SecurityGroup securityGroup(String sgId) {
+        checkArgument(!Strings.isNullOrEmpty(sgId), ERR_NULL_SG_ID);
+        return osSecurityGroupStore.securityGroup(sgId);
+    }
+
+    @Override
+    public boolean isSecurityGroupEnabled() {
+        return useSecurityGroup;
+    }
+
+    @Override
+    public void setSecurityGroupEnabled(boolean option) {
+        useSecurityGroup = option;
+    }
+
+    @Override
+    public void clear() {
+        osSecurityGroupStore.clear();
+    }
+
+    private SecurityGroupRule securityGroupRule(String sgRuleId) {
+        return osSecurityGroupStore.securityGroups().stream()
+                .flatMap(sg -> sg.getRules().stream())
+                .filter(sgRule -> Objects.equals(sgRule.getId(), sgRuleId))
+                .findFirst().orElse(null);
+    }
+
+    private class InternalSecurityGroupStoreDelegate implements OpenstackSecurityGroupStoreDelegate {
+
+        @Override
+        public void notify(OpenstackSecurityGroupEvent event) {
+            if (event != null) {
+                log.trace("send openstack security group event {}", event);
+                process(event);
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
new file mode 100644
index 0000000..a8bec5f
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
@@ -0,0 +1,276 @@
+/*
+* Copyright 2016-present Open Networking Foundation
+*
+* 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+
+/**
+ * Handles ARP packet from VMs.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingArpHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String GATEWAY_MAC = "gatewayMac";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackNetworkService osNetworkService;
+
+    @Property(name = GATEWAY_MAC, value = DEFAULT_GATEWAY_MAC_STR,
+            label = "Fake MAC address for virtual network subnet gateway")
+    private String gatewayMac = DEFAULT_GATEWAY_MAC_STR;
+
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final InternalOpenstackNetworkListener osNetworkListener =
+            new InternalOpenstackNetworkListener();
+    private final Set<IpAddress> gateways = Sets.newConcurrentHashSet();
+
+    private ApplicationId appId;
+
+    @Activate
+    void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        configService.registerProperties(getClass());
+        osNetworkService.addListener(osNetworkListener);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        osNetworkService.subnets().forEach(this::addSubnetGateway);
+        requestPacket();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        osNetworkService.removeListener(osNetworkListener);
+        configService.unregisterProperties(getClass(), false);
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        String updatedMac;
+
+        updatedMac = Tools.get(properties, GATEWAY_MAC);
+        if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(gatewayMac)) {
+            gatewayMac = updatedMac;
+        }
+
+        log.info("Modified");
+    }
+
+    private void requestPacket() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .build();
+
+        packetService.requestPackets(
+                selector,
+                PacketPriority.CONTROL,
+                appId);
+    }
+
+    private void addSubnetGateway(Subnet osSubnet) {
+        if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+            return;
+        }
+        IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+        gateways.add(gatewayIp);
+        log.debug("Added ARP proxy entry IP:{}", gatewayIp);
+    }
+
+    private void removeSubnetGateway(Subnet osSubnet) {
+        if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+            return;
+        }
+        IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+        gateways.remove(gatewayIp);
+        log.debug("Removed ARP proxy entry IP:{}", gatewayIp);
+    }
+
+    /**
+     * Processes ARP request packets.
+     * It checks if the target IP is owned by a known host first and then ask to
+     * OpenStack if it's not. This ARP proxy does not support overlapping IP.
+     *
+     * @param context   packet context
+     * @param ethPacket ethernet packet
+     */
+    private void processPacketIn(PacketContext context, Ethernet ethPacket) {
+        ARP arpPacket = (ARP) ethPacket.getPayload();
+        if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+            return;
+        }
+
+        InstancePort srcInstPort = instancePortService.instancePort(ethPacket.getSourceMAC());
+        if (srcInstPort == null) {
+            log.trace("Failed to find source instance port(MAC:{})",
+                    ethPacket.getSourceMAC());
+            return;
+        }
+
+        IpAddress targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+        MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
+                getMacFromHostOpenstack(targetIp, srcInstPort.networkId());
+        if (replyMac == MacAddress.NONE) {
+            log.trace("Failed to find MAC address for {}", targetIp);
+            return;
+        }
+
+        Ethernet ethReply = ARP.buildArpReply(
+                targetIp.getIp4Address(),
+                replyMac,
+                ethPacket);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(context.inPacket().receivedFrom().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                context.inPacket().receivedFrom().deviceId(),
+                treatment,
+                ByteBuffer.wrap(ethReply.serialize())));
+    }
+
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * instance port service.
+     *
+     * @param targetIp target ip
+     * @param osNetId  openstack network id of the source instance port
+     * @return mac address, or none mac address if it fails to find the mac
+     */
+    private MacAddress getMacFromHostOpenstack(IpAddress targetIp, String osNetId) {
+        checkNotNull(targetIp);
+
+        InstancePort instPort = instancePortService.instancePort(targetIp, osNetId);
+        if (instPort != null) {
+            log.trace("Found MAC from host service for {}", targetIp);
+            return instPort.macAddress();
+        } else {
+            return MacAddress.NONE;
+        }
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+                return;
+            }
+            processPacketIn(context, ethPacket);
+        }
+    }
+
+    private class InternalOpenstackNetworkListener implements OpenstackNetworkListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNetworkEvent event) {
+            Subnet osSubnet = event.subnet();
+            if (osSubnet == null) {
+                return false;
+            }
+            return !Strings.isNullOrEmpty(osSubnet.getGateway());
+        }
+
+        @Override
+        public void event(OpenstackNetworkEvent event) {
+            switch (event.type()) {
+                case OPENSTACK_SUBNET_CREATED:
+                case OPENSTACK_SUBNET_UPDATED:
+                    addSubnetGateway(event.subnet());
+                    break;
+                case OPENSTACK_SUBNET_REMOVED:
+                    removeSubnetGateway(event.subnet());
+                    break;
+                case OPENSTACK_NETWORK_CREATED:
+                case OPENSTACK_NETWORK_UPDATED:
+                case OPENSTACK_NETWORK_REMOVED:
+                case OPENSTACK_PORT_CREATED:
+                case OPENSTACK_PORT_UPDATED:
+                case OPENSTACK_PORT_REMOVED:
+                default:
+                    // do nothing for the other events
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
new file mode 100644
index 0000000..12e98e9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.List;
+
+import static org.onlab.packet.DHCP.DHCPOptionCode.*;
+import static org.onlab.packet.DHCP.MsgType.DHCPACK;
+import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles DHCP requests for the virtual instances.
+ */
+@Component(immediate = true)
+public class OpenstackSwitchingDhcpHandler {
+    protected final Logger log = getLogger(getClass());
+
+    private static final String DHCP_SERVER_MAC = "dhcpServerMac";
+    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
+    private static final byte PACKET_TTL = (byte) 127;
+    // TODO add MTU, static route option codes to ONOS DHCP and remove here
+    private static final byte DHCP_OPTION_MTU = (byte) 26;
+    private static final byte[] DHCP_DATA_LEASE_INFINITE =
+            ByteBuffer.allocate(4).putInt(-1).array();
+    private static final byte[] DHCP_DATA_MTU_DEFAULT =
+            ByteBuffer.allocate(2).putShort((short) 1450).array();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Property(name = DHCP_SERVER_MAC, value = DEFAULT_GATEWAY_MAC_STR,
+            label = "Fake MAC address for virtual network subnet gateway")
+    private String dhcpServerMac = DEFAULT_GATEWAY_MAC_STR;
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+        configService.registerProperties(getClass());
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        requestPackets();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        cancelPackets();
+        packetService.removeProcessor(packetProcessor);
+        configService.unregisterProperties(getClass(), false);
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        String updatedMac;
+
+        updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
+        if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
+            dhcpServerMac = updatedMac;
+        }
+
+        log.info("Modified");
+    }
+
+    private void requestPackets() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+                .build();
+        packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
+    }
+
+    private void cancelPackets() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+                .build();
+        packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
+                return;
+            }
+            IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
+            if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
+                return;
+            }
+            UDP udpPacket = (UDP) ipv4Packet.getPayload();
+            if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
+                    udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
+                return;
+            }
+
+            DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+            processDhcp(context, dhcpPacket);
+        }
+
+        private void processDhcp(PacketContext context, DHCP dhcpPacket) {
+            if (dhcpPacket == null) {
+                log.trace("DHCP packet without payload received, do nothing");
+                return;
+            }
+
+            DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
+            if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
+                log.trace("Malformed DHCP packet received, ignore it");
+                return;
+            }
+
+            MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
+            InstancePort reqInstPort = instancePortService.instancePort(clientMac);
+            if (reqInstPort == null) {
+                log.trace("Failed to find host(MAC:{})", clientMac);
+                return;
+            }
+            Ethernet ethPacket = context.inPacket().parsed();
+            switch (inPacketType) {
+                case DHCPDISCOVER:
+                    log.trace("DHCP DISCOVER received from {}", clientMac);
+                    Ethernet discoverReply = buildReply(
+                            ethPacket,
+                            (byte) DHCPOFFER.getValue(),
+                            reqInstPort);
+                    sendReply(context, discoverReply);
+                    log.trace("DHCP OFFER({}) is sent for {}",
+                              reqInstPort.ipAddress(), clientMac);
+                    break;
+                case DHCPREQUEST:
+                    log.trace("DHCP REQUEST received from {}", clientMac);
+                    Ethernet requestReply = buildReply(
+                            ethPacket,
+                            (byte) DHCPACK.getValue(),
+                            reqInstPort);
+                    sendReply(context, requestReply);
+                    log.trace("DHCP ACK({}) is sent for {}",
+                              reqInstPort.ipAddress(), clientMac);
+                    break;
+                case DHCPRELEASE:
+                    log.trace("DHCP RELEASE received from {}", clientMac);
+                    // do nothing
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
+            DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
+            if (optType == null) {
+                log.trace("DHCP packet with no message type, ignore it");
+                return null;
+            }
+
+            DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
+            if (inPacketType == null) {
+                log.trace("DHCP packet with no packet type, ignore it");
+            }
+            return inPacketType;
+        }
+
+        private Ethernet buildReply(Ethernet ethRequest, byte packetType,
+                                    InstancePort reqInstPort) {
+            Port osPort = osNetworkService.port(reqInstPort.portId());
+            // pick one IP address to make a reply
+            IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
+            Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
+
+            Ethernet ethReply = new Ethernet();
+            ethReply.setSourceMACAddress(dhcpServerMac);
+            ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
+            ethReply.setEtherType(Ethernet.TYPE_IPV4);
+
+            IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
+            IPv4 ipv4Reply = new IPv4();
+            ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
+            ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
+            ipv4Reply.setTtl(PACKET_TTL);
+
+            UDP udpRequest = (UDP) ipv4Request.getPayload();
+            UDP udpReply = new UDP();
+            udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
+            udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
+
+            DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
+            DHCP dhcpReply = buildDhcpReply(
+                    dhcpRequest,
+                    packetType,
+                    reqInstPort.ipAddress().getIp4Address(),
+                    osSubnet);
+
+            udpReply.setPayload(dhcpReply);
+            ipv4Reply.setPayload(udpReply);
+            ethReply.setPayload(ipv4Reply);
+
+            return ethReply;
+        }
+
+        private void sendReply(PacketContext context, Ethernet ethReply) {
+            if (ethReply == null) {
+                return;
+            }
+            ConnectPoint srcPoint = context.inPacket().receivedFrom();
+            TrafficTreatment treatment = DefaultTrafficTreatment
+                    .builder()
+                    .setOutput(srcPoint.port())
+                    .build();
+
+            packetService.emit(new DefaultOutboundPacket(
+                    srcPoint.deviceId(),
+                    treatment,
+                    ByteBuffer.wrap(ethReply.serialize())));
+            context.block();
+        }
+
+        private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
+                                    Subnet osSubnet) {
+            Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
+            int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
+
+            DHCP dhcpReply = new DHCP();
+            dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
+            dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
+            dhcpReply.setHardwareAddressLength((byte) 6);
+            dhcpReply.setTransactionId(request.getTransactionId());
+            dhcpReply.setFlags(request.getFlags());
+            dhcpReply.setYourIPAddress(yourIp.toInt());
+            dhcpReply.setServerIPAddress(gatewayIp.toInt());
+            dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
+
+            List<DhcpOption> options = Lists.newArrayList();
+            // message type
+            DhcpOption option = new DhcpOption();
+            option.setCode(OptionCode_MessageType.getValue());
+            option.setLength((byte) 1);
+            byte[] optionData = {msgType};
+            option.setData(optionData);
+            options.add(option);
+
+            // server identifier
+            option = new DhcpOption();
+            option.setCode(OptionCode_DHCPServerIp.getValue());
+            option.setLength((byte) 4);
+            option.setData(gatewayIp.toOctets());
+            options.add(option);
+
+            // lease time
+            option = new DhcpOption();
+            option.setCode(OptionCode_LeaseTime.getValue());
+            option.setLength((byte) 4);
+            option.setData(DHCP_DATA_LEASE_INFINITE);
+            options.add(option);
+
+            // subnet mask
+            Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
+            option = new DhcpOption();
+            option.setCode(OptionCode_SubnetMask.getValue());
+            option.setLength((byte) 4);
+            option.setData(subnetMask.toOctets());
+            options.add(option);
+
+            // broadcast address
+            Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
+            option = new DhcpOption();
+            option.setCode(OptionCode_BroadcastAddress.getValue());
+            option.setLength((byte) 4);
+            option.setData(broadcast.toOctets());
+            options.add(option);
+
+            // domain server
+            option = new DhcpOption();
+            option.setCode(OptionCode_DomainServer.getValue());
+            option.setLength((byte) 4);
+            option.setData(DEFAULT_DNS.toOctets());
+            options.add(option);
+
+            // TODO fix MTU value to be configurable
+            option = new DhcpOption();
+            option.setCode(DHCP_OPTION_MTU);
+            option.setLength((byte) 2);
+            option.setData(DHCP_DATA_MTU_DEFAULT);
+            options.add(option);
+
+            // router address
+            option = new DhcpOption();
+            option.setCode(OptionCode_RouterAddress.getValue());
+            option.setLength((byte) 4);
+            option.setData(gatewayIp.toOctets());
+            options.add(option);
+
+            // end option
+            option = new DhcpOption();
+            option.setCode(OptionCode_END.getValue());
+            option.setLength((byte) 1);
+            options.add(option);
+
+            dhcpReply.setOptions(options);
+            return dhcpReply;
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
new file mode 100644
index 0000000..37cabf4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
@@ -0,0 +1,409 @@
+/*
+* Copyright 2016-present Open Networking Foundation
+*
+* 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ADMIN_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SWITCHING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_TUNNEL_TAG_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.SRC_VNI_TABLE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Populates switching flow rules on OVS for the basic connectivity among the
+ * virtual instances in the same network.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingHandler {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String ERR_SET_FLOWS = "Failed to set flows for %s: ";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackFlowRuleService osFlowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackSecurityGroupService securityGroupService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+    private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+    private ApplicationId appId;
+
+    @Activate
+    void activate() {
+        appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        instancePortService.addListener(instancePortListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    void deactivate() {
+        instancePortService.removeListener(instancePortListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private void setNetworkRules(InstancePort instPort, boolean install) {
+        switch (osNetworkService.network(instPort.networkId()).getNetworkType()) {
+            case VXLAN:
+                setTunnelTagFlowRules(instPort, install);
+                setForwardingRules(instPort, install);
+                break;
+            case VLAN:
+                setVlanTagFlowRules(instPort, install);
+                setForwardingRulesForVlan(instPort, install);
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void setForwardingRules(InstancePort instPort, boolean install) {
+        // switching rules for the instPorts in the same node
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(instPort.ipAddress().toIpPrefix())
+                .matchTunnelId(getVni(instPort))
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(instPort.macAddress())
+                .setOutput(instPort.portNumber())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                selector,
+                treatment,
+                PRIORITY_SWITCHING_RULE,
+                FORWARDING_TABLE,
+                install);
+
+        // switching rules for the instPorts in the remote node
+        OpenstackNode localNode = osNodeService.node(instPort.deviceId());
+        if (localNode == null) {
+            final String error = String.format("Cannot find openstack node for %s",
+                    instPort.deviceId());
+            throw new IllegalStateException(error);
+        }
+        osNodeService.completeNodes(COMPUTE).stream()
+                .filter(remoteNode -> !remoteNode.intgBridge().equals(localNode.intgBridge()))
+                .forEach(remoteNode -> {
+                    TrafficTreatment treatmentToRemote = DefaultTrafficTreatment.builder()
+                            .extension(buildExtension(
+                                    deviceService,
+                                    remoteNode.intgBridge(),
+                                    localNode.dataIp().getIp4Address()),
+                                    remoteNode.intgBridge())
+                            .setOutput(remoteNode.tunnelPortNum())
+                            .build();
+
+                    osFlowRuleService.setRule(
+                            appId,
+                            remoteNode.intgBridge(),
+                            selector,
+                            treatmentToRemote,
+                            PRIORITY_SWITCHING_RULE,
+                            FORWARDING_TABLE,
+                            install);
+                });
+    }
+
+    private void setForwardingRulesForVlan(InstancePort instPort, boolean install) {
+        // switching rules for the instPorts in the same node
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(instPort.ipAddress().toIpPrefix())
+                .matchVlanId(getVlanId(instPort))
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setEthDst(instPort.macAddress())
+                .setOutput(instPort.portNumber())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                selector,
+                treatment,
+                PRIORITY_SWITCHING_RULE,
+                FORWARDING_TABLE,
+                install);
+
+        // switching rules for the instPorts in the remote node
+        osNodeService.completeNodes(COMPUTE).stream()
+                .filter(remoteNode -> !remoteNode.intgBridge().equals(instPort.deviceId()) &&
+                        remoteNode.vlanIntf() != null)
+                .forEach(remoteNode -> {
+                    TrafficTreatment treatmentToRemote = DefaultTrafficTreatment.builder()
+                                    .setOutput(remoteNode.vlanPortNum())
+                                    .build();
+
+                    osFlowRuleService.setRule(
+                            appId,
+                            remoteNode.intgBridge(),
+                            selector,
+                            treatmentToRemote,
+                            PRIORITY_SWITCHING_RULE,
+                            FORWARDING_TABLE,
+                            install);
+                });
+    }
+
+    private void setTunnelTagFlowRules(InstancePort instPort, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchInPort(instPort.portNumber())
+                .build();
+
+        // XXX All egress traffic needs to go through connection tracking module, which might hurt its performance.
+        ExtensionTreatment ctTreatment =
+                RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, instPort.deviceId())
+                        .commit(true).build();
+
+        TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
+                .setTunnelId(getVni(instPort))
+                .transition(ACL_TABLE);
+
+        if (securityGroupService.isSecurityGroupEnabled()) {
+            tb.extension(ctTreatment, instPort.deviceId());
+        }
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                selector,
+                tb.build(),
+                PRIORITY_TUNNEL_TAG_RULE,
+                SRC_VNI_TABLE,
+                install);
+    }
+
+    private void setVlanTagFlowRules(InstancePort instPort, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchInPort(instPort.portNumber())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(getVlanId(instPort))
+                .transition(ACL_TABLE)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                selector,
+                treatment,
+                PRIORITY_TUNNEL_TAG_RULE,
+                SRC_VNI_TABLE,
+                install);
+
+    }
+
+    private void setNetworkAdminRules(Network network, boolean install) {
+        TrafficSelector selector;
+        if (network.getNetworkType() == NetworkType.VXLAN) {
+
+            selector = DefaultTrafficSelector.builder()
+                    .matchTunnelId(Long.valueOf(network.getProviderSegID()))
+                    .build();
+        } else {
+            selector = DefaultTrafficSelector.builder()
+                    .matchVlanId(VlanId.vlanId(network.getProviderSegID()))
+                    .build();
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .drop()
+                .build();
+
+        osNodeService.completeNodes().stream()
+                .filter(osNode -> osNode.type() == COMPUTE)
+                .forEach(osNode -> {
+                    osFlowRuleService.setRule(
+                            appId,
+                            osNode.intgBridge(),
+                            selector,
+                            treatment,
+                            PRIORITY_ADMIN_RULE,
+                            ACL_TABLE,
+                            install);
+                });
+    }
+
+    private void setPortAdminRules(Port port, boolean install) {
+        InstancePort instancePort = instancePortService.instancePort(MacAddress.valueOf(port.getMacAddress()));
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(instancePort.portNumber())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .drop()
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instancePort.deviceId(),
+                selector,
+                treatment,
+                PRIORITY_ADMIN_RULE,
+                SRC_VNI_TABLE,
+                install);
+    }
+
+    private VlanId getVlanId(InstancePort instPort) {
+        Network osNet = osNetworkService.network(instPort.networkId());
+
+        if (osNet == null || Strings.isNullOrEmpty(osNet.getProviderSegID())) {
+            final String error = String.format(
+                    ERR_SET_FLOWS + "Failed to get VNI for %s",
+                    instPort, osNet == null ? "<none>" : osNet.getName());
+            throw new IllegalStateException(error);
+        }
+
+        return VlanId.vlanId(osNet.getProviderSegID());
+    }
+
+
+    private Long getVni(InstancePort instPort) {
+        Network osNet = osNetworkService.network(instPort.networkId());
+        if (osNet == null || Strings.isNullOrEmpty(osNet.getProviderSegID())) {
+            final String error = String.format(
+                    ERR_SET_FLOWS + "Failed to get VNI for %s",
+                    instPort, osNet == null ? "<none>" : osNet.getName());
+            throw new IllegalStateException(error);
+        }
+        return Long.valueOf(osNet.getProviderSegID());
+    }
+
+    private class InternalInstancePortListener implements InstancePortListener {
+
+        @Override
+        public boolean isRelevant(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            return mastershipService.isLocalMaster(instPort.deviceId());
+        }
+
+        @Override
+        public void event(InstancePortEvent event) {
+            InstancePort instPort = event.subject();
+            switch (event.type()) {
+                case OPENSTACK_INSTANCE_PORT_UPDATED:
+                case OPENSTACK_INSTANCE_PORT_DETECTED:
+                    eventExecutor.execute(() -> {
+                        log.info("Instance port detected MAC:{} IP:{}",
+                                instPort.macAddress(),
+                                instPort.ipAddress());
+                        instPortDetected(event.subject());
+                    });
+                    break;
+                case OPENSTACK_INSTANCE_PORT_VANISHED:
+                    eventExecutor.execute(() -> {
+                        log.info("Instance port vanished MAC:{} IP:{}",
+                                instPort.macAddress(),
+                                instPort.ipAddress());
+                        instPortRemoved(event.subject());
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void instPortDetected(InstancePort instPort) {
+            setNetworkRules(instPort, true);
+            // TODO add something else if needed
+        }
+
+        private void instPortRemoved(InstancePort instPort) {
+            setNetworkRules(instPort, false);
+            // TODO add something else if needed
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java
new file mode 100644
index 0000000..64141b2
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_CREATE_TIME;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+
+@Service
+@Component(immediate = true)
+public final class OpenstackSwitchingHostProvider extends AbstractProvider implements HostProvider {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String PORT_NAME_PREFIX_VM = "tap";
+    private static final String ERR_ADD_HOST = "Failed to add host: ";
+    private static final String ANNOTATION_SEGMENT_ID = "segId";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    HostProviderRegistry hostProviderRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    OpenstackNodeService osNodeService;
+
+    private final ExecutorService deviceEventExecutor =
+            Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
+    private final ExecutorService configEventExecutor =
+            Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "config-event"));
+    private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
+    private final InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+
+    private HostProviderService hostProvider;
+
+    /**
+     * Creates OpenStack switching host provider.
+     */
+    public OpenstackSwitchingHostProvider() {
+        super(new ProviderId("sona", OPENSTACK_NETWORKING_APP_ID));
+    }
+
+    @Activate
+    void activate() {
+        coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+        deviceService.addListener(internalDeviceListener);
+        osNodeService.addListener(internalNodeListener);
+        hostProvider = hostProviderRegistry.register(this);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    void deactivate() {
+        hostProviderRegistry.unregister(this);
+        osNodeService.removeListener(internalNodeListener);
+        deviceService.removeListener(internalDeviceListener);
+
+        deviceEventExecutor.shutdown();
+        configEventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void triggerProbe(Host host) {
+        // no probe is required
+    }
+
+    private void processPortAdded(Port port) {
+        // TODO check the node state is COMPLETE
+        org.openstack4j.model.network.Port osPort = osNetworkService.port(port);
+        if (osPort == null) {
+            log.warn(ERR_ADD_HOST + "OpenStack port for {} not found", port);
+            return;
+        }
+
+        Network osNet = osNetworkService.network(osPort.getNetworkId());
+        if (osNet == null) {
+            log.warn(ERR_ADD_HOST + "OpenStack network {} not found",
+                    osPort.getNetworkId());
+            return;
+        }
+
+        if (osPort.getFixedIps().isEmpty()) {
+            log.warn(ERR_ADD_HOST + "no fixed IP for port {}", osPort.getId());
+            return;
+        }
+
+        MacAddress macAddr = MacAddress.valueOf(osPort.getMacAddress());
+        Set<IpAddress> fixedIps = osPort.getFixedIps().stream()
+                .map(ip -> IpAddress.valueOf(ip.getIpAddress()))
+                .collect(Collectors.toSet());
+        ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(ANNOTATION_NETWORK_ID, osPort.getNetworkId())
+                .set(ANNOTATION_PORT_ID, osPort.getId())
+                .set(ANNOTATION_CREATE_TIME, String.valueOf(System.currentTimeMillis()))
+                .set(ANNOTATION_SEGMENT_ID, osNet.getProviderSegID());
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                macAddr,
+                VlanId.NONE,
+                new HostLocation(connectPoint, System.currentTimeMillis()),
+                fixedIps,
+                annotations.build());
+
+        HostId hostId = HostId.hostId(macAddr);
+        hostProvider.hostDetected(hostId, hostDesc, false);
+    }
+
+    private void processPortRemoved(Port port) {
+        ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+        hostService.getConnectedHosts(connectPoint).forEach(host -> {
+                    hostProvider.hostVanished(host.id());
+                });
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            Device device = event.subject();
+            if (!mastershipService.isLocalMaster(device.id())) {
+                // do not allow to proceed without mastership
+                return false;
+            }
+            Port port = event.port();
+            if (port == null) {
+                return false;
+            }
+            String portName = port.annotations().value(PORT_NAME);
+            if (Strings.isNullOrEmpty(portName) ||
+                    !portName.startsWith(PORT_NAME_PREFIX_VM)) {
+                // handles Nova created port event only
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void event(DeviceEvent event) {
+            switch (event.type()) {
+                case PORT_UPDATED:
+                    if (!event.port().isEnabled()) {
+                        deviceEventExecutor.execute(() -> {
+                            log.debug("Instance port {} is removed from {}",
+                                     event.port().annotations().value(PORT_NAME),
+                                     event.subject().id());
+                            processPortRemoved(event.port());
+                        });
+                    } else if (event.port().isEnabled()) {
+                        deviceEventExecutor.execute(() -> {
+                            log.debug("Instance Port {} is detected from {}",
+                                     event.port().annotations().value(PORT_NAME),
+                                     event.subject().id());
+                            processPortAdded(event.port());
+                        });
+                    }
+                    break;
+                case PORT_ADDED:
+                    deviceEventExecutor.execute(() -> {
+                        log.debug("Instance port {} is detected from {}",
+                                 event.port().annotations().value(PORT_NAME),
+                                 event.subject().id());
+                        processPortAdded(event.port());
+                    });
+                    break;
+                case PORT_REMOVED:
+                    deviceEventExecutor.execute(() -> {
+                        log.debug("Instance port {} is removed from {}",
+                                event.port().annotations().value(PORT_NAME),
+                                event.subject().id());
+                        processPortRemoved(event.port());
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode osNode = event.subject();
+            // TODO check leadership of the node and make only the leader process
+
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                    deviceEventExecutor.execute(() -> {
+                        log.info("COMPLETE node {} is detected", osNode.hostname());
+                        processCompleteNode(event.subject());
+                    });
+                    break;
+                case OPENSTACK_NODE_INCOMPLETE:
+                    log.warn("{} is changed to INCOMPLETE state", osNode);
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_REMOVED:
+                default:
+                    break;
+            }
+        }
+
+        private void processCompleteNode(OpenstackNode osNode) {
+            deviceService.getPorts(osNode.intgBridge()).stream()
+                    .filter(port -> port.annotations().value(PORT_NAME)
+                            .startsWith(PORT_NAME_PREFIX_VM) &&
+                            port.isEnabled())
+                    .forEach(port -> {
+                        log.debug("Instance port {} is detected from {}",
+                                  port.annotations().value(PORT_NAME),
+                                  osNode.hostname());
+                        processPortAdded(port);
+                    });
+
+            Tools.stream(hostService.getHosts())
+                    .filter(host -> deviceService.getPort(
+                            host.location().deviceId(),
+                            host.location().port()) == null)
+                    .forEach(host -> {
+                        log.info("Remove stale host {}", host.id());
+                        hostProvider.hostVanished(host.id());
+                });
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
new file mode 100644
index 0000000..5dbf7a7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.impl;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.ExtensionSelectorResolver;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.criteria.ExtensionSelectorType;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides common methods to help populating flow rules for SONA applications.
+ */
+public final class RulePopulatorUtil {
+
+    private static final Logger log = getLogger(RulePopulatorUtil.class);
+
+    private static final String TUNNEL_DST = "tunnelDst";
+    private static final String CT_FLAGS = "flags";
+    private static final String CT_ZONE = "zone";
+    private static final String CT_TABLE = "recircTable";
+    private static final String CT = "niciraCt";
+    private static final String CT_STATE = "ctState";
+    private static final String CT_STATE_MASK = "ctStateMask";
+    private static final String CT_PRESENT_FLAGS = "presentFlags";
+    private static final String CT_IPADDRESS_MIN = "ipAddressMin";
+    private static final String CT_IPADDRESS_MAX = "ipAddressMax";
+
+    private static final int ADDRESS_MIN_FLAG = 0;
+    private static final int ADDRESS_MAX_FLAG = 1;
+    private static final int PORT_MIN_FLAG = 2;
+    private static final int PORT_MAX_FLAG = 3;
+
+    // Refer to http://openvswitch.org/support/dist-docs/ovs-fields.7.txt for the values
+    public static final long CT_STATE_NONE = 0;
+    public static final long CT_STATE_NEW = 0x01;
+    public static final long CT_STATE_EST = 0x02;
+    public static final long CT_STATE_NOT_TRK = 0x20;
+    public static final long CT_STATE_TRK = 0x20;
+
+    private RulePopulatorUtil() {
+    }
+
+    /**
+     * Returns a builder for OVS Connection Tracking feature actions.
+     *
+     * @param ds DriverService
+     * @param id DeviceId
+     * @return a builder for OVS Connection Tracking feature actions
+     */
+    public static NiriraConnTrackTreatmentBuilder niciraConnTrackTreatmentBuilder(DriverService ds, DeviceId id) {
+        return new NiriraConnTrackTreatmentBuilder(ds, id);
+    }
+
+    /**
+     * Returns tunnel destination extension treatment object.
+     *
+     * @param deviceService driver service
+     * @param deviceId device id to apply this treatment
+     * @param remoteIp tunnel destination ip address
+     * @return extension treatment
+     */
+    public static ExtensionTreatment buildExtension(DeviceService deviceService,
+                                                    DeviceId deviceId,
+                                                    Ip4Address remoteIp) {
+        Device device = deviceService.getDevice(deviceId);
+        if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
+            log.error("The extension treatment is not supported");
+            return null;
+        }
+
+        if (device == null) {
+            return null;
+        }
+
+        ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+        ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+        try {
+            treatment.setPropertyValue(TUNNEL_DST, remoteIp);
+            return treatment;
+        } catch (ExtensionPropertyException e) {
+            log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
+            return null;
+        }
+    }
+
+    /**
+     * Builds OVS ConnTrack matches.
+     *
+     * @param driverService driver service
+     * @param deviceId device ID
+     * @param ctState connection tracking sate masking value
+     * @param ctSateMask connection tracking sate masking value
+     * @return OVS ConnTrack extension match
+     */
+    public static ExtensionSelector buildCtExtensionSelector(DriverService driverService, DeviceId deviceId,
+                                                             long ctState, long ctSateMask) {
+        DriverHandler handler = driverService.createHandler(deviceId);
+        ExtensionSelectorResolver esr = handler.behaviour(ExtensionSelectorResolver.class);
+
+        ExtensionSelector extensionSelector = esr.getExtensionSelector(
+                ExtensionSelectorType.ExtensionSelectorTypes.NICIRA_MATCH_CONNTRACK_STATE.type());
+        try {
+            extensionSelector.setPropertyValue(CT_STATE, ctState);
+            extensionSelector.setPropertyValue(CT_STATE_MASK, ctSateMask);
+        } catch (Exception e) {
+            log.error("Failed to set nicira match CT state");
+            return null;
+        }
+
+        return extensionSelector;
+    }
+
+    /**
+     * Computes ConnTack State flag values.
+     *
+     * @param isTracking true for +trk, false for -trk
+     * @param isNew true for +new, false for nothing
+     * @param isEstablished true for +est, false for nothing
+     * @return ConnTrack State flags
+     */
+    public static long computeCtStateFlag(boolean isTracking, boolean isNew, boolean isEstablished) {
+        long ctMaskFlag = 0x00;
+
+        if (isTracking) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+        }
+
+        if (isNew) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+        }
+
+        if (isEstablished) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+        }
+
+        return ctMaskFlag;
+    }
+
+    /**
+     * Computes ConnTrack State mask values.
+     *
+     * @param isTracking true for setting +trk/-trk value, false for otherwise
+     * @param isNew true for setting +new value, false for otherwise
+     * @param isEstablished true for setting +est value, false for otherwise
+     * @return ConnTrack State Mask value
+     */
+    public static long computeCtMaskFlag(boolean isTracking, boolean isNew, boolean isEstablished) {
+        long ctMaskFlag = 0x00;
+
+        if (isTracking) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+        }
+
+        if (isNew) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+        }
+
+        if (isEstablished) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+        }
+
+        return ctMaskFlag;
+    }
+
+    /**
+     * Builder class for OVS Connection Tracking feature actions.
+     */
+    public static final class NiriraConnTrackTreatmentBuilder {
+
+        private DriverService driverService;
+        private DeviceId deviceId;
+        private IpAddress natAddress = null;
+        private int zone;
+        private boolean commit;
+        private short table = -1;
+        private boolean natAction;
+
+
+        private NiriraConnTrackTreatmentBuilder(DriverService driverService, DeviceId deviceId) {
+            this.driverService = driverService;
+            this.deviceId = deviceId;
+        }
+
+        /**
+         * Sets commit flag.
+         *
+         * @param c true if commit, false if not.
+         * @return NiriraConnTrackTreatmentBuilder object
+         */
+        public NiriraConnTrackTreatmentBuilder commit(boolean c) {
+            this.commit = c;
+            return this;
+        }
+
+        /**
+         * Sets zone number.
+         *
+         * @param z zone number
+         * @return NiriraConnTrackTreatmentBuilder object
+         */
+        public NiriraConnTrackTreatmentBuilder zone(int z) {
+            this.zone = z;
+            return this;
+        }
+
+        /**
+         * Sets recirculation table number.
+         *
+         * @param t table number to restart
+         * @return NiriraConnTrackTreatmentBuilder object
+         */
+        public NiriraConnTrackTreatmentBuilder table(short t) {
+            this.table = t;
+            return this;
+        }
+
+        /**
+         * Sets IP address for NAT.
+         *
+         * @param ip NAT IP address
+         * @return NiriraConnTrackTreatmentBuilder object
+         */
+        public NiriraConnTrackTreatmentBuilder natIp(IpAddress ip) {
+            this.natAddress = ip;
+            return this;
+        }
+
+        /**
+         * Sets the flag for NAT action.
+         *
+         * @param nat nat action is included if true, no nat action otherwise
+         * @return NiriraConnTrackTreatmentBuilder object
+         */
+        public NiriraConnTrackTreatmentBuilder natAction(boolean nat) {
+            this.natAction = nat;
+            return this;
+        }
+
+        /**
+         * Builds extension treatment for OVS ConnTack and NAT feature.
+         *
+         * @return ExtensionTreatment object
+         */
+        public ExtensionTreatment build() {
+            DriverHandler handler = driverService.createHandler(deviceId);
+            ExtensionTreatmentResolver etr = handler.behaviour(ExtensionTreatmentResolver.class);
+
+            ExtensionTreatment natTreatment
+                    = etr.getExtensionInstruction(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_NAT.type());
+            try {
+                if (natAddress != null) {
+                    natTreatment.setPropertyValue(CT_FLAGS, 1);
+                    natTreatment.setPropertyValue(CT_PRESENT_FLAGS, buildPresentFlag(false, true));
+                    natTreatment.setPropertyValue(CT_IPADDRESS_MIN, natAddress);
+                    natTreatment.setPropertyValue(CT_IPADDRESS_MAX, natAddress);
+                } else {
+                    natTreatment.setPropertyValue(CT_FLAGS, 0);
+                    natTreatment.setPropertyValue(CT_PRESENT_FLAGS, 0);
+                }
+            } catch (Exception e) {
+                log.error("Failed to set NAT due to error : {}", e.getMessage());
+                return null;
+            }
+
+            ExtensionTreatment ctTreatment
+                    = etr.getExtensionInstruction(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_CT.type());
+            try {
+                List<ExtensionTreatment> nat = new ArrayList<>();
+                if (natAction) {
+                    nat.add(natTreatment);
+                }
+                ctTreatment.setPropertyValue(CT_FLAGS, commit ? 1 : 0);
+                ctTreatment.setPropertyValue(CT_ZONE, zone);
+                ctTreatment.setPropertyValue(CT_TABLE, table > -1 ? table : 0xff);
+                ctTreatment.setPropertyValue("nestedActions", nat);
+            } catch (Exception e) {
+                log.error("Failed to set CT due to error : {}", e.getMessage());
+                return null;
+            }
+
+            return ctTreatment;
+        }
+
+        private int buildPresentFlag(boolean isPortPresent, boolean isAddressPresent) {
+
+            int presentFlag = 0;
+
+            if (isPortPresent) {
+                presentFlag = presentFlag | 1 << PORT_MIN_FLAG | 1 << PORT_MAX_FLAG;
+            }
+
+            if (isAddressPresent) {
+                presentFlag =  1 << ADDRESS_MIN_FLAG | 1 << ADDRESS_MAX_FLAG;
+            }
+
+            return presentFlag;
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
new file mode 100644
index 0000000..34ca730
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implements OpenStack L3 service plugin, which routes packets between subnets,
+ * forwards packets from internal networks to external ones, and accesses instances
+ * from external networks through floating IPs.
+ */
+package org.onosproject.openstacknetworking.impl;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java
new file mode 100644
index 0000000..5fee7cc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.openstack4j.core.transport.ObjectMapperSingleton;
+import org.openstack4j.model.ModelEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+
+/**
+ * An utility that used in openstack networking app.
+ */
+public final class OpenstackUtil {
+
+    protected static final Logger log = LoggerFactory.getLogger(OpenstackUtil.class);
+
+    /**
+     * Prevents object instantiation from external.
+     */
+    private OpenstackUtil() {
+    }
+
+    /**
+     * Interprets JSON string to corresponding openstack model entity object.
+     *
+     * @param input JSON string
+     * @param entityClazz openstack model entity class
+     * @return openstack model entity object
+     */
+    public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
+            log.trace(new ObjectMapper().writeValueAsString(jsonTree));
+            return ObjectMapperSingleton.getContext(entityClazz)
+                    .readerFor(entityClazz)
+                    .readValue(jsonTree);
+        } catch (Exception e) {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Converts openstack model entity object into JSON object.
+     *
+     * @param entity openstack model entity object
+     * @param entityClazz openstack model entity class
+     * @return JSON object
+     */
+    public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
+                    .writerFor(entityClazz)
+                    .writeValueAsString(entity);
+            log.trace(strModelEntity);
+            return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
+        } catch (Exception e) {
+            throw new IllegalStateException();
+        }
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/package-info.java
new file mode 100644
index 0000000..4b6cfb8
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Openstack utility package.
+ */
+package org.onosproject.openstacknetworking.util;
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
new file mode 100644
index 0000000..d7ba4a9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neutron L3 plugin.
+ */
+@Path("floatingips")
+public class OpenstackFloatingIpWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received floating IP %s request";
+    private static final String FLOATING_IPS = "floatingips";
+
+    private final OpenstackRouterAdminService adminService =
+                                        get(OpenstackRouterAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a floating IP from the JSON input stream.
+     *
+     * @param input floating ip JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated floating ip already exists
+     * @onos.rsModel NeutronFloatingIp
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createFloatingIp(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronFloatingIP floatingIp = (NeutronFloatingIP)
+                                jsonToModelEntity(input, NeutronFloatingIP.class);
+
+        adminService.createFloatingIp(floatingIp);
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(FLOATING_IPS)
+                .path(floatingIp.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates the floating IP with the specified identifier.
+     *
+     * @param id    floating ip identifier
+     * @param input floating ip JSON input stream
+     * @return 200 OK with the updated floating ip, 400 BAD_REQUEST if the requested
+     * floating ip does not exist
+     * @onos.rsModel NeutronFloatingIp
+     */
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateFloatingIp(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+        final NeutronFloatingIP floatingIp = (NeutronFloatingIP)
+                                jsonToModelEntity(input, NeutronFloatingIP.class);
+
+        adminService.updateFloatingIp(floatingIp);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Removes the floating IP.
+     *
+     * @param id floating ip identifier
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the floating ip does not exist
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteFloatingIp(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "DELETE " + id));
+
+        adminService.removeFloatingIp(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
new file mode 100644
index 0000000..25d239c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neutron ML2 plugin.
+ */
+@Path("networks")
+public class OpenstackNetworkWebResource extends AbstractWebResource {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received networks %s request";
+    private static final String NETWORKS = "networks";
+
+    private final OpenstackNetworkAdminService adminService =
+                                        get(OpenstackNetworkAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a network from the JSON input stream.
+     *
+     * @param input network JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated network already exists
+     * @onos.rsModel NeutronNetwork
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createNetwork(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronNetwork net = (NeutronNetwork)
+                             jsonToModelEntity(input, NeutronNetwork.class);
+
+        adminService.createNetwork(net);
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(NETWORKS)
+                .path(net.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates the network with the specified identifier.
+     *
+     * @param id network identifier
+     * @param input network JSON input stream
+     * @return 200 OK with the updated network, 400 BAD_REQUEST if the requested
+     * network does not exist
+     * @onos.rsModel NeutronNetwork
+     */
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateNetwork(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+        final NeutronNetwork net = (NeutronNetwork)
+                             jsonToModelEntity(input, NeutronNetwork.class);
+
+        adminService.updateNetwork(net);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Removes the service network.
+     *
+     * @param id network identifier
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the network does not exist
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteNetwork(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "DELETE " + id));
+
+        adminService.removeNetwork(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java
new file mode 100644
index 0000000..8f9e623
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Openstack networking REST APIs web application.
+ */
+public class OpenstackNetworkingWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(
+                OpenstackFloatingIpWebResource.class,
+                OpenstackNetworkWebResource.class,
+                OpenstackPortWebResource.class,
+                OpenstackRouterWebResource.class,
+                OpenstackSecurityGroupRuleWebResource.class,
+                OpenstackSecurityGroupWebResource.class,
+                OpenstackSubnetWebResource.class
+        );
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
new file mode 100644
index 0000000..730eb46
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+@Path("ports")
+public class OpenstackPortWebResource extends AbstractWebResource {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received ports %s request";
+    private static final String PORTS = "ports";
+
+    private final OpenstackNetworkAdminService adminService =
+                                        get(OpenstackNetworkAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a port from the JSON input stream.
+     *
+     * @param input port JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated port already exists
+     * @onos.rsModel NeutronPort
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createPorts(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronPort port = (NeutronPort)
+                                 jsonToModelEntity(input, NeutronPort.class);
+
+        adminService.createPort(port);
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(PORTS)
+                .path(port.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates the port with the specified identifier.
+     *
+     * @param id    port identifier
+     * @param input port JSON input stream
+     * @return 200 OK with the updated port, 400 BAD_REQUEST if the requested
+     * port does not exist
+     * @onos.rsModel NeutronPort
+     */
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updatePort(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+        final NeutronPort port = (NeutronPort)
+                                 jsonToModelEntity(input, NeutronPort.class);
+
+        adminService.updatePort(port);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Removes the port with the given id.
+     *
+     * @param id port identifier
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the port does not exist
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deletePorts(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "DELETE " + id));
+
+        adminService.removePort(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
new file mode 100644
index 0000000..8326f40
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neturon L3 plugin.
+ */
+
+@Path("routers")
+public class OpenstackRouterWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE_ROUTER = "Received router %s request";
+    private static final String MESSAGE_ROUTER_IFACE = "Received router interface %s request";
+    private static final String ROUTERS = "routers";
+
+    private final OpenstackRouterAdminService adminService =
+                                        get(OpenstackRouterAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a router from the JSON input stream.
+     *
+     * @param input router JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated router already exists
+     * @onos.rsModel NeutronRouter
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createRouter(InputStream input) {
+        log.trace(String.format(MESSAGE_ROUTER, "CREATE"));
+
+        final NeutronRouter osRouter = (NeutronRouter)
+                            jsonToModelEntity(input, NeutronRouter.class);
+
+        adminService.createRouter(osRouter);
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(ROUTERS)
+                .path(osRouter.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates the router with the specified identifier.
+     *
+     * @param id router identifier
+     * @param input router JSON input stream
+     * @return 200 OK with the updated router, 400 BAD_REQUEST if the requested
+     * router does not exist
+     * @onos.rsModel NeutronRouter
+     */
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateRouter(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE_ROUTER, "UPDATE " + id));
+
+        final NeutronRouter osRouter = (NeutronRouter)
+                            jsonToModelEntity(input, NeutronRouter.class);
+
+        osRouter.setId(id);
+        adminService.updateRouter(osRouter);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Updates the router with the specified router interface.
+     *
+     * @param id router identifier
+     * @param input router interface JSON input stream
+     * @return 200 OK with the updated router interface, 400 BAD_REQUEST if the
+     * requested router does not exist
+     * @onos.rsModel NeutronRouterInterface
+     */
+    @PUT
+    @Path("{id}/add_router_interface")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addRouterInterface(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE_ROUTER_IFACE, "UPDATE " + id));
+
+        final NeutronRouterInterface osRouterIface = (NeutronRouterInterface)
+                        jsonToModelEntity(input, NeutronRouterInterface.class);
+
+        adminService.addRouterInterface(osRouterIface);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Updates the router with the specified router interface.
+     *
+     * @param id router identifier
+     * @param input router interface JSON input stream
+     * @return 200 OK with the updated router interface, 400 BAD_REQUEST if the
+     * requested router does not exist
+     * @onos.rsModel NeutronRouterInterface
+     */
+    @PUT
+    @Path("{id}/remove_router_interface")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteRouterInterface(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE_ROUTER_IFACE, "DELETE " + id));
+
+        final NeutronRouterInterface osRouterIface = (NeutronRouterInterface)
+                        jsonToModelEntity(input, NeutronRouterInterface.class);
+
+        adminService.removeRouterInterface(osRouterIface.getPortId());
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Removes the router.
+     *
+     * @param id router identifier
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the router does not exist
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteRouter(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE_ROUTER, "DELETE " + id));
+
+        adminService.removeRouter(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java
new file mode 100644
index 0000000..dc12860
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Security Group Rule Rest API call from Neutron ML2 plugin.
+ */
+@Path("security-group-rules")
+public class OpenstackSecurityGroupRuleWebResource extends AbstractWebResource {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received security group rules %s request";
+    private static final String SECURITY_GROUP_RULES = "security-group-rules";
+
+    private final OpenstackSecurityGroupAdminService adminService =
+                                    get(OpenstackSecurityGroupAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a security group from the JSON input stream.
+     *
+     * @param input security group JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated security group rule ID already exists
+     * @onos.rsModel NeutronSecurityGroupRule
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSecurityGroupRules(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronSecurityGroupRule sgRule = (NeutronSecurityGroupRule)
+                        jsonToModelEntity(input, NeutronSecurityGroupRule.class);
+
+        adminService.createSecurityGroupRule(sgRule);
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(SECURITY_GROUP_RULES)
+                .path(sgRule.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Removes the security group rule.
+     *
+     * @param id security group rule ID
+     * @return 204 NO_CONTENT
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteSecurityGroupRule(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "REMOVE " + id));
+
+        adminService.removeSecurityGroupRule(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java
new file mode 100644
index 0000000..da335a3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Security Group Rest API call from Neutron ML2 plugin.
+ */
+@Path("security-groups")
+public class OpenstackSecurityGroupWebResource extends AbstractWebResource {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received security groups %s request";
+    private static final String SECURITY_GROUPS = "security-groups";
+
+    private final OpenstackSecurityGroupAdminService adminService =
+                                    get(OpenstackSecurityGroupAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a security group from the JSON input stream.
+     *
+     * @param input security group JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated security group ID already exists
+     * @onos.rsModel NeutronSecurityGroup
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSecurityGroups(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronSecurityGroup sg = (NeutronSecurityGroup)
+                            jsonToModelEntity(input, NeutronSecurityGroup.class);
+
+        adminService.createSecurityGroup(sg);
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(SECURITY_GROUPS)
+                .path(sg.getId());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates a security group from the JSON input stream.
+     *
+     * @param input security group JSON input stream
+     * @return 200 OK with the updated security group, 400 BAD_REQUEST if the
+     * request is invalid
+     * @onos.rsModel NeutronSecurityGroup
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateSecurityGroups(InputStream input) {
+        log.trace(String.format(MESSAGE, "UPDATE"));
+
+        // Do nothing ...
+        // TODO: this interface will purged sooner or later...
+        return Response.ok().build();
+    }
+
+    /**
+     * Removes the security group.
+     *
+     * @param id security group ID
+     * @return 204 NO_CONTENT
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response removeSecurityGroup(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "REMOVE " + id));
+
+        adminService.removeSecurityGroup(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
new file mode 100644
index 0000000..0c46bfc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+@Path("subnets")
+public class OpenstackSubnetWebResource extends AbstractWebResource {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE = "Received subnets %s request";
+    private static final String SUBNETS = "subnets";
+
+    private final OpenstackNetworkAdminService adminService =
+                                        get(OpenstackNetworkAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a subnet from the JSON input stream.
+     *
+     * @param input subnet JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated subnet already exists
+     * @onos.rsModel NeutronSubnet
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSubnet(InputStream input) {
+        log.trace(String.format(MESSAGE, "CREATE"));
+
+        final NeutronSubnet subnet = (NeutronSubnet)
+                            jsonToModelEntity(input, NeutronSubnet.class);
+
+        adminService.createSubnet(subnet);
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(SUBNETS)
+                .path(subnet.getId());
+
+        // TODO fix networking-onos to send Network UPDATE when subnet created
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Updates the subnet with the specified identifier.
+     *
+     * @param id    subnet identifier
+     * @param input subnet JSON input stream
+     * @return 200 OK with the updated subnet, 400 BAD_REQUEST if the requested
+     * subnet does not exist
+     * @onos.rsModel NeutronSubnet
+     */
+    @PUT
+    @Path("{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateSubnet(@PathParam("id") String id, InputStream input) {
+        log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+        final NeutronSubnet subnet = (NeutronSubnet)
+                            jsonToModelEntity(input, NeutronSubnet.class);
+
+        adminService.updateSubnet(subnet);
+
+        return status(Response.Status.OK).build();
+    }
+
+    /**
+     * Removes the subnet.
+     *
+     * @param id subnet identifier
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the subnet does not exist
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteSubnet(@PathParam("id") String id) {
+        log.trace(String.format(MESSAGE, "DELETE " + id));
+
+        adminService.removeSubnet(id);
+        return noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
new file mode 100644
index 0000000..c8a0eb0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * OpenStack networking implementation.
+ */
+package org.onosproject.openstacknetworking.web;
diff --git a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..d3884df
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,73 @@
+<!--
+~ Copyright 2017-present Open Networking Foundation
+~
+~ 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.
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackNetworkListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackPortListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackRouterListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackFloatingIpListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackPurgeStateCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackSyncStateCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackSecurityGroupListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackPurgeRulesCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackSyncRulesCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.ExternalPeerRouterListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterCommand"/>
+            <completers>
+                <ref component-id="ipAddressCompleter"/>
+                <ref component-id="macAddressCompleter"/>
+                <ref component-id="vlanIdCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterVlanCommand"/>
+            <completers>
+                <ref component-id="ipAddressCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.DeleteExternalPeerRouterCommand" />
+            <completers>
+                <ref component-id="ipAddressCompleter"/>
+            </completers>
+        </command>
+    </command-bundle>
+
+    <bean id="ipAddressCompleter" class="org.onosproject.openstacknetworking.cli.IpAddressCompleter"/>
+    <bean id="macAddressCompleter" class="org.onosproject.openstacknetworking.cli.MacAddressCompleter"/>
+    <bean id="vlanIdCompleter" class="org.onosproject.openstacknetworking.cli.VlanIdCompleter"/>
+</blueprint>
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json
new file mode 100644
index 0000000..7976ffe
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json
@@ -0,0 +1,58 @@
+{
+  "type": "object",
+  "required": [
+    "floatingip"
+  ],
+  "properties": {
+    "floatingip": {
+      "type": "object",
+      "description": "A floatingip object.",
+      "required": [
+        "id",
+        "router_id",
+        "tenant_id",
+        "floating_network_id",
+        "floating_ip_address",
+        "fixed_ip_address",
+        "port_id"
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
+          "description": "The ID of the floating IP address."
+        },
+        "router_id": {
+          "type": "string",
+          "example": "d23abc8d-2991-4a55-ba98-2aaea84cc72",
+          "description": "The ID of the router for the floating IP."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "4969c491a3c74ee4af974e6d800c62de",
+          "description": "The ID of the project."
+        },
+        "floating_network_id": {
+          "type": "string",
+          "example": "376da547-b977-4cfe-9cba-275c80debf57",
+          "description": "The ID of the network associated with the floating IP."
+        },
+        "floating_ip_address": {
+          "type": "string",
+          "example": "172.24.4.228",
+          "description": "The floating IP address."
+        },
+        "fixed_ip_address": {
+          "type": "string",
+          "example": "10.0.0.3",
+          "description": "The fixed IP address that is associated with the floating IP address."
+        },
+        "port_id": {
+          "type": "string",
+          "example": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+          "description": "The ID of a port associated with the floating IP."
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json
new file mode 100644
index 0000000..ecc86d8
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json
@@ -0,0 +1,86 @@
+
+{
+  "type": "object",
+  "required": [
+    "network"
+  ],
+  "properties": {
+    "network": {
+      "type": "object",
+      "description": "A network object.",
+      "required": [
+        "id",
+        "subnets",
+        "provider:physical_network",
+        "admin_state_up",
+        "tenant_id",
+        "provider:network_type",
+        "router:external",
+        "provider:segmentation_id",
+        "availability_zone_hints",
+        "availability_zones"
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "396f12f8-521e-4b91-8e21-2e003500433a",
+          "description": "The ID of the network."
+        },
+        "subnets": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "example": "10.10.0.0/24",
+            "description": "The associated subnets."
+          }
+        },
+        "provider:physical_network": {
+          "type": "string",
+          "example": "physnet1",
+          "description": "The physical network where this network is implemented."
+        },
+        "admin_state_up": {
+          "type": "boolean",
+          "example": true,
+          "description": "The administrative state of the network, which is up (true) or down (false)."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "20bd52ff3e1b40039c312395b04683cf",
+          "description": "The ID of the project."
+        },
+        "provider:network_type": {
+          "type": "string",
+          "example": "vlan",
+          "description": "The type of physical network that this network is mapped to."
+        },
+        "router:external": {
+          "type": "boolean",
+          "example": true,
+          "description": "Indicates whether the network has an external routing facility that’s not managed by the networking service."
+        },
+        "provider:segmentation_id": {
+          "type": "string",
+          "example": "1002",
+          "description": "The ID of the isolated segment on the physical network."
+        },
+        "availability_zone_hints": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "example": "1",
+            "description": "The availability zone candidate for the network."
+          }
+        },
+        "availability_zones": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "example": "nova",
+            "description": "The availability zone for the network."
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json
new file mode 100644
index 0000000..5a833bf
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json
@@ -0,0 +1,190 @@
+{
+  "type": "object",
+  "required": [
+    "port"
+  ],
+  "properties": {
+    "port": {
+      "type": "object",
+      "description": "A port object.",
+      "required": [
+        "id",
+        "admin_state_up",
+        "device_id",
+        "device_owner",
+        "fixed_ips",
+        "allowed_address_pairs",
+        "mac_address",
+        "network_id",
+        "state",
+        "tenant_id",
+        "security_groups",
+        "extra_dhcp_opts",
+        "port_security_enabled",
+        "binding:host_id",
+        "binding:vif_type",
+        "binding:vif_details",
+        "binding:vnic_type",
+        "binding:profile"
+
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "65c0ee9f-d634-4522-8954-51021b570b0d",
+          "description": "The ID of the resource."
+        },
+        "admin_state_up": {
+          "type": "boolean",
+          "example": true,
+          "description": "The administrative state of the resource, which is up (true) or down (false)."
+        },
+        "device_id": {
+          "type": "string",
+          "example": "1",
+          "description": "The ID of the device that uses this port. For example, a server instance or a logical router."
+        },
+        "device_owner": {
+          "type": "string",
+          "example": "compute:nova",
+          "description": "The entity type that uses this port. For example, compute:nova (server instance)."
+        },
+        "fixed_ips": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "title": "fixed_ips",
+            "description": "The IP addresses for the port. If the port has multiple IP addresses, this field has multiple entries.",
+            "required": [
+              "ip_address",
+              "subnet_id"
+            ],
+            "properties": {
+              "ip_address": {
+                "type": "string",
+                "example": "10.0.0.2",
+                "description": "The IP address of the port."
+              },
+              "subnet_id": {
+                "type": "string",
+                "example": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+                "description": "The ID of the subnet."
+              }
+            }
+          }
+        },
+        "allowed_address_pairs": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "title": "allowed_address_pairs",
+            "description": "A set of zero or more allowed address pair objects each where address pair object contains an ip_address and mac_address.",
+            "required": [
+              "ip_address",
+              "mac_address"
+            ],
+            "properties": {
+              "ip_address": {
+                "type": "string",
+                "example": "12.12.11.12",
+                "description": "The IP address of the port."
+              },
+              "mac_address": {
+                "type": "string",
+                "example": "fa:14:2a:b3:cb:f0",
+                "description": "The MAC address of the port."
+              }
+            }
+          }
+        },
+        "mac_address": {
+          "type": "string",
+          "example": "fa:16:3e:c9:cb:f0",
+          "description": "The MAC address of the port."
+        },
+        "network_id": {
+          "type": "string",
+          "example": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+          "description": "The ID of the attached network."
+        },
+        "status": {
+          "type": "string",
+          "example": "ACTIVE",
+          "description": "The port status. Values are ACTIVE, DOWN, BUILD and ERROR."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+          "description": "The ID of the project."
+        },
+        "security_groups": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "example": "f0ac4394-7e4a-4409-9701-ba8be283dbc3",
+            "description": "The IDs of security groups applied to the port."
+          }
+        },
+        "extra_dhcp_opts": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "title": "extra_dhcp_opts",
+            "description": "A set of zero or more extra DHCP option pairs. An option pair consists of an option value and name.",
+            "required": [
+              "opt_value",
+              "opt_name"
+            ],
+            "properties": {
+              "opt_value": {
+                "type": "string",
+                "example": "pxelinux.0",
+                "description": "A value of option pair."
+              },
+              "opt_name": {
+                "type": "string",
+                "example": "bootfile-name",
+                "description": "A name of option pair."
+              }
+            }
+          }
+        },
+        "port_security_enabled": {
+          "type": "boolean",
+          "example": true,
+          "description": "The port security status. A valid value is enabled (true) or disabled (false)."
+        },
+        "binding:host_id": {
+          "type": "string",
+          "example": "4df8d9ff-6f6f-438f-90a1-ef660d4586ad",
+          "description": "The ID of the host where the port resides."
+        },
+        "binding:vif_type": {
+          "type": "string",
+          "example": "unbound",
+          "description": "The type of which mechanism is used for the port."
+        },
+        "binding:vif_details": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string",
+            "example": "",
+            "description": "A dictionary which contains additional information on the port."
+          }
+        },
+        "binding:vnic_type": {
+          "type": "string",
+          "example": "other",
+          "description": "The type of vNIC which this port should be attached to. This is used to determine which mechanism driver(s) to be used to bind the port."
+        },
+        "binding:profile": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string",
+            "description": "A dictionary that enables the application running on the specific host to pass and receive vif port information specific to the networking back-end."
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json
new file mode 100644
index 0000000..47d0f41
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json
@@ -0,0 +1,96 @@
+{
+  "type": "object",
+  "required": [
+    "router"
+  ],
+  "properties": {
+    "router": {
+      "type": "object",
+      "description": "A router object.",
+      "required": [
+        "id",
+        "name",
+        "status",
+        "external_gateway_info",
+        "admin_state_up",
+        "tenant_id",
+        "routes",
+        "distributed"
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
+          "description": "The ID of the router."
+        },
+        "name": {
+          "type": "string",
+          "example": "router1",
+          "description": "Human-readable name of the resource."
+        },
+        "status": {
+          "type": "string",
+          "example": "ACTIVE",
+          "description": "The router status."
+        },
+        "external_gateway_info": {
+          "type": "object",
+          "description": "The external gateway information of the router.",
+          "required": [
+            "network_id",
+            "enable_snat"
+          ],
+          "properties": {
+            "network_id": {
+              "type": "string",
+              "example": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+              "description": "Network ID which the router gateway is connected to."
+            },
+            "enable_snat": {
+              "type": "boolean",
+              "example": false,
+              "description": "Enable Source NAT (SNAT) attribute. true means Network Address Translation (NAT) is enabled."
+            }
+          }
+        },
+        "admin_state_up": {
+          "type": "boolean",
+          "example": true,
+          "description": "The administrative state of the resource, which is up (true) or down (false)."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+          "description": "The ID of the project."
+        },
+        "routes": {
+          "type": "array",
+          "description": "The extra routes configuration for L3 router. A list of dictionaries with destination and nexthop parameters.",
+          "items": {
+            "type": "object",
+            "title": "route",
+            "required": [
+              "destination",
+              "nexthop"
+            ],
+            "properties": {
+              "destination": {
+                "type": "string",
+                "description": "The destination CIDR."
+              },
+              "nexthop": {
+                "type": "string",
+                "description": "The IP address of the next hop for the corresponding destination."
+              }
+            }
+          }
+        },
+        "distributed": {
+          "type": "boolean",
+          "example": false,
+          "description": "true indicates a distributed router. It is available when dvr extension is enabled."
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json
new file mode 100644
index 0000000..59ae058
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json
@@ -0,0 +1,32 @@
+{
+  "type": "object",
+  "description": "A router interface object.",
+  "required": [
+    "id",
+    "tenant_id",
+    "subnet_id",
+    "port_id"
+  ],
+  "properties": {
+    "id": {
+      "type": "string",
+      "example": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
+      "description": "The ID of the router interface."
+    },
+    "tenant_id": {
+      "type": "string",
+      "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+      "description": "The ID of the project."
+    },
+    "subnet_id": {
+      "type": "string",
+      "example": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+      "description": "The subnet ID on which you want to create the floating IP."
+    },
+    "port_id": {
+      "type": "string",
+      "example": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+      "description": "The ID of a port associated with the floating IP."
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json
new file mode 100644
index 0000000..6859b59
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json
@@ -0,0 +1,115 @@
+{
+  "type": "object",
+  "description": "A security_group object.",
+  "required": [
+    "security_group"
+  ],
+  "properties": {
+    "security_group": {
+      "type": "object",
+      "required": [
+        "id",
+        "tenant_id",
+        "description",
+        "name",
+        "security_group_rules"
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "2076db17-a522-4506-91de-c6dd8e837028",
+          "description": "The ID of the security group."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+          "description": "The ID of the project."
+        },
+        "description": {
+          "type": "string",
+          "example": "security group for webservers",
+          "description": "A human-readable description for the resource."
+        },
+        "name": {
+          "type": "string",
+          "example": "new-webservers",
+          "description": "Human-readable name of the resource."
+        },
+        "security_group_rules": {
+          "type": "array",
+          "description": "A list of security_group_rule objects.",
+          "items": {
+            "type": "object",
+            "description": "A security group rule object.",
+            "required": [
+              "id",
+              "tenant_id",
+              "security_group_id",
+              "direction",
+              "ethertype",
+              "port_range_max",
+              "port_range_min",
+              "protocol",
+              "remote_ip_prefix",
+              "remote_group_id"
+            ],
+            "properties": {
+              "id": {
+                "type": "string",
+                "example": "2bc0accf-312e-429a-956e-e4407625eb62",
+                "description": "The ID of this security group rule."
+              },
+              "tenant_id": {
+                "type": "string",
+                "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+                "description": "The ID of the project."
+              },
+              "security_group_id": {
+                "type": "string",
+                "example": "a7734e61-b545-452d-a3cd-0189cbd9747a",
+                "description": "The security group ID to associate with this security group rule."
+              },
+              "direction": {
+                "type": "string",
+                "example": "ingress",
+                "description": "Ingress or egress, which is the direction in which the metering rule is applied."
+              },
+              "ethertype": {
+                "type": "string",
+                "example": "IPv4",
+                "description": "Must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules."
+              },
+              "port_range_max": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80,
+                "description": "The maximum port number in the range that is matched by the security group rule."
+              },
+              "port_range_min": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80,
+                "description": "The minimum port number in the range that is matched by the security group rule."
+              },
+              "protocol": {
+                "type": "string",
+                "example": "tcp",
+                "description": "The IP protocol can be represented by a string, an integer, or null."
+              },
+              "remote_ip_prefix": {
+                "type": "string",
+                "example": "",
+                "description": "The remote IP prefix to associate with this metering rule packet."
+              },
+              "remote_group_id": {
+                "type": "string",
+                "example": "85cc3048-abc3-43cc-89b3-377341426ac5",
+                "description": "The remote group UUID to associate with this security group rule."
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json
new file mode 100644
index 0000000..d68ea46
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json
@@ -0,0 +1,78 @@
+{
+  "type": "object",
+  "description": "",
+  "required": [
+    "security_group_rule"
+  ],
+  "properties": {
+    "security_group_rule": {
+      "type": "object",
+      "required": [
+        "id",
+        "tenant_id",
+        "security_group_id",
+        "direction",
+        "ethertype",
+        "port_range_max",
+        "port_range_min",
+        "protocol",
+        "remote_ip_prefix",
+        "remote_group_id"
+      ],
+      "properties": {
+        "id": {
+          "type": "string",
+          "example": "2bc0accf-312e-429a-956e-e4407625eb62",
+          "description": "The ID of this security group rule."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+          "description": "The ID of the project."
+        },
+        "security_group_id": {
+          "type": "string",
+          "example": "a7734e61-b545-452d-a3cd-0189cbd9747a",
+          "description": "The security group ID to associate with this security group rule."
+        },
+        "direction": {
+          "type": "string",
+          "example": "ingress",
+          "description": "Ingress or egress, which is the direction in which the metering rule is applied."
+        },
+        "ethertype": {
+          "type": "string",
+          "example": "IPv4",
+          "description": "Must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules."
+        },
+        "port_range_max": {
+          "type": "integer",
+          "format": "int32",
+          "example": 80,
+          "description": "The maximum port number in the range that is matched by the security group rule."
+        },
+        "port_range_min": {
+          "type": "integer",
+          "format": "int32",
+          "example": 80,
+          "description": "The minimum port number in the range that is matched by the security group rule."
+        },
+        "protocol": {
+          "type": "string",
+          "example": "tcp",
+          "description": "The IP protocol can be represented by a string, an integer, or null."
+        },
+        "remote_ip_prefix": {
+          "type": "string",
+          "example": "",
+          "description": "The remote IP prefix to associate with this metering rule packet."
+        },
+        "remote_group_id": {
+          "type": "string",
+          "example": "85cc3048-abc3-43cc-89b3-377341426ac5",
+          "description": "The remote group UUID to associate with this security group rule."
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json
new file mode 100644
index 0000000..3a44ff4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json
@@ -0,0 +1,114 @@
+{
+  "type": "object",
+  "description": "A subnet object.",
+  "required": [
+    "subnets"
+  ],
+  "properties": {
+    "subnets": {
+      "type": "object",
+      "required": [
+        "enable_dhcp",
+        "network_id",
+        "tenant_id",
+        "dns_nameservers",
+        "allocation_pools",
+        "host_routes",
+        "ip_version",
+        "gateway_ip",
+        "ipv6_address_mode",
+        "ipv6_ra_mode"
+      ],
+      "properties": {
+        "enable_dhcp": {
+          "type": "boolean",
+          "example": true,
+          "description": "Indicates whether dhcp is enabled or disabled for the subnet."
+        },
+        "network_id": {
+          "type": "string",
+          "example": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+          "description": "The ID of the network to which the subnet belongs."
+        },
+        "tenant_id": {
+          "type": "string",
+          "example": "4fd44f30292945e481c7b8a0c8908869",
+          "description": "The ID of the project."
+        },
+        "dns_nameservers": {
+          "type": "array",
+          "description": "List of dns name servers associated with the subnet.",
+          "items": {
+            "type": "string",
+            "description": "A dns name server instance."
+          }
+        },
+        "allocation_pools": {
+          "type": "array",
+          "description": "Allocation pools with start and end IP addresses for this subnet.",
+          "items": {
+            "type": "object",
+            "description": "An allocation pool.",
+            "required": [
+              "start",
+              "end"
+            ],
+            "properties": {
+              "start": {
+                "type": "string",
+                "example": "192.168.199.2",
+                "description": "A start IP address for this subnet."
+              },
+              "end": {
+                "type": "string",
+                "example": "10.10.10.254",
+                "description": "An end IP address for this subnet."
+              }
+            }
+          }
+        },
+        "host_routes": {
+          "type": "array",
+          "description": "Additional routes for the subnet. A list of dictionaries with destination and nexthop parameters.",
+          "items": {
+            "type": "object",
+            "description": "A route for the subnet.",
+            "required": [
+              "destination",
+              "nexthop"
+            ],
+            "properties": {
+              "destination": {
+                "type": "string",
+                "description": "The destination CIDR."
+              },
+              "nexthop": {
+                "type": "string",
+                "description": "The IP address of the next hop for the corresponding destination."
+              }
+            }
+          }
+        },
+        "ip_version": {
+          "type": "integer",
+          "format": "int32",
+          "example": 4,
+          "description": "The IP protocol version. Value is 4 or 6."
+        },
+        "gateway_ip": {
+          "type": "string",
+          "example": "192.168.199.1",
+          "description": "Gateway IP of this subnet. If the value is null that implies no gateway is associated with the subnet."
+        },
+        "ipv6_address_mode": {
+          "type": "string",
+          "description": "The IPv6 address modes specifies mechanisms for assigning IP addresses."
+        },
+        "ipv6_ra_mode": {
+          "type": "string",
+          "description": "The IPv6 router advertisement specifies whether the networking service should transmit ICMPv6 packets, for a subnet."
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar b/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar
new file mode 100644
index 0000000..bbeee88
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar b/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar
new file mode 100644
index 0000000..ee7b43d
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar b/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar
new file mode 100644
index 0000000..36569b6
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar b/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar
new file mode 100644
index 0000000..db74210
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar
new file mode 100644
index 0000000..c03203f
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar
new file mode 100644
index 0000000..cbe8476
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar
new file mode 100644
index 0000000..964b15a
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar b/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar
new file mode 100644
index 0000000..34084e3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml b/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9194183
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>Openstack Switching REST API v1.0</display-name>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Secured</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>admin</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <security-role>
+        <role-name>admin</role-name>
+    </security-role>
+
+    <login-config>
+        <auth-method>BASIC</auth-method>
+        <realm-name>karaf</realm-name>
+    </login-config>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>javax.ws.rs.Application</param-name>
+            <param-value>org.onosproject.openstacknetworking.web.OpenstackNetworkingWebApplication</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+</web-app>
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
new file mode 100644
index 0000000..866aa55
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
@@ -0,0 +1,678 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.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.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+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.store.service.TestStorageService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_UPDATED;
+import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
+
+/**
+ * Unit tests for OpenStack network manager.
+ */
+public class OpenstackNetworkManagerTest {
+
+    private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+    private static final String UNKNOWN_ID = "unknown_id";
+    private static final String UPDATED_NAME = "updated_name";
+
+    private static final String NETWORK_ID = "network_id";
+    private static final String NETWORK_NAME = "network_name";
+    private static final Network NETWORK = NeutronNetwork.builder()
+            .name(NETWORK_NAME)
+            .build();
+    private static final Network NETWORK_COPY = NeutronNetwork.builder()
+            .name("network")
+            .build();
+
+    private static final String SUBNET_ID = "subnet_id";
+    private static final Subnet SUBNET = NeutronSubnet.builder()
+            .networkId(NETWORK_ID)
+            .cidr("192.168.0.0/24")
+            .build();
+    private static final Subnet SUBNET_COPY = NeutronSubnet.builder()
+            .networkId(NETWORK_ID)
+            .cidr("192.168.0.0/24")
+            .build();
+
+    private static final String PORT_ID = "port_id";
+    private static final Port PORT = NeutronPort.builder()
+            .networkId(NETWORK_ID)
+            .fixedIp("192.168.0.1", SUBNET_ID)
+            .build();
+    private static final Port PORT_COPY = NeutronPort.builder()
+            .networkId(NETWORK_ID)
+            .fixedIp("192.168.0.1", SUBNET_ID)
+            .build();
+
+    private final TestOpenstackNetworkListener testListener = new TestOpenstackNetworkListener();
+
+    private OpenstackNetworkManager target;
+    private DistributedOpenstackNetworkStore osNetworkStore;
+
+    @Before
+    public void setUp() throws Exception {
+        NETWORK.setId(NETWORK_ID);
+        NETWORK_COPY.setId(NETWORK_ID);
+        SUBNET.setId(SUBNET_ID);
+        SUBNET_COPY.setId(SUBNET_ID);
+        PORT.setId(PORT_ID);
+        PORT_COPY.setId(PORT_ID);
+
+        osNetworkStore = new DistributedOpenstackNetworkStore();
+        TestUtils.setField(osNetworkStore, "coreService", new TestCoreService());
+        TestUtils.setField(osNetworkStore, "storageService", new TestStorageService());
+        TestUtils.setField(osNetworkStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+        osNetworkStore.activate();
+
+        target = new OpenstackNetworkManager();
+        TestUtils.setField(target, "coreService", new TestCoreService());
+        TestUtils.setField(target, "packetService", new PacketServiceAdapter());
+        TestUtils.setField(target, "deviceService", new DeviceServiceAdapter());
+        TestUtils.setField(target, "storageService", new TestStorageService());
+        TestUtils.setField(target, "clusterService", new ClusterServiceAdapter());
+        TestUtils.setField(target, "openstacknodeService", new TestOpenstackNodeManager());
+        target.osNetworkStore = osNetworkStore;
+        target.addListener(testListener);
+        target.activate();
+    }
+
+    @After
+    public void tearDown() {
+        target.removeListener(testListener);
+        osNetworkStore.deactivate();
+        target.deactivate();
+        osNetworkStore = null;
+        target = null;
+    }
+
+    /**
+     * Tests if getting all networks returns the correct set of networks.
+     */
+    @Test
+    public void testGetNetworks() {
+        createBasicNetworks();
+        assertEquals("Number of network did not match", 1, target.networks().size());
+    }
+
+    /**
+     * Tests if getting a network with ID returns the correct network.
+     */
+    @Test
+    public void testGetNetworkById() {
+        createBasicNetworks();
+        assertTrue("Network did not match", target.network(NETWORK_ID) != null);
+        assertTrue("Network did not match", target.network(UNKNOWN_ID) == null);
+    }
+
+    /**
+     * Tests creating and removing a network, and checks if it triggers proper events.
+     */
+    @Test
+    public void testCreateAndRemoveNetwork() {
+        target.createNetwork(NETWORK);
+        assertEquals("Number of networks did not match", 1, target.networks().size());
+        assertTrue("Network was not created", target.network(NETWORK_ID) != null);
+
+        target.removeNetwork(NETWORK_ID);
+        assertEquals("Number of networks did not match", 0, target.networks().size());
+        assertTrue("Network was not removed", target.network(NETWORK_ID) == null);
+
+        validateEvents(OPENSTACK_NETWORK_CREATED, OPENSTACK_NETWORK_REMOVED);
+    }
+
+    /**
+     * Tests updating a network, and checks if it triggers proper events.
+     */
+    @Test
+    public void testCreateAndUpdateNetwork() {
+        target.createNetwork(NETWORK);
+        assertEquals("Number of networks did not match", 1, target.networks().size());
+        assertEquals("Network did not match", NETWORK_NAME, target.network(NETWORK_ID).getName());
+
+        final Network updated = NeutronNetwork.builder()
+                .from(NETWORK_COPY)
+                .name(UPDATED_NAME)
+                .build();
+        target.updateNetwork(updated);
+
+        assertEquals("Number of networks did not match", 1, target.networks().size());
+        assertEquals("Network did not match", UPDATED_NAME, target.network(NETWORK_ID).getName());
+        validateEvents(OPENSTACK_NETWORK_CREATED, OPENSTACK_NETWORK_UPDATED);
+    }
+
+    /**
+     * Tests if creating a null network fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullNetwork() {
+        target.createNetwork(null);
+    }
+
+    /**
+     * Tests if creating a network with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateNetworkWithNullId() {
+        final Network testNet = NeutronNetwork.builder().build();
+        target.createNetwork(testNet);
+    }
+
+    /**
+     * Tests if creating a duplicate network fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateDuplicateNetwork() {
+        target.createNetwork(NETWORK);
+        target.createNetwork(NETWORK);
+    }
+
+    /**
+     * Tests if removing network with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRemoveNetworkWithNull() {
+        target.removeNetwork(null);
+    }
+
+    /**
+     * Tests if updating a network with null name fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateNetworkWithNullName() {
+        final Network updated = NeutronNetwork.builder()
+                .name(null)
+                .build();
+        updated.setId(NETWORK_ID);
+        target.updateNetwork(updated);
+    }
+
+    /**
+     * Tests if updating an unregistered network fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateUnregisteredNetwork() {
+        target.updateNetwork(NETWORK);
+    }
+
+    /**
+     * Tests if updating a network with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateNetworkWithNullId() {
+        final Network testNet = NeutronNetwork.builder().build();
+        target.updateNetwork(testNet);
+    }
+
+
+    /**
+     * Tests if getting all subnets returns the correct set of subnets.
+     */
+    @Test
+    public void testGetSubnets() {
+        createBasicNetworks();
+        assertEquals("Number of subnet did not match", 1, target.subnets().size());
+    }
+
+    @Test
+    public void testGetSubnetsByNetworkId() {
+        createBasicNetworks();
+        assertEquals("Subnet did not match", 1, target.subnets(NETWORK_ID).size());
+        assertEquals("Subnet did not match", 0, target.subnets(UNKNOWN_ID).size());
+    }
+
+    /**
+     * Tests if getting a subnet with ID returns the correct subnet.
+     */
+    @Test
+    public void testGetSubnetById() {
+        createBasicNetworks();
+        assertTrue("Subnet did not match", target.subnet(SUBNET_ID) != null);
+        assertTrue("Subnet did not match", target.subnet(UNKNOWN_ID) == null);
+    }
+
+    /**
+     * Tests creating and removing a subnet, and checks if it triggers proper events.
+     */
+    @Test
+    public void testCreateAndRemoveSubnet() {
+        target.createSubnet(SUBNET);
+        assertEquals("Number of subnet did not match", 1, target.subnets().size());
+        assertTrue("Subnet was not created", target.subnet(SUBNET_ID) != null);
+
+        target.removeSubnet(SUBNET_ID);
+        assertEquals("Number of subnet did not match", 0, target.subnets().size());
+        assertTrue("Subnet was not removed", target.subnet(SUBNET_ID) == null);
+
+        validateEvents(OPENSTACK_SUBNET_CREATED, OPENSTACK_SUBNET_REMOVED);
+    }
+
+    /**
+     * Tests updating a subnet, and checks if it triggers proper events.
+     */
+    @Test
+    public void testCreateAndUpdateSubnet() {
+        target.createSubnet(SUBNET_COPY);
+        assertEquals("Number of subnet did not match", 1, target.subnets().size());
+        assertEquals("Subnet did not match", null, target.subnet(SUBNET_ID).getName());
+
+        // TODO fix NeutronSubnet.builder().from() in openstack4j
+        final Subnet updated = NeutronSubnet.builder()
+                .networkId(NETWORK_ID)
+                .cidr("192.168.0.0/24")
+                .name(UPDATED_NAME)
+                .build();
+        updated.setId(SUBNET_ID);
+        target.updateSubnet(updated);
+
+        assertEquals("Number of subnet did not match", 1, target.subnets().size());
+        assertEquals("Subnet did not match", UPDATED_NAME, target.subnet(SUBNET_ID).getName());
+
+        validateEvents(OPENSTACK_SUBNET_CREATED, OPENSTACK_SUBNET_UPDATED);
+    }
+
+    /**
+     * Tests if creating a null subnet fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullSubnet() {
+        target.createSubnet(null);
+    }
+
+    /**
+     * Tests if creating a subnet with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateSubnetWithNullId() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .networkId(NETWORK_ID)
+                .cidr("192.168.0.0/24")
+                .build();
+        target.createSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if creating subnet with null network ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateSubnetWithNullNetworkId() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .cidr("192.168.0.0/24")
+                .build();
+        testSubnet.setId(SUBNET_ID);
+        target.createSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if creating a subnet with null CIDR fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateSubnetWithNullCidr() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .networkId(NETWORK_ID)
+                .build();
+        testSubnet.setId(SUBNET_ID);
+        target.createSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if creating a duplicate subnet fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateDuplicateSubnet() {
+        target.createSubnet(SUBNET);
+        target.createSubnet(SUBNET);
+    }
+
+    /**
+     * Tests if updating an unregistered subnet fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateUnregisteredSubnet() {
+        target.updateSubnet(SUBNET);
+    }
+
+    /**
+     * Tests if updating a null subnet fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testUpdateSubnetWithNull() {
+        target.updateSubnet(null);
+    }
+
+    /**
+     * Tests if updating a subnet with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateSubnetWithNullId() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .networkId(NETWORK_ID)
+                .cidr("192.168.0.0/24")
+                .build();
+        target.updateSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if updating a subnet with null network ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateSubnetWithNullNetworkId() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .cidr("192.168.0.0/24")
+                .build();
+        testSubnet.setId(SUBNET_ID);
+        target.updateSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if updating a subnet with null CIDR fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateSubnetWithNullCidr() {
+        final Subnet testSubnet = NeutronSubnet.builder()
+                .networkId(NETWORK_ID)
+                .build();
+        testSubnet.setId(SUBNET_ID);
+        target.updateSubnet(testSubnet);
+    }
+
+    /**
+     * Tests if getting all ports returns correct set of values.
+     */
+    @Test
+    public void testGetPorts() {
+        createBasicNetworks();
+        assertEquals("Number of port did not match", 1, target.ports().size());
+    }
+
+    /**
+     * Tests if getting a port with network ID returns correct set of values.
+     */
+    @Test
+    public void testGetPortsByNetworkId() {
+        createBasicNetworks();
+        assertEquals("Number of port did not match", 1, target.ports(NETWORK_ID).size());
+        assertEquals("Number of port did not match", 0, target.ports(UNKNOWN_ID).size());
+    }
+
+    /**
+     * Tests if getting a port with ID returns correct value.
+     */
+    @Test
+    public void testGetPortById() {
+        createBasicNetworks();
+        assertTrue("Port did not match", target.port(PORT_ID) != null);
+        assertTrue("Port did not match", target.port(UNKNOWN_ID) == null);
+    }
+
+    /**
+     * Tests creating and removing a port, and checks if proper event is triggered.
+     */
+    @Test
+    public void testCreateAndRemovePort() {
+        target.createPort(PORT);
+        assertEquals("Number of port did not match", 1, target.ports().size());
+        assertTrue("Port was not created", target.port(PORT_ID) != null);
+
+        target.removePort(PORT_ID);
+        assertEquals("Number of port did not match", 0, target.ports().size());
+        assertTrue("Port was not created", target.port(PORT_ID) == null);
+
+        validateEvents(OPENSTACK_PORT_CREATED, OPENSTACK_PORT_REMOVED);
+    }
+
+    /**
+     * Tests creating and updating a port, and checks if proper event is triggered.
+     */
+    @Test
+    public void testCreateAndUpdatePort() {
+        target.createPort(PORT);
+        assertEquals("Number of port did not match", 1, target.ports().size());
+        assertEquals("Port did not match", null, target.port(PORT_ID).getName());
+
+        final Port updated = PORT_COPY.toBuilder()
+                .name(UPDATED_NAME)
+                .build();
+        target.updatePort(updated);
+
+        assertEquals("Number of port did not match", 1, target.ports().size());
+        assertEquals("Port did not match", UPDATED_NAME, target.port(PORT_ID).getName());
+
+        validateEvents(OPENSTACK_PORT_CREATED, OPENSTACK_PORT_UPDATED);
+    }
+
+    /**
+     * Tests if creating a null port fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullPort() {
+        target.createPort(null);
+    }
+
+    /**
+     * Tests if creating a port with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreatePortWithNullId() {
+        final Port testPort = NeutronPort.builder()
+                .networkId(NETWORK_ID)
+                .build();
+        target.createPort(testPort);
+    }
+
+    /**
+     * Tests if creating a port with null network ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreatePortWithNullNetworkId() {
+        final Port testPort = NeutronPort.builder().build();
+        testPort.setId(PORT_ID);
+        target.createPort(testPort);
+    }
+
+    /**
+     * Tests if creating a duplicate port fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void createDuplicatePort() {
+        target.createPort(PORT);
+        target.createPort(PORT);
+    }
+
+    /**
+     * Tests if updating an unregistered port fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateUnregisteredPort() {
+        target.updatePort(PORT);
+    }
+
+    /**
+     * Tests if updating a null port fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testUpdateNullPort() {
+        target.updatePort(null);
+    }
+
+    /**
+     * Tests if updating a port with null ID fails with exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdatePortWithNullId() {
+        final Port testPort = NeutronPort.builder()
+                .networkId(NETWORK_ID)
+                .build();
+        target.updatePort(testPort);
+    }
+
+    /**
+     * Tests if updating a port with null network ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdatePortWithNullNetworkId() {
+        final Port testPort = NeutronPort.builder().build();
+        testPort.setId(PORT_ID);
+        target.updatePort(testPort);
+    }
+
+    private void createBasicNetworks() {
+        target.createNetwork(NETWORK);
+        target.createSubnet(SUBNET);
+        target.createPort(PORT);
+    }
+
+    private static class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return TEST_APP_ID;
+        }
+    }
+
+    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))
+                    .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 TestOpenstackNetworkListener implements OpenstackNetworkListener {
+        private List<OpenstackNetworkEvent> events = Lists.newArrayList();
+
+        @Override
+        public void event(OpenstackNetworkEvent event) {
+            events.add(event);
+        }
+    }
+
+    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();
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java
new file mode 100644
index 0000000..b4afb75
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.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.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.store.service.TestStorageService;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.openstacknetworking.api.OpenstackRouterEvent.Type.*;
+
+/**
+ * Unit tests for OpenStack router manager.
+ */
+public class OpenstackRouterManagerTest {
+
+    private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+    private static final String UNKNOWN_ID = "unknown_id";
+    private static final String ROUTER_ID = "router_1";
+    private static final String ROUTER_NAME = "router_1";
+    private static final Router ROUTER = NeutronRouter.builder()
+            .id(ROUTER_ID)
+            .name(ROUTER_NAME)
+            .build();
+
+    private final TestOpenstackRouterListener testListener = new TestOpenstackRouterListener();
+
+    private OpenstackRouterManager target;
+    private DistributedOpenstackRouterStore osRouterStore;
+
+    @Before
+    public void setUp() throws Exception {
+        osRouterStore = new DistributedOpenstackRouterStore();
+        TestUtils.setField(osRouterStore, "coreService", new TestCoreService());
+        TestUtils.setField(osRouterStore, "storageService", new TestStorageService());
+        TestUtils.setField(osRouterStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+        osRouterStore.activate();
+
+        target = new OpenstackRouterManager();
+        target.coreService = new TestCoreService();
+        target.osRouterStore = osRouterStore;
+        target.addListener(testListener);
+        target.activate();
+    }
+
+    @After
+    public void tearDown() {
+        target.removeListener(testListener);
+        osRouterStore.deactivate();
+        target.deactivate();
+        osRouterStore = null;
+        target = null;
+    }
+
+    /**
+     * Tests if getting all routers returns correct set of values.
+     */
+    @Test
+    public void testGetRouters() {
+        createBasicRouters();
+        assertEquals("Number of router did not match", 1, target.routers().size());
+    }
+
+    /**
+     * Tests if getting a router with ID returns correct value.
+     */
+    @Test
+    public void testGetRouterById() {
+        createBasicRouters();
+        assertTrue("Router did not exist", target.router(ROUTER_ID) != null);
+        assertTrue("Router did not exist", target.router(UNKNOWN_ID) == null);
+    }
+
+    /**
+     * Tests creating and removing a router, and checks if proper event is triggered.
+     */
+    @Test
+    public void testCreateAndRemoveRouter() {
+        target.createRouter(ROUTER);
+        assertEquals("Number of router did not match", 1, target.routers().size());
+        assertTrue("Router was not created", target.router(ROUTER_ID) != null);
+
+        target.removeRouter(ROUTER.getId());
+        assertEquals("Number of router did not match", 0, target.routers().size());
+        assertTrue("Router was not removed", target.router(ROUTER_ID) == null);
+
+        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_REMOVED);
+    }
+
+    /**
+     * Tests creating and updating a router, and checks if proper event is triggered.
+     */
+    @Test
+    public void testCreateAndUpdateRouter() {
+        target.createRouter(ROUTER);
+        assertEquals("Number of router did not match", 1, target.routers().size());
+        assertEquals("Router did not match", ROUTER_NAME, target.router(ROUTER_ID).getName());
+
+        final Router updated = NeutronRouter.builder()
+                .id(ROUTER_ID)
+                .name("updated-name")
+                .build();
+
+        target.updateRouter(updated);
+        assertEquals("Number of router did not match", 1, target.routers().size());
+        assertEquals("Router did not match", "updated-name", target.router(ROUTER_ID).getName());
+
+        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED);
+    }
+
+    /**
+     * Tests adding and removing external gateway to a router, and checks if
+     * proper events are triggered.
+     */
+    @Test
+    public void testAddAndRemoveExternalGateway() {
+        target.createRouter(ROUTER);
+        assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);
+
+        Router updated = NeutronRouter.builder()
+                .id(ROUTER_ID)
+                .name(ROUTER_NAME)
+                .externalGateway("test-network-id")
+                .build();
+
+        target.updateRouter(updated);
+        assertEquals("Router did not match",
+                "test-network-id",
+                target.router(ROUTER_ID).getExternalGatewayInfo().getNetworkId());
+
+        target.updateRouter(ROUTER);
+        assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);
+
+        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED,
+                OPENSTACK_ROUTER_GATEWAY_ADDED,
+                OPENSTACK_ROUTER_UPDATED,
+                OPENSTACK_ROUTER_GATEWAY_REMOVED);
+    }
+
+    /**
+     * Tests if creating a router with null value fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateRouterWithNull() {
+        target.createRouter(null);
+    }
+
+    /**
+     * Tests if creating a router with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateRouterWithNullId() {
+        final Router testRouter = NeutronRouter.builder()
+                .id(null)
+                .name(ROUTER_NAME)
+                .build();
+        target.createRouter(testRouter);
+    }
+
+    /**
+     * Tests if updating a router with null name fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateRouterWithNullName() {
+        final Router testRouter = NeutronRouter.builder()
+                .id(ROUTER_ID)
+                .name(null)
+                .build();
+        target.createRouter(testRouter);
+    }
+
+    /**
+     * Tests if creating a duplicate router fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateDuplicateRouter() {
+        target.createRouter(ROUTER);
+        target.createRouter(ROUTER);
+    }
+
+    /**
+     * Tests if updating a router with null value fails with an exception.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testUpdateRouterWithNull() {
+        target.updateRouter(null);
+    }
+
+    /**
+     * Tests if updating a router with null ID fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateRouterWithNullId() {
+        final Router testRouter = NeutronRouter.builder()
+                .id(null)
+                .name(ROUTER_NAME)
+                .build();
+        target.updateRouter(testRouter);
+    }
+
+    /**
+     * Tests if updating a router with null name fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateRouterWithNullName() {
+        final Router testRouter = NeutronRouter.builder()
+                .id(ROUTER_ID)
+                .name(null)
+                .build();
+        target.updateRouter(testRouter);
+    }
+
+    /**
+     * Tests if updating an unregistered router fails with an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateUnregisteredRouter() {
+        target.updateRouter(ROUTER);
+    }
+
+    // TODO fix openstack4j floating IP data model and add unit tests
+
+    private void createBasicRouters() {
+        target.createRouter(ROUTER);
+    }
+
+    private static class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return TEST_APP_ID;
+        }
+    }
+
+    private static class TestOpenstackRouterListener implements OpenstackRouterListener {
+        private List<OpenstackRouterEvent> events = Lists.newArrayList();
+
+        @Override
+        public void event(OpenstackRouterEvent event) {
+            events.add(event);
+        }
+    }
+
+    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();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java
new file mode 100644
index 0000000..3e98a5d
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack floating IP REST API.
+ */
+public class OpenstackFloatingIpWebResourceTest extends ResourceTest {
+
+    final OpenstackRouterAdminService mockOpenstackRouterAdminService =
+            createMock(OpenstackRouterAdminService.class);
+    private static final String PATH = "floatingips";
+
+    /**
+     * Constructs an openstack floating IP test instance.
+     */
+    public OpenstackFloatingIpWebResourceTest() {
+        super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+    }
+
+    /**
+     * Sets up the global values for all tests.
+     */
+    @Before
+    public void setUpTest() {
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(OpenstackRouterAdminService.class,
+                                mockOpenstackRouterAdminService);
+        setServiceDirectory(testDirectory);
+
+    }
+
+    /**
+     * Tests the results of the REST API POST with creation operation.
+     */
+    @Test
+    public void testCreateFloatingIpWithCreationOperation() {
+        mockOpenstackRouterAdminService.createFloatingIp(anyObject());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("openstack-floatingip.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(201));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API POST with incorrect input.
+     */
+    @Test
+    public void testCreateFloatingIpWithIncorrectInput() {
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("dummy.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+    }
+
+    /**
+     * Tests the results of the REST API POST with duplicated floating IP.
+     */
+    @Test
+    public void testCreateFloatingIpWithDuplicatedIp() {
+        mockOpenstackRouterAdminService.createFloatingIp(anyObject());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("openstack-floatingip.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API PUT with updating operation.
+     */
+    @Test
+    public void testUpdateFloatingIpWithUpdatingOperation() {
+        mockOpenstackRouterAdminService.updateFloatingIp(anyObject());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("openstack-floatingip.json");
+
+        Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(200));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API PUT with incorrect input.
+     */
+    @Test
+    public void testUpdateFloatingIpWithIncorrectInput() {
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("dummy.json");
+
+        Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+    }
+
+    /**
+     * Tests the results of the REST API PUT with non-existing ID.
+     */
+    @Test
+    public void testUpdateFloatingIpWithNonexistId() {
+        mockOpenstackRouterAdminService.updateFloatingIp(anyObject());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+                .getResourceAsStream("openstack-floatingip.json");
+
+        Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE with deletion operation.
+     */
+    @Test
+    public void testDeleteFloatingIpWithDeletionOperation() {
+        mockOpenstackRouterAdminService.removeFloatingIp(anyString());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+
+        Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertThat(status, is(204));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE with non-existing ID.
+     */
+    @Test
+    public void testDeleteFloatingIpWithNonexistId() {
+        mockOpenstackRouterAdminService.removeFloatingIp(anyString());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackRouterAdminService);
+
+        final WebTarget wt = target();
+
+        Response response = wt.path(PATH + "/non-exist-id")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackRouterAdminService);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java
new file mode 100644
index 0000000..bbac93b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack network REST API.
+ */
+public class OpenstackNetworkWebResourceTest extends ResourceTest {
+
+    final OpenstackNetworkAdminService mockOpenstackNetworkAdminService =
+            createMock(OpenstackNetworkAdminService.class);
+    private static final String PATH = "networks";
+
+    /**
+     * Constructs an openstack network test instance.
+     */
+    public OpenstackNetworkWebResourceTest() {
+        super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+    }
+
+    /**
+     * Sets up the global values for all tests.
+     */
+    @Before
+    public void setUpTest() {
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(OpenstackNetworkAdminService.class,
+                                mockOpenstackNetworkAdminService);
+        setServiceDirectory(testDirectory);
+    }
+
+    /**
+     * Tests the results of the REST API POST with creation operation.
+     */
+    @Test
+    public void testCreateNetworkWithCreationOperation() {
+        mockOpenstackNetworkAdminService.createNetwork(anyObject());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-network.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(201));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API POST with incorrect input.
+     */
+    @Test
+    public void testCreateNetworkWithIncorrectInput() {
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("dummy.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+    }
+
+    /**
+     * Tests the results of the REST API POST with duplicated network ID.
+     */
+    @Test
+    public void testCreateNetworkWithDuplicatedId() {
+        mockOpenstackNetworkAdminService.createNetwork(anyObject());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-network.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API PUT with updating operation.
+     */
+    @Test
+    public void testUpdateNetworkWithUpdatingOperation() {
+        mockOpenstackNetworkAdminService.updateNetwork(anyObject());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-network.json");
+
+        Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(200));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API PUT with incorrect input.
+     */
+    @Test
+    public void testUpdateNetworkWithIncorrectInput() {
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("dummy.json");
+
+        Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+    }
+
+    /**
+     * Tests the results of the REST API PUT with non-existing network ID.
+     */
+    @Test
+    public void testUpdateNetworkWithNonexistId() {
+        mockOpenstackNetworkAdminService.updateNetwork(anyObject());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-network.json");
+
+        Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE with deletion operation.
+     */
+    @Test
+    public void testDeleteNetworkWithDeletionOperation() {
+        mockOpenstackNetworkAdminService.removeNetwork(anyString());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+
+        Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertThat(status, is(204));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE with non-existing network ID.
+     */
+    @Test
+    public void testDeleteNetworkWithNonexistId() {
+        mockOpenstackNetworkAdminService.removeNetwork(anyString());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+
+        Response response = wt.path(PATH + "/non-exist-id")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java
new file mode 100644
index 0000000..e3f4bf0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack port REST API.
+ */
+public class OpenstackPortWebResourceTest extends ResourceTest {
+
+    final OpenstackNetworkAdminService mockOpenstackNetworkAdminService =
+            createMock(OpenstackNetworkAdminService.class);
+    private static final String PATH = "ports";
+
+    /**
+     * Constructs an openstack port test instance.
+     */
+    public OpenstackPortWebResourceTest() {
+        super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+    }
+
+    /**
+     * Sets up the global values for all tests.
+     */
+    @Before
+    public void setUpTest() {
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(OpenstackNetworkAdminService.class,
+                                mockOpenstackNetworkAdminService);
+        setServiceDirectory(testDirectory);
+
+    }
+
+    /**
+     * Tests the results of the REST API POST with creation operation.
+     */
+    @Test
+    public void testCreatePortWithCreationOperation() {
+        mockOpenstackNetworkAdminService.createPort(anyObject());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-port.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(201));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API POST with incorrect input.
+     */
+    @Test
+    public void testCreatePortWithIncorrectInput() {
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackPortWebResourceTest.class
+                .getResourceAsStream("dummy.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+    }
+
+    /**
+     * Tests the results of the REST API POST with duplicated port ID.
+     */
+    @Test
+    public void testCreatePortWithDuplicatedId() {
+        mockOpenstackNetworkAdminService.createPort(anyObject());
+        expectLastCall().andThrow(new IllegalArgumentException());
+        replay(mockOpenstackNetworkAdminService);
+
+        final WebTarget wt = target();
+        InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+                .getResourceAsStream("openstack-port.json");
+
+        Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        final int status = response.getStatus();
+
+        assertThat(status, is(400));
+
+        verify(mockOpenstackNetworkAdminService);
+    }
+