ONOS-5182 Simplified OpenStack networking application structure

Change-Id: Ic7941f2c9a2febec4f24745278c4c305a3937097
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
new file mode 100644
index 0000000..4ba121a
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+/**
+ * Provides constants used in OpenStackSwitching.
+ */
+public final class Constants {
+
+    private Constants() {
+    }
+
+    public static final String SWITCHING_APP_ID = "org.onosproject.openstackswitching";
+    public static final String ROUTING_APP_ID = "org.onosproject.openstackrouting";
+
+    public static final String DEVICE_OWNER_ROUTER_INTERFACE = "network:router_interface";
+    public static final String DEVICE_OWNER_ROUTER_GATEWAY = "network:router_gateway";
+    public static final String DEVICE_OWNER_FLOATING_IP = "network:floatingip";
+
+    public static final String PORT_NAME_PREFIX_VM = "tap";
+    public static final String PORT_NAME_PREFIX_TUNNEL = "vxlan";
+
+    public static final String DEFAULT_GATEWAY_MAC_STR = "fe:00:00:00:00:02";
+    public static final MacAddress DEFAULT_GATEWAY_MAC = MacAddress.valueOf(DEFAULT_GATEWAY_MAC_STR);
+    // TODO make this configurable
+    public static final MacAddress DEFAULT_EXTERNAL_ROUTER_MAC = MacAddress.valueOf("fe:00:00:00:00:01");
+
+    public static final Ip4Address DNS_SERVER_IP = Ip4Address.valueOf("8.8.8.8");
+    public static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
+    public static final int DHCP_INFINITE_LEASE = -1;
+
+    public static final String NETWORK_ID = "networkId";
+    public static final String SUBNET_ID = "subnetId";
+    public static final String PORT_ID = "portId";
+    public static final String VXLAN_ID = "vxlanId";
+    public static final String TENANT_ID = "tenantId";
+    public static final String GATEWAY_IP = "gatewayIp";
+    public static final String CREATE_TIME = "createTime";
+
+    public static final int SWITCHING_RULE_PRIORITY = 30000;
+    public static final int TUNNELTAG_RULE_PRIORITY = 30000;
+    public static final int ACL_RULE_PRIORITY = 30000;
+    public static final int EW_ROUTING_RULE_PRIORITY = 28000;
+
+    public static final int GATEWAY_ICMP_PRIORITY = 43000;
+    public static final int ROUTING_RULE_PRIORITY = 25000;
+    public static final int FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY = 42000;
+    public static final int FLOATING_RULE_PRIORITY = 41000;
+    public static final int PNAT_RULE_PRIORITY = 26000;
+    public static final int PNAT_TIMEOUT = 120;
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackFloatingIpService.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackFloatingIpService.java
new file mode 100644
index 0000000..9988a4e
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackFloatingIpService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onosproject.net.Host;
+
+import org.onosproject.openstackinterface.OpenstackFloatingIP;
+
+/**
+ * Handles floating IP update requests from OpenStack.
+ */
+public interface OpenstackFloatingIpService {
+
+    enum Action {
+        ASSOCIATE,
+        DISSASSOCIATE
+    }
+
+    /**
+     * Handles floating IP create request from OpenStack.
+     *
+     * @param floatingIp floating IP information
+     */
+    void createFloatingIp(OpenstackFloatingIP floatingIp);
+
+    /**
+     * Handles floating IP update request from OpenStack.
+     *
+     * @param floatingIp floating IP information
+     */
+    void updateFloatingIp(OpenstackFloatingIP floatingIp);
+
+    /**
+     * Handles floating IP remove request from OpenStack.
+     *
+     * @param floatingIpId floating ip identifier
+     */
+    void deleteFloatingIp(String floatingIpId);
+
+    /**
+     * Handles to purge data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void purgeVmFlow(Host host);
+
+    /**
+     * Handles to reinstall data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void reinstallVmFlow(Host host);
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRoutingService.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRoutingService.java
new file mode 100644
index 0000000..29e492e
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRoutingService.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onosproject.net.Host;
+
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstackinterface.OpenstackRouterInterface;
+
+/**
+ * Handles router update requests from OpenStack.
+ */
+public interface OpenstackRoutingService {
+
+    /**
+     * Handles the router create request from OpenStack.
+     *
+     * @param osRouter router information
+     */
+    void createRouter(OpenstackRouter osRouter);
+
+    /**
+     * Handles the router update request from OpenStack.
+     * Update router is called when the name, administrative state, or the external
+     * gateway setting is updated. The external gateway update is the only case
+     * that openstack routing service cares.
+     *
+     * @param osRouter router information
+     */
+    void updateRouter(OpenstackRouter osRouter);
+
+    /**
+     * Handles the router remove request from OpenStack.
+     *
+     * @param osRouterId identifier of the router
+     */
+    void removeRouter(String osRouterId);
+
+    /**
+     * Handles router interface add request from OpenStack.
+     *
+     * @param osInterface router interface information
+     */
+    void addRouterInterface(OpenstackRouterInterface osInterface);
+
+    /**
+     * Handles router interface remove request from OpenStack.
+     *
+     * @param osInterface router interface information
+     */
+    void removeRouterInterface(OpenstackRouterInterface osInterface);
+
+    /**
+     * Handles to purge data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void purgeVmFlow(Host host);
+
+    /**
+     * Handles to reinstall data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void reinstallVmFlow(Host host);
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSecurityGroupService.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSecurityGroupService.java
new file mode 100644
index 0000000..1709c95
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSecurityGroupService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onosproject.net.Host;
+
+import org.onosproject.openstackinterface.OpenstackPort;
+
+/**
+ * Represents OpenstackSecurityGroupService Interface.
+ */
+public interface OpenstackSecurityGroupService {
+
+    /**
+     * Updates the flow rules for Security Group for the VM (OpenstackPort).
+     *
+     * @param osPort OpenstackPort information for the VM
+     */
+    void updateSecurityGroup(OpenstackPort osPort);
+
+    /**
+     * Handles to purge data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void purgeVmFlow(Host host);
+
+    /**
+     * Handles to reinstall data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void reinstallVmFlow(Host host);
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSwitchingService.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSwitchingService.java
new file mode 100644
index 0000000..dc1113e
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackSwitchingService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onosproject.net.Host;
+
+/**
+ * Handles switch update requests from OpenStack and  CLI.
+ */
+public interface OpenstackSwitchingService {
+    /**
+     * Handles to purge data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void purgeVmFlow(Host host);
+
+    /**
+     * Handles to reinstall data plane flow of existing VM.
+     *
+     * @param host VM Host information
+     */
+    void reinstallVmFlow(Host host);
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/package-info.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/package-info.java
new file mode 100644
index 0000000..0cd17c1
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Application for OpenstackRouting.
+ */
+package org.onosproject.openstacknetworking.api;
\ No newline at end of file
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstancePurgeFlowsCommand.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstancePurgeFlowsCommand.java
new file mode 100644
index 0000000..b2f5b8d
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstancePurgeFlowsCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostService;
+
+import org.onosproject.openstacknetworking.api.OpenstackSwitchingService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknetworking.api.OpenstackRoutingService;
+import org.onosproject.openstacknetworking.api.OpenstackFloatingIpService;
+
+import static org.onosproject.openstacknetworking.api.Constants.*;
+
+/**
+ * Purge Flows of OpenstackInstance Data Plane.
+ */
+
+@Command(scope = "onos", name = "openstack-purge-flows",
+        description = "Purge data plane flows of existing VM.")
+public class OpenstackInstancePurgeFlowsCommand extends AbstractShellCommand {
+
+    @Option(name = "-a", aliases = "--all",
+            description = "HostIDs are all existing VM",
+            required = false, multiValued = false)
+    private Boolean allhost = false;
+
+    @Argument(index = 0, name = "hostId", description = "HostID(s)",
+            required = false, multiValued = true)
+    private String[] hostids = null;
+
+    @Override
+    protected void execute() {
+         HostService hostService = AbstractShellCommand.get(HostService.class);
+
+         OpenstackSwitchingService switchingService = getService(OpenstackSwitchingService.class);
+         OpenstackSecurityGroupService sgService = getService(OpenstackSecurityGroupService.class);
+         OpenstackRoutingService routingService = getService(OpenstackRoutingService.class);
+         OpenstackFloatingIpService floatingIpService = getService(OpenstackFloatingIpService.class);
+
+         if (allhost) {
+            switchingService.purgeVmFlow(null);
+            sgService.purgeVmFlow(null);
+            routingService.purgeVmFlow(null);
+            floatingIpService.purgeVmFlow(null);
+
+            hostService.getHosts().forEach(host -> {
+                printHost(host);
+            });
+         } else if (hostids != null) {
+            for (String hostid : hostids) {
+                Host host = hostService.getHost(HostId.hostId(hostid));
+                if (host == null) {
+                    continue;
+                }
+                switchingService.purgeVmFlow(host);
+                sgService.purgeVmFlow(host);
+                routingService.purgeVmFlow(host);
+                floatingIpService.purgeVmFlow(host);
+                printHost(host);
+            }
+         }
+    }
+
+    private void printHost(Host host) {
+        print("Purge data plane flows of VM(hostid=%s, vni=%s, ip=%s, mac=%s).",
+              host.id(), host.annotations().value(VXLAN_ID),
+              host.ipAddresses().stream().findFirst().get().getIp4Address(), host.mac());
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstanceReInstallFlowCommand.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstanceReInstallFlowCommand.java
new file mode 100644
index 0000000..eda334d
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackInstanceReInstallFlowCommand.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostService;
+
+import org.onosproject.openstacknetworking.api.OpenstackSwitchingService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknetworking.api.OpenstackRoutingService;
+import org.onosproject.openstacknetworking.api.OpenstackFloatingIpService;
+
+import static org.onosproject.openstacknetworking.api.Constants.*;
+
+/**
+ * Re-Install Flows of OpenstackInstance Data Plane.
+ */
+
+@Command(scope = "onos", name = "openstack-reinstall-flows",
+        description = "Re-install data plane flows of existing VM.")
+public class OpenstackInstanceReInstallFlowCommand extends AbstractShellCommand {
+
+    @Option(name = "-a", aliases = "--all",
+    description = "HostIDs are all existing VM", required = false, multiValued = false)
+    private Boolean allhost = false;
+
+    @Argument(index = 0, name = "hostId", description = "HostID(s)",
+            required = false, multiValued = true)
+    private String[] hostids = null;
+
+    @Override
+    protected void execute() {
+         HostService hostService = AbstractShellCommand.get(HostService.class);
+
+         OpenstackSwitchingService switchingService = getService(OpenstackSwitchingService.class);
+         OpenstackSecurityGroupService sgService = getService(OpenstackSecurityGroupService.class);
+         OpenstackRoutingService routingService = getService(OpenstackRoutingService.class);
+         OpenstackFloatingIpService floatingIpService = getService(OpenstackFloatingIpService.class);
+
+         if (allhost) {
+            hostService.getHosts().forEach(host -> {
+                switchingService.reinstallVmFlow(host);
+                sgService.reinstallVmFlow(host);
+                routingService.reinstallVmFlow(host);
+                floatingIpService.reinstallVmFlow(host);
+                printHost(host);
+
+            });
+         } else if (hostids != null) {
+            for (String hostid : hostids) {
+                Host host = hostService.getHost(HostId.hostId(hostid));
+                if (host == null) {
+                    continue;
+                }
+                switchingService.reinstallVmFlow(host);
+                sgService.reinstallVmFlow(host);
+                routingService.reinstallVmFlow(host);
+                floatingIpService.reinstallVmFlow(host);
+                printHost(host);
+            }
+         }
+    }
+
+    private void printHost(Host host) {
+        print("Re-install data plane flows of VM(hostid=%s, vni=%s, ip=%s, mac=%s).",
+              host.id(), host.annotations().value(VXLAN_ID),
+              host.ipAddresses().stream().findFirst().get().getIp4Address(), host.mac());
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
new file mode 100644
index 0000000..92d0a2c
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * CLI implementation for refresh/reprogram the data plane for the existing VM(OpenStackInstance).
+ */
+package org.onosproject.openstacknetworking.cli;
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/AbstractVmHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/AbstractVmHandler.java
new file mode 100644
index 0000000..f37a323
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/AbstractVmHandler.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.Ip4Address;
+import org.onlab.util.Tools;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.slf4j.Logger;
+
+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.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides abstract virtual machine handler.
+ */
+public abstract class AbstractVmHandler {
+    protected final Logger log = getLogger(getClass());
+
+    protected final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    protected CoreService coreService;
+    protected MastershipService mastershipService;
+    protected HostService hostService;
+    protected OpenstackInterfaceService openstackService;
+
+    protected HostListener hostListener = new InternalHostListener();
+
+    protected void activate() {
+        ServiceDirectory services = new DefaultServiceDirectory();
+        coreService = services.get(CoreService.class);
+        mastershipService = services.get(MastershipService.class);
+        openstackService = services.get(OpenstackInterfaceService.class);
+        hostService = services.get(HostService.class);
+        hostService.addListener(hostListener);
+
+        log.info("Started");
+    }
+
+    protected void deactivate() {
+        hostService.removeListener(hostListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    /**
+     * Performs any action when a host is detected.
+     *
+     * @param host detected host
+     */
+    protected abstract void hostDetected(Host host);
+
+    /**
+     * Performs any action when a host is removed.
+     *
+     * @param host removed host
+     */
+    protected abstract void hostRemoved(Host host);
+
+    protected boolean isValidHost(Host host) {
+        return !host.ipAddresses().isEmpty() &&
+                host.annotations().value(VXLAN_ID) != null &&
+                host.annotations().value(NETWORK_ID) != null &&
+                host.annotations().value(TENANT_ID) != null &&
+                host.annotations().value(PORT_ID) != null;
+    }
+
+    protected Set<Host> getVmsInDifferentCnode(Host host) {
+        return Tools.stream(hostService.getHosts())
+                .filter(h -> !h.location().deviceId().equals(host.location().deviceId()))
+                .filter(this::isValidHost)
+                .filter(h -> Objects.equals(getVni(h), getVni(host)))
+                .collect(Collectors.toSet());
+    }
+
+    protected Optional<Host> getVmByPortId(String portId) {
+        return Tools.stream(hostService.getHosts())
+                .filter(this::isValidHost)
+                .filter(host -> host.annotations().value(PORT_ID).equals(portId))
+                .findFirst();
+    }
+
+    protected Set<Host> getHosts(OpenstackSubnet osSubnet) {
+        return Tools.stream(hostService.getHosts())
+                .filter(host -> host.annotations().value(SUBNET_ID).equals(osSubnet.id()))
+                .collect(Collectors.toSet());
+    }
+
+    protected Optional<OpenstackRouter> getRouter(Host host) {
+        return openstackService.routers().stream()
+                .filter(router -> routableSubNets(router.id()).stream()
+                        .filter(subnet -> subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                        .findAny().isPresent())
+                .findAny();
+    }
+
+    protected Set<OpenstackSubnet> routableSubNets(String osRouterId) {
+        return openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                        p.deviceId().equals(osRouterId))
+                .map(p -> openstackService.subnet(p.fixedIps().keySet().stream().findFirst().get()))
+                .collect(Collectors.toSet());
+    }
+
+    protected Ip4Address getIp(Host host) {
+        return host.ipAddresses().stream().findFirst().get().getIp4Address();
+    }
+
+    protected String getVni(Host host) {
+        return host.annotations().value(VXLAN_ID);
+    }
+
+    protected String getTenantId(Host host) {
+        return host.annotations().value(TENANT_ID);
+    }
+
+    private class InternalHostListener implements HostListener {
+
+        @Override
+        public void event(HostEvent event) {
+            Host host = event.subject();
+            if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+                // do not allow to proceed without mastership
+                return;
+            }
+
+            if (!isValidHost(host)) {
+                log.debug("Invalid host event, ignore it {}", host);
+                return;
+            }
+
+            switch (event.type()) {
+                case HOST_UPDATED:
+                case HOST_ADDED:
+                    eventExecutor.execute(() -> hostDetected(host));
+                    break;
+                case HOST_REMOVED:
+                    eventExecutor.execute(() -> hostRemoved(host));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java
new file mode 100644
index 0000000..602b0f7
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.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.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstackinterface.OpenstackFloatingIP;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackFloatingIpService;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+
+
+@Service
+@Component(immediate = true)
+public class OpenstackFloatingIpManager extends AbstractVmHandler implements OpenstackFloatingIpService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    private static final String NOT_ASSOCIATED = "null";
+    private static final KryoNamespace.Builder FLOATING_IP_SERIALIZER =
+            KryoNamespace.newBuilder().register(KryoNamespaces.API);
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
+    private ConsistentMap<IpAddress, Host> floatingIpMap;
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        super.activate();
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        nodeService.addListener(nodeListener);
+        floatingIpMap = storageService.<IpAddress, Host>consistentMapBuilder()
+                .withSerializer(Serializer.using(FLOATING_IP_SERIALIZER.build()))
+                .withName("openstackrouting-floatingip")
+                .withApplicationId(appId)
+                .build();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+        nodeService.removeListener(nodeListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
+        Optional<OpenstackFloatingIP> floatingIp = openstackService.floatingIps().stream()
+                .filter(fip -> fip.fixedIpAddress() != null && fip.fixedIpAddress().equals(hostIp))
+                .findFirst();
+        if (floatingIp.isPresent()) {
+            eventExecutor.execute(() -> associateFloatingIp(floatingIp.get()));
+        }
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
+        Optional<OpenstackFloatingIP> floatingIp = openstackService.floatingIps().stream()
+                .filter(fip -> fip.fixedIpAddress() != null && fip.fixedIpAddress().equals(hostIp))
+                .findFirst();
+        if (floatingIp.isPresent()) {
+            eventExecutor.execute(() -> disassociateFloatingIp(floatingIp.get()));
+        }
+    }
+
+    @Override
+    public void reinstallVmFlow(Host host) {
+        if (host == null) {
+            hostService.getHosts().forEach(h -> {
+                hostDetected(h);
+                log.info("Re-Install data plane flow of virtual machine {}", h);
+            });
+        } else {
+            hostDetected(host);
+            log.info("Re-Install data plane flow of virtual machine {}", host);
+        }
+    }
+
+    @Override
+    public void purgeVmFlow(Host host) {
+        if (host == null) {
+            hostService.getHosts().forEach(h -> {
+                hostRemoved(h);
+                log.info("Purge data plane flow of virtual machine {}", h);
+            });
+        } else {
+            hostRemoved(host);
+            log.info("Purge data plane flow of virtual machine {}", host);
+        }
+    }
+
+    @Override
+    public void createFloatingIp(OpenstackFloatingIP floatingIp) {
+    }
+
+    @Override
+    public void updateFloatingIp(OpenstackFloatingIP floatingIp) {
+        if (Strings.isNullOrEmpty(floatingIp.portId()) ||
+                floatingIp.portId().equals(NOT_ASSOCIATED)) {
+            eventExecutor.execute(() -> disassociateFloatingIp(floatingIp));
+        } else {
+            eventExecutor.execute(() -> associateFloatingIp(floatingIp));
+        }
+    }
+
+    @Override
+    public void deleteFloatingIp(String floatingIpId) {
+    }
+
+    private void associateFloatingIp(OpenstackFloatingIP floatingIp) {
+        Optional<Host> associatedVm = Tools.stream(hostService.getHosts())
+                .filter(host -> Objects.equals(
+                        host.annotations().value(PORT_ID),
+                        floatingIp.portId()))
+                .findAny();
+        if (!associatedVm.isPresent()) {
+            log.warn("Failed to associate floating IP({}) to port:{}",
+                     floatingIp.floatingIpAddress(),
+                     floatingIp.portId());
+            return;
+        }
+
+        floatingIpMap.put(floatingIp.floatingIpAddress(), associatedVm.get());
+        populateFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.get());
+
+        log.info("Associated floating IP {} to fixed IP {}",
+                 floatingIp.floatingIpAddress(), floatingIp.fixedIpAddress());
+    }
+
+    private void disassociateFloatingIp(OpenstackFloatingIP floatingIp) {
+        Versioned<Host> associatedVm = floatingIpMap.remove(floatingIp.floatingIpAddress());
+        if (associatedVm == null) {
+            log.warn("Failed to disassociate floating IP({})",
+                     floatingIp.floatingIpAddress());
+            // No VM is actually associated with the floating IP, do nothing
+            return;
+        }
+
+        removeFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.value());
+        log.info("Disassociated floating IP {} from fixed IP {}",
+                 floatingIp.floatingIpAddress(),
+                 associatedVm.value().ipAddresses());
+    }
+
+    private void populateFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+        populateFloatingIpIncomingRules(floatingIp, associatedVm);
+        populateFloatingIpOutgoingRules(floatingIp, associatedVm);
+    }
+
+    private void removeFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+        Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+        if (!fixedIp.isPresent()) {
+            log.warn("Failed to remove floating IP({}) from {}",
+                     floatingIp, associatedVm);
+            return;
+        }
+
+        TrafficSelector.Builder sOutgoingBuilder = DefaultTrafficSelector.builder();
+        TrafficSelector.Builder sIncomingBuilder = DefaultTrafficSelector.builder();
+
+        sOutgoingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                .matchIPSrc(fixedIp.get().toIpPrefix());
+
+        sIncomingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floatingIp.toIpPrefix());
+
+        gatewayService.getGatewayDeviceIds().forEach(deviceId -> {
+            TrafficSelector.Builder sForTrafficFromVmBuilder = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(floatingIp.toIpPrefix())
+                    .matchInPort(nodeService.tunnelPort(deviceId).get());
+
+            RulePopulatorUtil.setRule(
+                    flowObjectiveService,
+                    appId,
+                    deviceId,
+                    sOutgoingBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
+                    ForwardingObjective.Flag.VERSATILE,
+                    FLOATING_RULE_PRIORITY, false);
+
+            RulePopulatorUtil.setRule(
+                    flowObjectiveService,
+                    appId,
+                    deviceId,
+                    sIncomingBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
+                    ForwardingObjective.Flag.VERSATILE,
+                    FLOATING_RULE_PRIORITY, false);
+
+            RulePopulatorUtil.setRule(
+                    flowObjectiveService,
+                    appId,
+                    deviceId,
+                    sForTrafficFromVmBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
+                    ForwardingObjective.Flag.VERSATILE,
+                    FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY, false);
+        });
+    }
+
+    private void populateFloatingIpIncomingRules(IpAddress floatingIp, Host associatedVm) {
+        DeviceId cnodeId = associatedVm.location().deviceId();
+        Optional<IpAddress> dataIp = nodeService.dataIp(cnodeId);
+        Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+
+        if (!fixedIp.isPresent() || !dataIp.isPresent()) {
+            log.warn("Failed to associate floating IP({})", floatingIp);
+            return;
+        }
+
+        TrafficSelector selectorForTrafficFromExternal = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floatingIp.toIpPrefix())
+                .build();
+
+        gatewayService.getGatewayDeviceIds().forEach(gnodeId -> {
+            TrafficTreatment treatmentForTrafficFromExternal = DefaultTrafficTreatment.builder()
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(associatedVm.mac())
+                    .setIpDst(associatedVm.ipAddresses().stream().findFirst().get())
+                    .setTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                    .extension(buildExtension(deviceService, gnodeId, dataIp.get().getIp4Address()),
+                               gnodeId)
+                    .setOutput(nodeService.tunnelPort(gnodeId).get())
+                    .build();
+
+            ForwardingObjective forwardingObjectiveForTrafficFromExternal = DefaultForwardingObjective.builder()
+                    .withSelector(selectorForTrafficFromExternal)
+                    .withTreatment(treatmentForTrafficFromExternal)
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(FLOATING_RULE_PRIORITY)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gnodeId, forwardingObjectiveForTrafficFromExternal);
+
+
+            TrafficSelector selectorForTrafficFromVm = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(floatingIp.toIpPrefix())
+                    .matchInPort(nodeService.tunnelPort(gnodeId).get())
+                    .build();
+
+            TrafficTreatment treatmentForTrafficFromVm = DefaultTrafficTreatment.builder()
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(associatedVm.mac())
+                    .setIpDst(associatedVm.ipAddresses().stream().findFirst().get())
+                    .setTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                    .extension(buildExtension(deviceService, gnodeId, dataIp.get().getIp4Address()),
+                            gnodeId)
+                    .setOutput(PortNumber.IN_PORT)
+                    .build();
+
+            ForwardingObjective forwardingObjectiveForTrafficFromVm = DefaultForwardingObjective.builder()
+                    .withSelector(selectorForTrafficFromVm)
+                    .withTreatment(treatmentForTrafficFromVm)
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gnodeId, forwardingObjectiveForTrafficFromVm);
+
+        });
+    }
+
+    private void populateFloatingIpOutgoingRules(IpAddress floatingIp, Host associatedVm) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                .matchIPSrc(associatedVm.ipAddresses().stream().findFirst().get().toIpPrefix())
+                .build();
+
+        gatewayService.getGatewayDeviceIds().forEach(gnodeId -> {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setIpSrc(floatingIp)
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(Constants.DEFAULT_EXTERNAL_ROUTER_MAC)
+                    .setOutput(gatewayService.getUplinkPort(gnodeId))
+                    .build();
+
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(FLOATING_RULE_PRIORITY)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gnodeId, fo);
+        });
+    }
+
+    // TODO consider the case that port with associated floating IP is attached to a VM
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+
+            switch (event.type()) {
+                case COMPLETE:
+                    reinstallVmFlow(null);
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                case INCOMPLETE:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackIcmpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackIcmpHandler.java
new file mode 100644
index 0000000..d58062a
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackIcmpHandler.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+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.Ip4Address;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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.host.HostService;
+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.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Handle ICMP packet sent from OpenStack Gateway nodes.
+ * For a request to any private network gateway IPs, it generates fake reply.
+ * For a request to the external network, it does source NAT with a public IP and
+ * forward the request to the external only if the request instance has external
+ * connection setups.
+ */
+@Component(immediate = true)
+public class OpenstackIcmpHandler {
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
+    private final Map<String, Host> icmpInfoMap = Maps.newHashMap();
+
+    ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        nodeService.addListener(nodeListener);
+        requestPacket(appId);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
+    }
+
+    private void requestPacket(ApplicationId appId) {
+        TrafficSelector icmpSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .build();
+
+        gatewayService.getGatewayDeviceIds().forEach(gateway -> {
+            packetService.requestPackets(icmpSelector,
+                                         PacketPriority.CONTROL,
+                                         appId,
+                                         Optional.of(gateway));
+            log.debug("Requested ICMP packet on {}", gateway);
+        });
+    }
+
+    private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
+        IPv4 ipPacket = (IPv4) ethernet.getPayload();
+        log.trace("Processing ICMP packet from ip {}, mac {}",
+                  Ip4Address.valueOf(ipPacket.getSourceAddress()),
+                  ethernet.getSourceMAC());
+
+        ICMP icmp = (ICMP) ipPacket.getPayload();
+        short icmpId = getIcmpId(icmp);
+
+        DeviceId srcDevice = context.inPacket().receivedFrom().deviceId();
+        switch (icmp.getIcmpType()) {
+            case ICMP.TYPE_ECHO_REQUEST:
+                Optional<Host> reqHost = hostService.getHostsByMac(ethernet.getSourceMAC())
+                        .stream().findFirst();
+                if (!reqHost.isPresent()) {
+                    log.warn("No host found for MAC {}", ethernet.getSourceMAC());
+                    return;
+                }
+
+                // TODO Considers icmp between internal subnets belong to the same router.
+                // TODO do we have to support ICMP reply for non-existing gateway?
+                Ip4Address gatewayIp = Ip4Address.valueOf(
+                        reqHost.get().annotations().value(Constants.GATEWAY_IP));
+                if (Objects.equals(ipPacket.getDestinationAddress(), gatewayIp.toInt())) {
+                    processRequestToGateway(ipPacket, reqHost.get());
+                } else {
+                    Optional<Ip4Address> srcNatIp = getSrcNatIp(reqHost.get());
+                    if (!srcNatIp.isPresent()) {
+                        log.trace("VM {} has no external connection", reqHost.get());
+                        return;
+                    }
+
+                    sendRequestToExternal(ipPacket, srcDevice, srcNatIp.get());
+                    String icmpInfoKey = String.valueOf(icmpId)
+                            .concat(String.valueOf(srcNatIp.get().toInt()))
+                            .concat(String.valueOf(ipPacket.getDestinationAddress()));
+                    icmpInfoMap.putIfAbsent(icmpInfoKey, reqHost.get());
+                }
+                break;
+            case ICMP.TYPE_ECHO_REPLY:
+                String icmpInfoKey = String.valueOf(icmpId)
+                        .concat(String.valueOf(ipPacket.getDestinationAddress()))
+                        .concat(String.valueOf(ipPacket.getSourceAddress()));
+
+                processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey));
+                icmpInfoMap.remove(icmpInfoKey);
+                break;
+            default:
+                break;
+        }
+    }
+
+    // TODO do we have to handle the request to the fake gateway?
+    private void processRequestToGateway(IPv4 ipPacket, Host reqHost) {
+        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(reqHost.mac())
+                .setPayload(ipPacket);
+
+        sendReply(icmpReply, reqHost);
+    }
+
+    private void sendRequestToExternal(IPv4 ipPacket, DeviceId srcDevice, Ip4Address srcNatIp) {
+        ICMP icmpReq = (ICMP) ipPacket.getPayload();
+        icmpReq.resetChecksum();
+        ipPacket.setSourceAddress(srcNatIp.toInt()).resetChecksum();
+        ipPacket.setPayload(icmpReq);
+
+        Ethernet icmpRequestEth = new Ethernet();
+        icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
+                .setPayload(ipPacket);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(gatewayService.getUplinkPort(srcDevice))
+                .build();
+
+        OutboundPacket packet = new DefaultOutboundPacket(
+                srcDevice,
+                treatment,
+                ByteBuffer.wrap(icmpRequestEth.serialize()));
+
+        packetService.emit(packet);
+    }
+
+    private void processReplyFromExternal(IPv4 ipPacket, Host dstHost) {
+        ICMP icmpReply = (ICMP) ipPacket.getPayload();
+        icmpReply.resetChecksum();
+
+        Ip4Address ipAddress = dstHost.ipAddresses().stream().findFirst().get().getIp4Address();
+        ipPacket.setDestinationAddress(ipAddress.toInt())
+                .resetChecksum();
+        ipPacket.setPayload(icmpReply);
+
+        Ethernet icmpResponseEth = new Ethernet();
+        icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(dstHost.mac())
+                .setPayload(ipPacket);
+
+        sendReply(icmpResponseEth, dstHost);
+    }
+
+    private void sendReply(Ethernet icmpReply, Host dstHost) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dstHost.location().port())
+                .build();
+
+        OutboundPacket packet = new DefaultOutboundPacket(
+                dstHost.location().deviceId(),
+                treatment,
+                ByteBuffer.wrap(icmpReply.serialize()));
+
+        packetService.emit(packet);
+    }
+
+    private Optional<Ip4Address> getSrcNatIp(Host host) {
+        // TODO cache external gateway IP for each network because
+        // asking Neutron for every ICMP request is a bad idea
+        Optional<OpenstackPort> osPort = openstackService.ports().stream()
+                .filter(port -> port.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                        Objects.equals(host.annotations().value(NETWORK_ID),
+                                       port.networkId()))
+                .findAny();
+        if (!osPort.isPresent()) {
+            return Optional.empty();
+        }
+
+        OpenstackRouter osRouter = openstackService.router(osPort.get().deviceId());
+        if (osRouter == null) {
+            return Optional.empty();
+        }
+
+        return osRouter.gatewayExternalInfo().externalFixedIps()
+                .values().stream().findAny();
+    }
+
+    private short getIcmpId(ICMP icmp) {
+        return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().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));
+            }
+        }
+    }
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+
+            switch (event.type()) {
+                case COMPLETE:
+                    if (node.type() == GATEWAY) {
+                        log.info("GATEWAY node {} detected", node.hostname());
+                        eventExecutor.execute(() -> {
+                            GatewayNode gnode = GatewayNode.builder()
+                                    .gatewayDeviceId(node.intBridge())
+                                    .dataIpAddress(node.dataIp().getIp4Address())
+                                    .uplinkIntf(node.externalPortName().get())
+                                    .build();
+                            gatewayService.addGatewayNode(gnode);
+                            requestPacket(appId);
+                        });
+                    }
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                case INCOMPLETE:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackPnatHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackPnatHandler.java
new file mode 100644
index 0000000..5a5eafc
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackPnatHandler.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.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.Ip4Address;
+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.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.host.HostService;
+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.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+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.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle NAT packet processing for managing flow rules in openstack nodes.
+ */
+@Component(immediate = true)
+public class OpenstackPnatHandler {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API);
+
+    private static final int PNAT_PORT_EXPIRE_TIME = 1200 * 1000;
+    private static final int TP_PORT_MINIMUM_NUM = 1024;
+    private static final int TP_PORT_MAXIMUM_NUM = 65535;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    private ConsistentMap<Integer, String> tpPortNumMap;
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        tpPortNumMap = storageService.<Integer, String>consistentMapBuilder()
+                .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
+                .withName("openstackrouting-tpportnum")
+                .withApplicationId(appId)
+                .build();
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
+    }
+
+    private void processPnatPacket(PacketContext context, Ethernet ethernet) {
+        IPv4 iPacket = (IPv4) ethernet.getPayload();
+        InboundPacket inboundPacket = context.inPacket();
+
+        int srcPort = getPortNum(ethernet.getSourceMAC(), iPacket.getDestinationAddress());
+        OpenstackPort osPort = getOpenstackPort(ethernet.getSourceMAC());
+        if (osPort == null) {
+            return;
+        }
+        Ip4Address externalGatewayIp = getExternalGatewayIp(osPort);
+        if (externalGatewayIp == null) {
+            return;
+        }
+
+        populatePnatFlowRules(context.inPacket(),
+                osPort,
+                TpPort.tpPort(srcPort),
+                externalGatewayIp);
+
+        packetOut((Ethernet) ethernet.clone(),
+                  inboundPacket.receivedFrom().deviceId(),
+                  srcPort,
+                  externalGatewayIp);
+    }
+
+    private void packetOut(Ethernet ethernet, DeviceId deviceId, int portNum, Ip4Address externalIp) {
+        IPv4 iPacket = (IPv4) ethernet.getPayload();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                tcpPacket.setSourcePort(portNum);
+                tcpPacket.resetChecksum();
+                tcpPacket.setParent(iPacket);
+                iPacket.setPayload(tcpPacket);
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                udpPacket.setSourcePort(portNum);
+                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(ethernet);
+        ethernet.setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC);
+        ethernet.setPayload(iPacket);
+
+        treatment.setOutput(gatewayService.getUplinkPort(deviceId));
+        ethernet.resetChecksum();
+        packetService.emit(new DefaultOutboundPacket(
+                deviceId,
+                treatment.build(),
+                ByteBuffer.wrap(ethernet.serialize())));
+    }
+
+    private int getPortNum(MacAddress sourceMac, int destinationAddress) {
+        int portNum = findUnusedPortNum();
+        if (portNum == 0) {
+            clearPortNumMap();
+            portNum = findUnusedPortNum();
+        }
+        tpPortNumMap.put(portNum, sourceMac.toString().concat(":").concat(String.valueOf(destinationAddress)));
+        return portNum;
+    }
+
+    private int findUnusedPortNum() {
+        for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
+            if (!tpPortNumMap.containsKey(i)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    private void clearPortNumMap() {
+        tpPortNumMap.entrySet().forEach(e -> {
+            if (System.currentTimeMillis() - e.getValue().creationTime() > PNAT_PORT_EXPIRE_TIME) {
+                tpPortNumMap.remove(e.getKey());
+            }
+        });
+    }
+
+    // TODO there can be multiple routers connected to a particular openstack port
+    // TODO cache router information
+    private Ip4Address getExternalGatewayIp(OpenstackPort osPort) {
+        Optional<OpenstackPort> routerPort = openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
+                .filter(p -> checkSameSubnet(p, osPort))
+                .findAny();
+        if (!routerPort.isPresent()) {
+            log.warn("No router is connected to network {}", osPort.networkId());
+            return null;
+        }
+
+        OpenstackRouter osRouter = openstackService.router(routerPort.get().deviceId());
+        if (osRouter == null) {
+            log.warn("Failed to get OpenStack router {}",
+                     routerPort.get().deviceId());
+            return null;
+        }
+
+        return osRouter.gatewayExternalInfo().externalFixedIps().values()
+                .stream().findAny().orElse(null);
+    }
+
+    private OpenstackPort getOpenstackPort(MacAddress srcMac) {
+        Optional<Host> host = hostService.getHostsByMac(srcMac).stream()
+                .filter(h -> h.annotations().value(PORT_ID) != null)
+                .findAny();
+        if (!host.isPresent()) {
+            log.warn("Failed to find a host with MAC:{}", srcMac);
+            return null;
+        }
+        return openstackService.port(host.get().annotations().value(PORT_ID));
+    }
+
+    private boolean checkSameSubnet(OpenstackPort osPortA, OpenstackPort osPortB) {
+        return osPortA.fixedIps().keySet().stream()
+                .anyMatch(subnetId -> osPortB.fixedIps().keySet().contains(subnetId));
+    }
+
+    private void populatePnatFlowRules(InboundPacket inboundPacket,
+                                      OpenstackPort osPort,
+                                      TpPort patPort,
+                                      Ip4Address externalIp) {
+        long vni = getVni(osPort.networkId());
+        populatePnatIncomingFlowRules(vni, externalIp, patPort, inboundPacket);
+        populatePnatOutgoingFlowRules(vni, externalIp, patPort, inboundPacket);
+    }
+
+    private long getVni(String netId) {
+        // TODO remove this and use host vxlan annotation if applicable
+        return Long.parseLong(openstackService.network(netId).segmentId());
+    }
+
+    private void populatePnatOutgoingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
+                                               InboundPacket inboundPacket) {
+        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchTunnelId(vni)
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
+                .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        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(DEFAULT_EXTERNAL_ROUTER_MAC);
+                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(DEFAULT_EXTERNAL_ROUTER_MAC);
+
+                break;
+            default:
+                log.debug("Unsupported IPv4 protocol {}");
+                break;
+        }
+
+        tBuilder.setIpSrc(externalIp);
+        gatewayService.getGatewayNodes().forEach(gateway -> {
+            TrafficTreatment.Builder tmpBuilder = DefaultTrafficTreatment.builder(tBuilder.build());
+            tmpBuilder.setOutput(gatewayService.getUplinkPort(gateway.getGatewayDeviceId()));
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(sBuilder.build())
+                    .withTreatment(tmpBuilder.build())
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(PNAT_RULE_PRIORITY)
+                    .makeTemporary(PNAT_TIMEOUT)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gateway.getGatewayDeviceId(), fo);
+        });
+    }
+
+    private void populatePnatIncomingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
+                                               InboundPacket inboundPacket) {
+        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
+        IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchIPDst(IpPrefix.valueOf(externalIp, 32))
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.setTunnelId(vni)
+                .setEthDst(inboundPacket.parsed().getSourceMAC())
+                .setIpDst(internalIp);
+
+        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;
+        }
+
+        Optional<Host> srcVm = Tools.stream(hostService.getHostsByIp(internalIp))
+                .filter(host -> Objects.equals(
+                        host.annotations().value(VXLAN_ID),
+                        String.valueOf(vni)))
+                .findFirst();
+        if (!srcVm.isPresent()) {
+            log.warn("Failed to find source VM with IP {}", internalIp);
+            return;
+        }
+
+        gatewayService.getGatewayDeviceIds().forEach(deviceId -> {
+            DeviceId srcDeviceId = srcVm.get().location().deviceId();
+            TrafficTreatment.Builder tmpBuilder = DefaultTrafficTreatment.builder(tBuilder.build());
+            tmpBuilder.extension(RulePopulatorUtil.buildExtension(
+                    deviceService,
+                    deviceId,
+                    nodeService.dataIp(srcDeviceId).get().getIp4Address()), deviceId)
+                    .setOutput(nodeService.tunnelPort(deviceId).get());
+
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(sBuilder.build())
+                    .withTreatment(tmpBuilder.build())
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(PNAT_RULE_PRIORITY)
+                    .makeTemporary(PNAT_TIMEOUT)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(deviceId, fo);
+        });
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().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();
+            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(() -> processPnatPacket(context, ethernet));
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
new file mode 100644
index 0000000..0bf93dc
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.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.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.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+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.DEVICE_OWNER_FLOATING_IP;
+import static org.onosproject.openstacknetworking.api.Constants.DEVICE_OWNER_ROUTER_GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle ARP, ICMP and NAT packets from gateway nodes.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingArpHandler {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    private final ExecutorService executorService =
+            newSingleThreadExecutor(groupedThreads("onos/openstackrouting", "packet-event", log));
+
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    @Activate
+    protected void activate() {
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
+    }
+
+    private void processArpPacket(PacketContext context, Ethernet ethernet) {
+        ARP arp = (ARP) ethernet.getPayload();
+        log.trace("arpEvent called from {} to {}",
+                Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
+                Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
+
+        if (arp.getOpCode() != ARP.OP_REQUEST) {
+            return;
+        }
+
+        IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+        if (getTargetMacForTargetIp(targetIp.getIp4Address()) == MacAddress.NONE) {
+            return;
+        }
+
+        MacAddress targetMac = Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
+        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())));
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().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) {
+                executorService.execute(() -> processArpPacket(context, ethernet));
+            }
+        }
+    }
+
+    // TODO make a cache for the MAC, not a good idea to REST call every time it gets ARP request
+    private MacAddress getTargetMacForTargetIp(Ip4Address targetIp) {
+        OpenstackPort port = openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_GATEWAY) ||
+                        p.deviceOwner().equals(DEVICE_OWNER_FLOATING_IP))
+                .filter(p -> p.fixedIps().containsValue(targetIp.getIp4Address()))
+                .findAny().orElse(null);
+
+        return port == null ? MacAddress.NONE : port.macAddress();
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingManager.java
new file mode 100644
index 0000000..2482333
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingManager.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.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.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstackinterface.OpenstackRouterInterface;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackRoutingService;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+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.*;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
+
+@Component(immediate = true)
+@Service
+public class OpenstackRoutingManager extends AbstractVmHandler implements OpenstackRoutingService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        super.activate();
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        nodeService.addListener(nodeListener);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+        nodeService.removeListener(nodeListener);
+        log.info("stopped");
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        // Installs forwarding flow rules to VMs in different nodes and different subnets
+        // that are connected via a router
+        Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
+
+        if (!routerOfTheHost.isPresent()) {
+            return;
+        }
+
+        routableSubNets(routerOfTheHost.get().id()).stream()
+                .filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                .forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), true));
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        // Removes forwarding flow rules to VMs in different nodes and different subnets
+        // that are connected via a router
+        Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
+        if (!routerOfTheHost.isPresent()) {
+            return;
+        }
+
+        routableSubNets(routerOfTheHost.get().id()).stream()
+                .filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                .forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), false));
+    }
+
+    @Override
+    public void createRouter(OpenstackRouter osRouter) {
+    }
+
+    @Override
+    public void updateRouter(OpenstackRouter osRouter) {
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            routableSubNets(osRouter.id()).stream()
+                    .forEach(subnet -> setExternalConnection(osRouter, subnet, true));
+
+            log.info("Connected external gateway {} to router {}",
+                     osRouter.gatewayExternalInfo().externalFixedIps(),
+                     osRouter.name());
+        } else {
+            routableSubNets(osRouter.id()).stream()
+                    .forEach(subnet -> setExternalConnection(osRouter, subnet, false));
+
+            log.info("Disconnected external gateway from router {}",
+                     osRouter.name());
+        }
+    }
+
+    @Override
+    public void removeRouter(String osRouterId) {
+        // Nothing to do
+        // All interfaces need to be removed before the router is removed,
+        // and all related flow rues are removed when the interfaces are removed.
+    }
+
+    @Override
+    public void addRouterInterface(OpenstackRouterInterface routerIfaceAdded) {
+        OpenstackRouter osRouter = openstackRouter(routerIfaceAdded.id());
+        OpenstackSubnet osSubnetAdded = openstackService.subnet(routerIfaceAdded.subnetId());
+        if (osRouter == null || osSubnetAdded == null) {
+            log.warn("Failed to add router interface {}", routerIfaceAdded);
+            return;
+        }
+        handleRouterInterfaces(osRouter, osSubnetAdded);
+    }
+
+    @Override
+    public void removeRouterInterface(OpenstackRouterInterface routerIface) {
+        OpenstackRouter osRouter = openstackService.router(routerIface.id());
+        OpenstackSubnet osSubnetRemoved = openstackService.subnet(routerIface.subnetId());
+        if (osRouter == null) {
+            log.warn("Failed to remove router interface {}", routerIface);
+            return;
+        }
+        handleRouterInterfacesRemoved(osRouter, osSubnetRemoved);
+
+        log.info("Disconnected {} from router {}", osSubnetRemoved.cidr(), osRouter.name());
+    }
+
+    private void handleRouterInterfaces(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded) {
+        OpenstackNetwork osNetworkAdded = openstackService.network(osSubnetAdded.networkId());
+        if (osNetworkAdded == null) {  // in case of external network subnet
+            return;
+        }
+
+        // Sets flow rules for routing among subnets connected to a router.
+        setRoutesAmongSubnets(osRouter, osSubnetAdded, true);
+
+        // Sets flow rules for forwarding "packets going to external networks" to gateway nodes.
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            setExternalConnection(osRouter, osSubnetAdded, true);
+        }
+
+        // Sets flow rules to handle ping to the virtual gateway.
+        Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetAdded.gatewayIp());
+        gatewayService.getGatewayDeviceIds()
+                .forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, true));
+
+        // Sets east-west routing rules for VMs in different Cnode to Switching Table.
+        setForwardingRulesForEastWestRouting(osRouter, osSubnetAdded, true);
+
+    }
+
+    private void handleRouterInterfacesRemoved(OpenstackRouter osRouter, OpenstackSubnet osSubnetRemoved) {
+
+        // Removes flow rules for routing among subnets connected to a router.
+        setRoutesAmongSubnets(osRouter, osSubnetRemoved, false);
+
+        // Removes flow rules for forwarding "packets going to external networks" to gateway nodes.
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            setExternalConnection(osRouter, osSubnetRemoved, false);
+        }
+
+        // Removes flow rules to handle ping to the virtual gateway.
+        Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetRemoved.gatewayIp());
+        gatewayService.getGatewayDeviceIds()
+                .forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, false));
+
+        // Removes east-west routing rules for VMs in different Cnode to Switching Table.
+        setForwardingRulesForEastWestRouting(osRouter, osSubnetRemoved, false);
+
+        // Resets east-west routing rules for VMs in different Cnode to Switching Table.
+        routableSubNets(osRouter.id()).stream()
+                .forEach(subnet -> setForwardingRulesForEastWestRouting(osRouter, subnet, true));
+    }
+
+    private void setRoutesAmongSubnets(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded, boolean install) {
+        Set<OpenstackSubnet> routableSubNets = routableSubNets(osRouter.id());
+        if (routableSubNets.size() < 2) {
+            // no other subnet interface is connected to this router, do nothing
+            return;
+        }
+
+        Map<String, String> vniMap = new HashMap<>();
+        openstackService.networks().forEach(n -> vniMap.put(n.id(), n.segmentId()));
+
+        routableSubNets.stream()
+                .filter(subnet -> !subnet.id().equals(osSubnetAdded.id()))
+                .filter(subnet -> vniMap.get(subnet.networkId()) != null)
+                .forEach(subnet -> nodeService.completeNodes().stream()
+                        .filter(node -> node.type().equals(COMPUTE))
+                        .forEach(node -> {
+                                setRoutingRules(node.intBridge(),
+                                        Integer.parseInt(vniMap.get(subnet.networkId())),
+                                        Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
+                                        subnet, osSubnetAdded, install);
+                                setRoutingRules(node.intBridge(),
+                                        Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
+                                        Integer.parseInt(vniMap.get(subnet.networkId())),
+                                        osSubnetAdded, subnet, install);
+                                }
+                        ));
+    }
+
+    private void setRoutingRules(DeviceId deviceId, int srcVni, int dstVni,
+                                 OpenstackSubnet subnetSrc, OpenstackSubnet subnetDst, boolean install) {
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(srcVni)
+                .matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
+                .matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
+
+        tBuilder.setTunnelId(dstVni);
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
+
+        // Flow rules for destination is in different subnet and different node,
+        // because VNI is converted to destination VNI in the source VM node.
+        sBuilder = DefaultTrafficSelector.builder();
+        tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(dstVni)
+                .matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
+                .matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
+
+        tBuilder.setTunnelId(dstVni);
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
+    }
+
+    private void setExternalConnection(OpenstackRouter osRouter, OpenstackSubnet osSubNet, boolean install) {
+        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
+            log.debug("Source NAT is disabled");
+            return;
+        }
+
+        //OpenstackSubnet osSubNet = openstackService.subnet(osSubNetId);
+        OpenstackNetwork osNet = openstackService.network(osSubNet.networkId());
+
+        nodeService.completeNodes().stream()
+                .filter(node -> node.type().equals(COMPUTE))
+                .forEach(node -> setRulesToGateway(
+                        node.intBridge(),
+                        gatewayService.getGatewayGroupId(node.intBridge()),
+                        Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install));
+
+        // Is this for PNAT ??
+        setRulesForGatewayToController(Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install);
+    }
+
+    private void setRulesToGateway(DeviceId deviceId, GroupId groupId, long vni, String cidr, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(vni)
+                .matchIPSrc(IpPrefix.valueOf(cidr))
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        tBuilder.group(groupId);
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, ROUTING_RULE_PRIORITY, install);
+    }
+
+    private void setRulesForGatewayToController(long vni, String subNetCidr, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(vni)
+                .matchIPSrc(IpPrefix.valueOf(subNetCidr))
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+        tBuilder.setOutput(PortNumber.CONTROLLER);
+
+        gatewayService.getGatewayDeviceIds().forEach(deviceId ->
+                RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                        tBuilder.build(), ForwardingObjective.Flag.VERSATILE, ROUTING_RULE_PRIORITY, install));
+    }
+
+    private void setGatewayIcmpRule(Ip4Address gatewayIp, DeviceId deviceId, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIPDst(gatewayIp.toIpPrefix());
+
+        tBuilder.setOutput(PortNumber.CONTROLLER);
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.VERSATILE, GATEWAY_ICMP_PRIORITY, install);
+    }
+
+    private void setForwardingRulesForEastWestRouting(OpenstackRouter router, OpenstackSubnet subnetAdded,
+                                                      boolean install) {
+
+        Set<OpenstackSubnet> subnets = routableSubNets(router.id());
+
+        Set<Host> hosts = Tools.stream(hostService.getHosts())
+                .filter(h -> getVni(h).equals(openstackService.network(subnetAdded.networkId()).segmentId()))
+                .collect(Collectors.toSet());
+
+        subnets.stream()
+                .filter(subnet -> !subnet.id().equals(subnetAdded.id()))
+                .forEach(subnet -> getHosts(subnet)
+                        .forEach(h -> setForwardingRulesAmongHostsInDifferentCnodes(h, hosts, install)));
+    }
+
+    private void setForwardingRulesAmongHostsInDifferentCnodes(Host host, Set<Host> remoteHosts, boolean install) {
+        Ip4Address localVmIp = getIp(host);
+        DeviceId localDeviceId = host.location().deviceId();
+        Optional<IpAddress> localDataIp = nodeService.dataIp(localDeviceId);
+
+        if (!localDataIp.isPresent()) {
+            log.debug("Failed to get data IP for device {}",
+                    host.location().deviceId());
+            return;
+        }
+
+        remoteHosts.stream()
+                .filter(remoteHost -> !host.location().deviceId().equals(remoteHost.location().deviceId()))
+                .forEach(remoteVm -> {
+                    Optional<IpAddress> remoteDataIp = nodeService.dataIp(remoteVm.location().deviceId());
+                    if (remoteDataIp.isPresent()) {
+                        setVxLanFlowRule(getVni(remoteVm),
+                                localDeviceId,
+                                remoteDataIp.get().getIp4Address(),
+                                getIp(remoteVm), install);
+
+                        setVxLanFlowRule(getVni(host),
+                                remoteVm.location().deviceId(),
+                                localDataIp.get().getIp4Address(),
+                                localVmIp, install);
+                    }
+                });
+    }
+
+    private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address remoteIp,
+                                  Ip4Address vmIp, boolean install) {
+        Optional<PortNumber> tunnelPort = nodeService.tunnelPort(deviceId);
+        if (!tunnelPort.isPresent()) {
+            log.warn("Failed to get tunnel port from {}", deviceId);
+            return;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.parseLong(vni))
+                .matchIPDst(vmIp.toIpPrefix());
+        tBuilder.extension(buildExtension(deviceService, deviceId, remoteIp), deviceId)
+                .setOutput(tunnelPort.get());
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, SWITCHING_RULE_PRIORITY, install);
+    }
+
+
+    private OpenstackRouter openstackRouter(String routerId) {
+        return openstackService.routers().stream().filter(r ->
+                r.id().equals(routerId)).iterator().next();
+    }
+
+    @Override
+    public void reinstallVmFlow(Host host) {
+        // TODO: implements later
+    }
+
+    @Override
+    public void purgeVmFlow(Host host) {
+        // TODO: implements later
+    }
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+
+            switch (event.type()) {
+                case COMPLETE:
+                case INCOMPLETE:
+                    log.info("COMPLETE node {} detected", node.hostname());
+                    eventExecutor.execute(() -> {
+                        if (node.type() == GATEWAY) {
+                            GatewayNode gnode = GatewayNode.builder()
+                                    .gatewayDeviceId(node.intBridge())
+                                    .dataIpAddress(node.dataIp().getIp4Address())
+                                    .uplinkIntf(node.externalPortName().get())
+                                    .build();
+                            gatewayService.addGatewayNode(gnode);
+                        }
+                    });
+                    openstackService.routers().stream()
+                            .forEach(router -> routableSubNets(router.id()).stream()
+                                        .forEach(subnet -> handleRouterInterfaces(router, subnet)));
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
new file mode 100644
index 0000000..3f6f480
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
@@ -0,0 +1,425 @@
+/*
+* Copyright 2016-present Open Networking Laboratory
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.onosproject.openstacknetworking.impl;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSecurityGroup;
+import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.api.Constants.*;
+
+/**
+ * Populates flows rules for Security Groups of VMs.
+ *
+ */
+@Component(immediate = true)
+@Service
+public class OpenstackSecurityGroupManager extends AbstractVmHandler
+        implements OpenstackSecurityGroupService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    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 final Map<Host, Set<SecurityGroupRule>> securityGroupRuleMap = Maps.newConcurrentMap();
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        super.activate();
+        appId = coreService.registerApplication(SWITCHING_APP_ID);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    public void updateSecurityGroup(OpenstackPort osPort) {
+        if (!osPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
+            return;
+        }
+
+        Optional<Host> host = getVmByPortId(osPort.id());
+        if (!host.isPresent()) {
+            log.debug("No host found with {}", osPort.id());
+            return;
+        }
+        eventExecutor.execute(() -> updateSecurityGroupRules(host.get(), true));
+    }
+
+    /**
+     * Populates security group rules for all VMs in the supplied tenant ID.
+     * VMs in the same tenant tend to be engaged to each other by sharing the
+     * same security groups or setting the remote to another security group.
+     * To make the implementation simpler and robust, it tries to reinstall
+     * security group rules for all the VMs in the same tenant whenever a new
+     * VM is detected or port is updated.
+     *
+     * @param tenantId tenant id to update security group rules
+     */
+    private void populateSecurityGroupRules(String tenantId, boolean install) {
+        securityGroupRuleMap.entrySet().stream()
+                .filter(entry -> getTenantId(entry.getKey()).equals(tenantId))
+                .forEach(entry -> {
+                    Host local = entry.getKey();
+                    entry.getValue().forEach(sgRule -> {
+                        setSecurityGroupRule(local.location().deviceId(),
+                                sgRule.rule(),
+                                getIp(local),
+                                sgRule.remoteIp(), install);
+                    });
+                });
+        log.debug("Updated security group rules for {}", tenantId);
+    }
+
+    private void setSecurityGroupRule(DeviceId deviceId, OpenstackSecurityGroupRule sgRule,
+                                      Ip4Address vmIp, IpPrefix remoteIp,
+                                      boolean install) {
+        ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule, vmIp, remoteIp);
+        if (foBuilder == null) {
+            return;
+        }
+
+        if (install) {
+            flowObjectiveService.forward(deviceId, foBuilder.add());
+        } else {
+            flowObjectiveService.forward(deviceId, foBuilder.remove());
+        }
+    }
+
+    private ForwardingObjective.Builder buildFlowObjective(OpenstackSecurityGroupRule 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;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+        return DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(DefaultTrafficTreatment.builder().build())
+                .withPriority(ACL_RULE_PRIORITY)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .fromApp(appId);
+    }
+
+    private void buildMatchs(TrafficSelector.Builder sBuilder, OpenstackSecurityGroupRule sgRule,
+                             Ip4Address vmIp, IpPrefix remoteIp) {
+        buildMatchEthType(sBuilder, sgRule.ethertype());
+        buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
+        buildMatchProto(sBuilder, sgRule.protocol());
+        buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
+                sgRule.portRangeMax(), sgRule.portRangeMin());
+        buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
+    }
+
+    private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+                                     OpenstackSecurityGroupRule.Direction direction,
+                                     Ip4Address vmIp) {
+        if (direction.equals(OpenstackSecurityGroupRule.Direction.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,
+                                    OpenstackSecurityGroupRule.Direction direction) {
+        if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+            if (direction.equals(OpenstackSecurityGroupRule.Direction.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,
+                                OpenstackSecurityGroupRule.Direction direction,
+                                int portMin, int portMax) {
+        if (portMin > 0 && portMax > 0 && portMin == portMax) {
+            if (protocol.toUpperCase().equals(PROTO_TCP)) {
+                if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+                    sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+                }
+            } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
+                if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+                    sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+                }
+            }
+        }
+    }
+
+    private void updateSecurityGroupRulesMap(Host host) {
+        OpenstackPort osPort = openstackService.port(host.annotations().value(PORT_ID));
+        if (osPort == null) {
+            log.debug("Failed to get OpenStack port information for {}", host);
+            return;
+        }
+
+        Set<SecurityGroupRule> rules = Sets.newHashSet();
+        osPort.securityGroups().forEach(sgId -> {
+            OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId);
+            if (osSecGroup != null) {
+                osSecGroup.rules().forEach(rule -> rules.addAll(getSgRules(rule)));
+            } else {
+                // TODO handle the case that the security group removed
+                log.warn("Failed to get security group {}", sgId);
+            }
+        });
+        securityGroupRuleMap.put(host, rules);
+    }
+
+    /**
+     * Returns set of security group rules with individual remote IP by
+     * converting remote group to actual IP address.
+     *
+     * @param sgRule security group rule
+     * @return set of security group rules
+     */
+    private Set<SecurityGroupRule> getSgRules(OpenstackSecurityGroupRule sgRule) {
+        Set<SecurityGroupRule> sgRules = Sets.newHashSet();
+        if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
+            sgRules = getRemoteIps(sgRule.tenantId(), sgRule.remoteGroupId())
+                    .stream()
+                    .map(remoteIp -> new SecurityGroupRule(sgRule, remoteIp))
+                    .collect(Collectors.toSet());
+        } else {
+            sgRules.add(new SecurityGroupRule(sgRule, sgRule.remoteIpPrefix()));
+        }
+        return sgRules;
+    }
+
+    /**
+     * 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 in ip prefix format
+     */
+    private Set<IpPrefix> getRemoteIps(String tenantId, String sgId) {
+        Set<IpPrefix> remoteIps = Sets.newHashSet();
+        securityGroupRuleMap.entrySet().stream()
+                .filter(entry -> Objects.equals(getTenantId(entry.getKey()), tenantId))
+                .forEach(entry -> {
+                    if (entry.getValue().stream()
+                            .anyMatch(rule -> rule.rule().secuityGroupId().equals(sgId))) {
+                        remoteIps.add(IpPrefix.valueOf(getIp(entry.getKey()), 32));
+                    }
+                });
+        return remoteIps;
+    }
+
+    private void updateSecurityGroupRules(Host host, boolean isHostAdded) {
+        String tenantId = getTenantId(host);
+        populateSecurityGroupRules(tenantId, false);
+
+        if (isHostAdded) {
+            updateSecurityGroupRulesMap(host);
+        } else {
+            securityGroupRuleMap.remove(host);
+        }
+
+        Tools.stream(hostService.getHosts())
+                .filter(h -> Objects.equals(getTenantId(h), getTenantId(host)))
+                .forEach(this::updateSecurityGroupRulesMap);
+
+        populateSecurityGroupRules(tenantId, true);
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        updateSecurityGroupRules(host, true);
+        log.info("Applied security group rules for {}", host);
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        updateSecurityGroupRules(host, false);
+        log.info("Applied security group rules for {}", host);
+    }
+
+    @Override
+    public void reinstallVmFlow(Host host) {
+        if (host == null) {
+            hostService.getHosts().forEach(h -> {
+                updateSecurityGroupRules(h, true);
+                log.info("Re-Install data plane flow of virtual machine {}", h);
+            });
+        } else {
+            securityGroupRuleMap.entrySet().stream()
+                .filter(entry -> entry.getKey().id().equals(host.id()))
+                .forEach(entry -> {
+                    Host local = entry.getKey();
+                    entry.getValue().forEach(sgRule -> {
+                        setSecurityGroupRule(local.location().deviceId(),
+                                sgRule.rule(),
+                                getIp(local),
+                                sgRule.remoteIp(), true);
+                    });
+                });
+            log.info("Re-Install data plane flow of virtual machine {}", host);
+        }
+    }
+
+    @Override
+    public void purgeVmFlow(Host host) {
+        if (host == null) {
+            securityGroupRuleMap.entrySet().stream()
+                .forEach(entry -> {
+                    Host local = entry.getKey();
+                    entry.getValue().forEach(sgRule -> {
+                        setSecurityGroupRule(local.location().deviceId(),
+                                sgRule.rule(),
+                                getIp(local),
+                                sgRule.remoteIp(), false);
+                    });
+                    log.info("Purge data plane flow of virtual machine {}", local);
+                });
+        } else {
+            securityGroupRuleMap.entrySet().stream()
+                .filter(entry -> entry.getKey().id().equals(host.id()))
+                .forEach(entry -> {
+                    Host local = entry.getKey();
+                    entry.getValue().forEach(sgRule -> {
+                        setSecurityGroupRule(local.location().deviceId(),
+                                sgRule.rule(),
+                                getIp(local),
+                                sgRule.remoteIp(), false);
+                    });
+                });
+            log.info("Purge data plane flow of virtual machine {}", host);
+        }
+    }
+
+    private final class SecurityGroupRule {
+        private final OpenstackSecurityGroupRule rule;
+        private final IpPrefix remoteIp;
+
+        private SecurityGroupRule(OpenstackSecurityGroupRule rule, IpPrefix remoteIp) {
+            this.rule = rule;
+            this.remoteIp = remoteIp;
+        }
+
+        private OpenstackSecurityGroupRule rule() {
+            return rule;
+        }
+
+        private IpPrefix remoteIp() {
+            return remoteIp;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (obj instanceof SecurityGroupRule) {
+                SecurityGroupRule that = (SecurityGroupRule) obj;
+                if (Objects.equals(rule, that.rule) &&
+                        Objects.equals(remoteIp, that.remoteIp)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(rule, remoteIp);
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
new file mode 100644
index 0000000..b4ae36c
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
@@ -0,0 +1,219 @@
+/*
+* Copyright 2016-present Open Networking Laboratory
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.onosproject.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.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+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.*;
+
+/**
+ * Handles ARP packet from VMs.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingArpHandler extends AbstractVmHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String GATEWAY_MAC = "gatewayMac";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @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 Set<Ip4Address> gateways = Sets.newConcurrentHashSet();
+
+    @Activate
+    protected void activate() {
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        super.deactivate();
+    }
+
+    @Modified
+    protected 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");
+    }
+
+    /**
+     * 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;
+        }
+
+        Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+        MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
+                getMacFromHostService(targetIp);
+        if (replyMac.equals(MacAddress.NONE)) {
+            replyMac = getMacFromOpenstack(targetIp);
+        }
+
+        if (replyMac == MacAddress.NONE) {
+            log.debug("Failed to find MAC address for {}", targetIp.toString());
+            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
+     * OpenStack. It does not support overlapping IP.
+     *
+     * @param targetIp target ip address
+     * @return mac address, or null if it fails to fetch the mac
+     */
+    private MacAddress getMacFromOpenstack(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        OpenstackPort openstackPort = openstackService.ports()
+                .stream()
+                .filter(port -> port.fixedIps().containsValue(targetIp.getIp4Address()))
+                .findFirst()
+                .orElse(null);
+
+        if (openstackPort != null) {
+            log.debug("Found MAC from OpenStack for {}", targetIp.toString());
+            return openstackPort.macAddress();
+        } else {
+            return MacAddress.NONE;
+        }
+    }
+
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * host service. It does not support overlapping IP.
+     *
+     * @param targetIp target ip
+     * @return mac address, or null if it fails to find the mac
+     */
+    private MacAddress getMacFromHostService(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        Host host = hostService.getHostsByIp(targetIp)
+                .stream()
+                .findFirst()
+                .orElse(null);
+
+        if (host != null) {
+            log.debug("Found MAC from host service for {}", targetIp.toString());
+            return host.mac();
+        } else {
+            return MacAddress.NONE;
+        }
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        OpenstackNetwork osNet = openstackService.network(host.annotations().value(NETWORK_ID));
+        if (osNet == null) {
+            log.warn("Failed to get OpenStack network for {}", host);
+            return;
+        }
+        osNet.subnets().forEach(subnet -> gateways.add(Ip4Address.valueOf(subnet.gatewayIp())));
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        // TODO remove subnet gateway from gateways if no hosts exists on that subnet
+    }
+
+    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);
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostManager.java
new file mode 100644
index 0000000..99dd927
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostManager.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcp.DhcpService;
+import org.onosproject.dhcp.IpAssignment;
+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.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
+
+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.onosproject.openstacknetworking.api.Constants.*;
+
+@Service
+@Component(immediate = true)
+public final class OpenstackSwitchingHostManager extends AbstractProvider
+        implements HostProvider {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostProviderRegistry hostProviderRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DhcpService dhcpService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService openstackNodeService;
+
+    private final ExecutorService deviceEventExecutor =
+            Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "device-event"));
+    private final ExecutorService configEventExecutor =
+            Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "config-event"));
+    private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
+    private final InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+
+    private HostProviderService hostProvider;
+
+    /**
+     * Creates OpenStack switching host provider.
+     */
+    public OpenstackSwitchingHostManager() {
+        super(new ProviderId("host", SWITCHING_APP_ID));
+    }
+
+    @Activate
+    protected void activate() {
+        coreService.registerApplication(SWITCHING_APP_ID);
+        deviceService.addListener(internalDeviceListener);
+        openstackNodeService.addListener(internalNodeListener);
+        hostProvider = hostProviderRegistry.register(this);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        hostProviderRegistry.unregister(this);
+        deviceService.removeListener(internalDeviceListener);
+        openstackNodeService.removeListener(internalNodeListener);
+
+        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
+        OpenstackPort osPort = openstackService.port(port);
+        if (osPort == null) {
+            log.warn("Failed to get OpenStack port for {}", port);
+            return;
+        }
+
+        OpenstackNetwork osNet = openstackService.network(osPort.networkId());
+        if (osNet == null) {
+            log.warn("Failed to get OpenStack network {}",
+                    osPort.networkId());
+            return;
+        }
+
+        Map.Entry<String, Ip4Address> fixedIp = osPort.fixedIps().entrySet().stream().findFirst().get();
+
+        OpenstackSubnet openstackSubnet = openstackService.subnets().stream()
+                .filter(n -> n.networkId().equals(osPort.networkId()) &&
+                n.id().equals(fixedIp.getKey()))
+                .findFirst().orElse(null);
+        if (openstackSubnet == null) {
+            log.warn("Failed to find subnet for {}", osPort);
+            return;
+        }
+
+        registerDhcpInfo(osPort, openstackSubnet);
+        ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(NETWORK_ID, osPort.networkId())
+                .set(SUBNET_ID, fixedIp.getKey())
+                .set(PORT_ID, osPort.id())
+                .set(VXLAN_ID, osNet.segmentId())
+                .set(TENANT_ID, osNet.tenantId())
+                // TODO remove gateway IP from host annotation
+                .set(GATEWAY_IP, openstackSubnet.gatewayIp());
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                osPort.macAddress(),
+                VlanId.NONE,
+                new HostLocation(connectPoint, System.currentTimeMillis()),
+                Sets.newHashSet(osPort.fixedIps().values()),
+                annotations.build());
+
+        HostId hostId = HostId.hostId(osPort.macAddress());
+        hostProvider.hostDetected(hostId, hostDesc, false);
+    }
+
+    private void processPortRemoved(Port port) {
+        ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+        removeHosts(connectPoint);
+    }
+
+    private void removeHosts(ConnectPoint connectPoint) {
+        hostService.getConnectedHosts(connectPoint).stream()
+                .forEach(host -> {
+                    dhcpService.removeStaticMapping(host.mac());
+                    hostProvider.hostVanished(host.id());
+                });
+    }
+
+    private void registerDhcpInfo(OpenstackPort openstackPort, OpenstackSubnet openstackSubnet) {
+        checkNotNull(openstackPort);
+        checkNotNull(openstackSubnet);
+        checkArgument(!openstackPort.fixedIps().isEmpty());
+
+        Ip4Address ipAddress = openstackPort.fixedIps().values().stream().findFirst().get();
+        IpPrefix subnetPrefix = IpPrefix.valueOf(openstackSubnet.cidr());
+        Ip4Address broadcast = Ip4Address.makeMaskedAddress(
+                ipAddress,
+                subnetPrefix.prefixLength());
+
+        // TODO: supports multiple DNS servers
+        Ip4Address domainServer = openstackSubnet.dnsNameservers().isEmpty() ?
+                DNS_SERVER_IP : openstackSubnet.dnsNameservers().get(0);
+
+        IpAssignment ipAssignment = IpAssignment.builder()
+                .ipAddress(ipAddress)
+                .leasePeriod(DHCP_INFINITE_LEASE)
+                .timestamp(new Date())
+                .subnetMask(Ip4Address.makeMaskPrefix(subnetPrefix.prefixLength()))
+                .broadcast(broadcast)
+                .domainServer(domainServer)
+                .assignmentStatus(Option_RangeNotEnforced)
+                .routerAddress(Ip4Address.valueOf(openstackSubnet.gatewayIp()))
+                .build();
+
+        dhcpService.setStaticMapping(openstackPort.macAddress(), ipAssignment);
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            Device device = event.subject();
+            if (!mastershipService.isLocalMaster(device.id())) {
+                // do not allow to proceed without mastership
+                return;
+            }
+
+            Port port = event.port();
+            if (port == null) {
+                return;
+            }
+
+            String portName = port.annotations().value(PORT_NAME);
+            if (Strings.isNullOrEmpty(portName) ||
+                    !portName.startsWith(PORT_NAME_PREFIX_VM)) {
+                // handles VM connected port event only
+                return;
+            }
+
+            switch (event.type()) {
+                case PORT_UPDATED:
+                    if (!event.port().isEnabled()) {
+                        deviceEventExecutor.execute(() -> processPortRemoved(event.port()));
+                    } else if (event.port().isEnabled()) {
+                        deviceEventExecutor.execute(() -> processPortAdded(event.port()));
+                    }
+                    break;
+                case PORT_ADDED:
+                    deviceEventExecutor.execute(() -> processPortAdded(event.port()));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+            // TODO check leadership of the node and make only the leader process
+
+            switch (event.type()) {
+                case COMPLETE:
+                    log.info("COMPLETE node {} detected", node.hostname());
+
+                    // adds existing VMs running on the complete state node
+                    deviceService.getPorts(node.intBridge()).stream()
+                            .filter(port -> port.annotations().value(PORT_NAME)
+                                    .startsWith(PORT_NAME_PREFIX_VM) &&
+                                    port.isEnabled())
+                            .forEach(port -> {
+                                deviceEventExecutor.execute(() -> processPortAdded(port));
+                                log.info("VM is detected on {}", port);
+                            });
+
+                    // removes stale VMs
+                    hostService.getHosts().forEach(host -> {
+                        if (deviceService.getPort(host.location().deviceId(),
+                                host.location().port()) == null) {
+                            deviceEventExecutor.execute(() -> removeHosts(host.location()));
+                            log.info("Removed stale VM {}", host.location());
+                        }
+                    });
+                    break;
+                case INCOMPLETE:
+                    log.warn("{} is changed to INCOMPLETE state", node);
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingManager.java
new file mode 100644
index 0000000..d9efb3f
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingManager.java
@@ -0,0 +1,235 @@
+/*
+* Copyright 2016-present Open Networking Laboratory
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.onosproject.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.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstacknetworking.api.OpenstackSwitchingService;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+
+
+/**
+ * Populates switching flow rules.
+ */
+@Service
+@Component(immediate = true)
+public final class OpenstackSwitchingManager extends AbstractVmHandler
+        implements OpenstackSwitchingService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        super.activate();
+        appId = coreService.registerApplication(SWITCHING_APP_ID);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    private void populateSwitchingRules(Host host) {
+        setFlowRulesForTunnelTag(host, true);
+        setFlowRulesForTrafficToSameCnode(host, true);
+        setFlowRulesForTrafficToDifferentCnode(host, true);
+
+        log.debug("Populated switching rule for {}", host);
+    }
+
+    private void removeSwitchingRules(Host host) {
+        setFlowRulesForTunnelTag(host, false);
+        setFlowRulesForTrafficToSameCnode(host, false);
+        removeFlowRuleForVMsInDiffrentCnode(host);
+
+        log.debug("Removed switching rule for {}", host);
+    }
+
+    private void setFlowRulesForTunnelTag(Host host, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchInPort(host.location().port());
+
+        tBuilder.setTunnelId(Long.valueOf(getVni(host)));
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, host.location().deviceId(),
+                sBuilder.build(), tBuilder.build(), ForwardingObjective.Flag.SPECIFIC,
+                TUNNELTAG_RULE_PRIORITY, install);
+    }
+
+    private void setFlowRulesForTrafficToSameCnode(Host host, boolean install) {
+        //For L2 Switching Case
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(getIp(host).toIpPrefix())
+                .matchTunnelId(Long.valueOf(getVni(host)));
+
+        // Destination setting is required for routing cases.
+        // We do not believe the rule would not degrade the forwarding performance.
+        // But, if it does, we need to move the rule in a separate routing table.
+        tBuilder.setEthDst(host.mac())
+                .setOutput(host.location().port());
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, host.location().deviceId(),
+                sBuilder.build(), tBuilder.build(), ForwardingObjective.Flag.SPECIFIC,
+                SWITCHING_RULE_PRIORITY, install);
+    }
+
+    private void setFlowRulesForTrafficToDifferentCnode(Host host, boolean install) {
+        Ip4Address localVmIp = getIp(host);
+        DeviceId localDeviceId = host.location().deviceId();
+        Optional<IpAddress> localDataIp = nodeService.dataIp(localDeviceId);
+
+        if (!localDataIp.isPresent()) {
+            log.debug("Failed to get data IP for device {}",
+                    host.location().deviceId());
+            return;
+        }
+
+        String vni = getVni(host);
+        getVmsInDifferentCnode(host).forEach(remoteVm -> {
+            Optional<IpAddress> remoteDataIp = nodeService.dataIp(remoteVm.location().deviceId());
+            if (remoteDataIp.isPresent()) {
+                setVxLanFlowRule(vni,
+                        localDeviceId,
+                        remoteDataIp.get().getIp4Address(),
+                        getIp(remoteVm), install);
+
+                setVxLanFlowRule(vni,
+                        remoteVm.location().deviceId(),
+                        localDataIp.get().getIp4Address(),
+                        localVmIp, install);
+            }
+        });
+    }
+
+    private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address remoteIp,
+                                  Ip4Address vmIp, boolean install) {
+        Optional<PortNumber> tunnelPort = nodeService.tunnelPort(deviceId);
+        if (!tunnelPort.isPresent()) {
+            log.warn("Failed to get tunnel port from {}", deviceId);
+            return;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.parseLong(vni))
+                .matchIPDst(vmIp.toIpPrefix());
+        tBuilder.extension(buildExtension(deviceService, deviceId, remoteIp), deviceId)
+                .setOutput(tunnelPort.get());
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId,
+                sBuilder.build(), tBuilder.build(), ForwardingObjective.Flag.SPECIFIC,
+                SWITCHING_RULE_PRIORITY, install);
+    }
+
+    private void removeFlowRuleForVMsInDiffrentCnode(Host host) {
+        DeviceId deviceId = host.location().deviceId();
+        final boolean anyPortRemainedInSameCnode = hostService.getConnectedHosts(deviceId)
+                .stream()
+                .filter(this::isValidHost)
+                .anyMatch(h -> Objects.equals(getVni(h), getVni(host)));
+
+        getVmsInDifferentCnode(host).forEach(h -> {
+            setVxLanFlowRule(getVni(host), h.location().deviceId(), Ip4Address.valueOf(0), getIp(host),  false);
+            if (!anyPortRemainedInSameCnode) {
+                setVxLanFlowRule(getVni(host), deviceId, Ip4Address.valueOf(0), getIp(h), false);
+            }
+        });
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        populateSwitchingRules(host);
+        log.info("Added new virtual machine to switching service {}", host);
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        removeSwitchingRules(host);
+        log.info("Removed virtual machine from switching service {}", host);
+    }
+
+    @Override
+    public void reinstallVmFlow(Host host) {
+        if (host == null) {
+            hostService.getHosts().forEach(h -> {
+                populateSwitchingRules(h);
+                log.info("Re-Install data plane flow of virtual machine {}", h);
+            });
+        } else {
+            populateSwitchingRules(host);
+            log.info("Re-Install data plane flow of virtual machine {}", host);
+        }
+    }
+
+    @Override
+    public void purgeVmFlow(Host host) {
+        if (host == null) {
+            hostService.getHosts().forEach(h -> {
+                removeSwitchingRules(h);
+                log.info("Purge data plane flow of virtual machine {}", h);
+            });
+        } else {
+            removeSwitchingRules(host);
+            log.info("Purge data plane flow of virtual machine {}", host);
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
new file mode 100644
index 0000000..3a77804
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import org.onlab.packet.Ip4Address;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.slf4j.Logger;
+
+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 {
+
+    protected static final Logger log = getLogger(RulePopulatorUtil.class);
+
+    private static final String TUNNEL_DST = "tunnelDst";
+
+    private RulePopulatorUtil() {
+    }
+
+    /**
+     * 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;
+        }
+
+        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;
+        }
+    }
+
+    /**
+     * Adds flow rules with the supplied information.
+     *
+     * @param flowObjectiveService flow objective service
+     * @param appId application id
+     * @param deviceId device id to remove this flow rule
+     * @param selector traffic selector
+     * @param treatment traffic treatment
+     * @param flag flag
+     * @param priority priority
+     * @param install populate flows if true, remove them otherwise
+     */
+    public static void setRule(FlowObjectiveService flowObjectiveService,
+                               ApplicationId appId,
+                               DeviceId deviceId,
+                               TrafficSelector selector,
+                               TrafficTreatment treatment,
+                               ForwardingObjective.Flag flag,
+                               int priority,
+                               boolean install) {
+        ForwardingObjective.Builder foBuilder = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withFlag(flag)
+                .withPriority(priority)
+                .fromApp(appId);
+
+        if (install) {
+            flowObjectiveService.forward(deviceId, foBuilder.add());
+        } else {
+            flowObjectiveService.forward(deviceId, foBuilder.remove());
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
new file mode 100644
index 0000000..47cd773
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * 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/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
new file mode 100644
index 0000000..c39afd6
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstackinterface.OpenstackRouterInterface;
+import org.onosproject.openstackinterface.web.OpenstackRouterCodec;
+import org.onosproject.openstackinterface.web.OpenstackRouterInterfaceCodec;
+import org.onosproject.openstacknetworking.api.OpenstackRoutingService;
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Handles REST API call of Neturon L3 plugin.
+ */
+
+@Path("routers")
+public class OpensatckRouterWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final OpenstackRouterInterfaceCodec ROUTER_INTERFACE_CODEC
+            = new OpenstackRouterInterfaceCodec();
+    private static final OpenstackRouterCodec ROUTER_CODEC
+            = new OpenstackRouterCodec();
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createRouter(InputStream input) {
+        checkNotNull(input);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode routerNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackRouter openstackRouter
+                    = ROUTER_CODEC.decode(routerNode, this);
+
+            OpenstackRoutingService routingService
+                    = getService(OpenstackRoutingService.class);
+            routingService.createRouter(openstackRouter);
+
+            log.debug("REST API CREATE router is called {}", input.toString());
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("Create Router failed because of exception {}",
+                    e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateRouter(@PathParam("id") String id, InputStream input) {
+        checkNotNull(input);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode routerNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackRouter or = ROUTER_CODEC.decode(routerNode, this);
+
+            OpenstackRouter.Builder osBuilder = new OpenstackRouter.Builder()
+                    .tenantId(or.tenantId())
+                    .id(id)
+                    .name(or.name())
+                    .status(OpenstackRouter.RouterStatus.ACTIVE)
+                    .adminStateUp(Boolean.valueOf(or.adminStateUp()))
+                    .gatewayExternalInfo(or.gatewayExternalInfo());
+
+            OpenstackRoutingService routingService
+                    = getService(OpenstackRoutingService.class);
+            routingService.updateRouter(osBuilder.build());
+
+            log.debug("REST API UPDATE router is called from router {}", input.toString());
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("Updates Router failed because of exception {}",
+                    e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+
+    @PUT
+    @Path("{id}/add_router_interface")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addRouterInterface(@PathParam("id") String id, InputStream input) {
+        checkNotNull(input);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode routerIfNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackRouterInterface openstackRouterInterface
+                    = ROUTER_INTERFACE_CODEC.decode(routerIfNode, this);
+
+            OpenstackRoutingService routingService
+                    = getService(OpenstackRoutingService.class);
+            routingService.addRouterInterface(openstackRouterInterface);
+
+            log.debug("REST API AddRouterInterface is called from router {} portId: {}, subnetId: {}, tenantId: {}",
+                    openstackRouterInterface.id(), openstackRouterInterface.portId(),
+                    openstackRouterInterface.subnetId(), openstackRouterInterface.tenantId());
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("AddRouterInterface failed because of exception {}",
+                    e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteRouter(@PathParam("id") String id) {
+        checkNotNull(id);
+        OpenstackRoutingService routingService
+                = getService(OpenstackRoutingService.class);
+        routingService.removeRouter(id);
+
+        log.debug("REST API DELETE routers is called {}", id);
+        return Response.noContent().build();
+    }
+
+    @PUT
+    @Path("{id}/remove_router_interface")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response removeRouterInterface(@PathParam("id") String id, InputStream input) {
+        checkNotNull(id);
+        checkNotNull(input);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode routerIfNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackRouterInterface openstackRouterInterface
+                    = ROUTER_INTERFACE_CODEC.decode(routerIfNode, this);
+
+            OpenstackRoutingService routingService
+                    = getService(OpenstackRoutingService.class);
+            routingService.removeRouterInterface(openstackRouterInterface);
+
+            log.debug("REST API RemoveRouterInterface is called from router {} portId: {}, subnetId: {}," +
+                    "tenantId: {}", openstackRouterInterface.id(), openstackRouterInterface.portId(),
+                    openstackRouterInterface.subnetId(), openstackRouterInterface.tenantId());
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("RemoveRouterInterface failed because of exception {}",
+                    e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
new file mode 100644
index 0000000..a6880fd
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.openstackinterface.OpenstackFloatingIP;
+import org.onosproject.openstackinterface.web.OpenstackFloatingIpCodec;
+import org.onosproject.openstacknetworking.api.OpenstackFloatingIpService;
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * 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 OpenstackFloatingIpCodec FLOATING_IP_CODEC = new OpenstackFloatingIpCodec();
+
+    /**
+     * Create FloatingIP.
+     *
+     * @param input JSON data describing FloatingIP
+     * @return 200 OK
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createFloatingIp(InputStream input) {
+        checkNotNull(input);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode floatingIpNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackFloatingIP osFloatingIp = FLOATING_IP_CODEC.decode(floatingIpNode, this);
+            OpenstackFloatingIpService floatingIpService =
+                    getService(OpenstackFloatingIpService.class);
+            floatingIpService.createFloatingIp(osFloatingIp);
+
+            log.debug("REST API CREATE floatingip called");
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("createFloatingIp failed with {}", e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+
+    /**
+     * Update FloatingIP.
+     *
+     * @param id    FloatingIP identifier
+     * @param input JSON data describing FloatingIP
+     * @return 200 OK
+     */
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateFloatingIp(@PathParam("id") String id, InputStream input) {
+        checkNotNull(id);
+        checkNotNull(input);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode floatingIpNode = (ObjectNode) mapper.readTree(input);
+
+            OpenstackFloatingIP osFloatingIp = FLOATING_IP_CODEC.decode(floatingIpNode, this);
+            OpenstackFloatingIpService floatingIpService =
+                    getService(OpenstackFloatingIpService.class);
+            floatingIpService.updateFloatingIp(osFloatingIp);
+
+            log.debug("REST API UPDATE floatingip called {}", id);
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            log.error("updateFloatingIp failed with {}", e.toString());
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
+                    .build();
+        }
+    }
+
+    /**
+     * Delete FloatingIP.
+     *
+     * @param id FloatingIP identifier
+     * @return 204 OK
+     */
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteFloatingIp(@PathParam("id") String id) {
+        checkNotNull(id);
+
+        OpenstackFloatingIpService floatingIpService =
+                getService(OpenstackFloatingIpService.class);
+        floatingIpService.deleteFloatingIp(id);
+
+        log.debug("REST API DELETE floatingip is called {}", id);
+        return Response.noContent().build();
+    }
+
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
new file mode 100644
index 0000000..c547b2e
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.rest.AbstractWebResource;
+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.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+/**
+ * Handles REST API call of Neutron ML2 plugin.
+ */
+@Path("networks")
+public class OpenstackNetworkWebResource extends AbstractWebResource {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createNetwork(InputStream input) {
+        log.debug("REST API networks is called {}", input.toString());
+        return Response.status(Response.Status.OK).build();
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateNetwork(InputStream input) {
+        log.debug("REST API networks is called {}", input.toString());
+        return Response.status(Response.Status.OK).build();
+    }
+
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteNetwork(InputStream input) {
+        log.debug("REST API networks is called {}", input.toString());
+        return Response.noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
new file mode 100644
index 0000000..7683b0c
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.web.OpenstackPortCodec;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+@Path("ports")
+public class OpenstackPortWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final OpenstackPortCodec PORT_CODEC
+            = new OpenstackPortCodec();
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createPorts(InputStream input) {
+        return Response.status(Response.Status.OK).build();
+    }
+
+    @Path("{portUUID}")
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deletePorts(@PathParam("portUUID") String id) {
+        return Response.noContent().build();
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updatePorts(@PathParam("id") String id, InputStream input) {
+        checkNotNull(input);
+        checkNotNull(id);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode portNode = (ObjectNode) mapper.readTree(input);
+            OpenstackPort osPort = PORT_CODEC.decode(portNode, this);
+
+            OpenstackSecurityGroupService sgService
+                    = getService(OpenstackSecurityGroupService.class);
+            sgService.updateSecurityGroup(osPort);
+
+            return Response.status(Response.Status.OK).build();
+        } catch (IOException e) {
+            log.error("UpdatePort post process failed due to {}", e.getMessage());
+
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
+                    .build();
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
new file mode 100644
index 0000000..ca9f3ab
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+@Path("subnets")
+public class OpenstackSubnetWebResource extends AbstractWebResource {
+    protected static final Logger log = LoggerFactory
+            .getLogger(OpenstackSubnetWebResource.class);
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSubnet(InputStream input) {
+        return Response.status(Response.Status.OK).build();
+    }
+
+
+    @PUT
+    @Path("{subnetId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateSubnet(@PathParam("subnetId") String id,
+                                 final InputStream input) {
+        return Response.status(Response.Status.OK).build();
+    }
+
+    @DELETE
+    @Path("{subnetId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteSubnet(@PathParam("subnetId") String id) {
+        return Response.noContent().build();
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/package-info.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
new file mode 100644
index 0000000..9f1e043
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * OpenStack networking implementation.
+ */
+package org.onosproject.openstacknetworking.web;
diff --git a/apps/openstacknetworking/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknetworking/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..93076d8
--- /dev/null
+++ b/apps/openstacknetworking/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,25 @@
+<!--
+~ Copyright 2015-present Open Networking Laboratory
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<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.OpenstackInstancePurgeFlowsCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackInstanceReInstallFlowCommand"/>
+        </command>
+    </command-bundle>
+</blueprint>
diff --git a/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..93e95f0
--- /dev/null
+++ b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<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>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>jersey.config.server.provider.classnames</param-name>
+            <param-value>
+                org.onosproject.openstacknetworking.web.OpenstackPortWebResource,
+                org.onosproject.openstacknetworking.web.OpenstackNetworkWebResource,
+                org.onosproject.openstacknetworking.web.OpenstackSubnetWebResource,
+                org.onosproject.openstacknetworking.web.OpensatckRouterWebResource,
+                org.onosproject.openstacknetworking.web.OpenstackFloatingIpWebResource
+            </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>