[ONOS-7444] Optimize SONA gw doesn't use vrouter app and quagga anymore
- Done: Deriving MAC address from external peer router and simple SNAT functionality
- Todo: SNAT, Floating IP-based routing

Change-Id: Ib1a5784a7304c44b28d7b2c9891b98fd13000db1
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/ExternalPeerRouter.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/ExternalPeerRouter.java
new file mode 100644
index 0000000..d42d337
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/ExternalPeerRouter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.api;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+/**
+ * Representation of external peer router.
+ */
+public interface ExternalPeerRouter {
+    /**
+     * Returns external peer router ip address.
+     *
+     * @return ip address.
+     */
+    IpAddress externalPeerRouterIp();
+
+    /**
+     * Returns external peer router mac address.
+     *
+     * @return mac address
+     */
+    MacAddress externalPeerRouterMac();
+
+    /**
+     * Returns external peer router vlan id.
+     *
+     * @return vlan id
+     */
+    VlanId externalPeerRouterVlanId();
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
index 058bb9a..a297b83 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
@@ -15,9 +15,14 @@
  */
 package org.onosproject.openstacknetworking.api;
 
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.event.ListenerService;
+import org.openstack4j.model.network.ExternalGateway;
 import org.openstack4j.model.network.Network;
 import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
 import org.openstack4j.model.network.Subnet;
 
 import java.util.Set;
@@ -89,9 +94,74 @@
     Set<Port> ports();
 
     /**
-     * Returns all OpenStack ports associated with the supplied network.
+     * Returns all OpenStack ports associated with supplied network.
+     *
      * @param networkId network id
      * @return set of ports
      */
     Set<Port> ports(String networkId);
+
+    /**
+     * Derives external router mac address with supplied external gateway.
+     *
+     * @param externalGateway external gateway information
+     * @param router router which owns externalGateway
+     */
+    void deriveExternalPeerRouterMac(ExternalGateway externalGateway, Router router);
+
+    /**
+     * Deletes external router with supplied external gateway.
+     *
+     * @param externalGateway external gateway information
+     */
+    void deleteExternalPeerRouter(ExternalGateway externalGateway);
+
+    /**
+     * Updates external router mac address with supplied ip address.
+     *
+     * @param ipAddress ip address
+     * @param macAddress mac address
+     */
+    void updateExternalPeerRouterMac(IpAddress ipAddress, MacAddress macAddress);
+
+    /**
+     * Updates external router vlan id with supplied ip address.
+     *
+     * @param ipAddress ip address
+     * @param vlanId vlan id
+     */
+    void updateExternalPeerRouterVlan(IpAddress ipAddress, VlanId vlanId);
+
+    /**
+     * Updates external router ith supplied ip address, mac address, vlan id.
+     *
+     * @param ipAddress ip address
+     * @param macAddress mac address
+     * @param vlanId vlan id
+     */
+    void updateExternalPeerRouter(IpAddress ipAddress, MacAddress macAddress, VlanId vlanId);
+
+    /**
+     * Returns external router mac with supplied external gateway.
+     *
+     * @param externalGateway external gateway information
+     * @return mac address
+     */
+    MacAddress externalPeerRouterMac(ExternalGateway externalGateway);
+
+    /**
+     * Returns external peer router with supplied ip address.
+     *
+     * @param ipAddress ip address
+     * @return external peer router
+     */
+    ExternalPeerRouter externalPeerRouter(IpAddress ipAddress);
+
+    /**
+     * Returns external peer router list.
+     *
+     * @return external peer router list
+     */
+    Set<ExternalPeerRouter> externalPeerRouters();
+
 }
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
new file mode 100644
index 0000000..ebd949c
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Lists external peer router lists.
+ */
+@Command(scope = "onos", name = "openstack-peer-routers",
+        description = "Lists external peer router lists")
+public class ExternalPeerRouterListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
new file mode 100644
index 0000000..4ae1dd4
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * IP Address Completer.
+ */
+public class IpAddressCompleter implements Completer {
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<IpAddress> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterIp)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<IpAddress> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
new file mode 100644
index 0000000..6e21de6
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Mac Address Completer.
+ */
+public class MacAddressCompleter implements Completer {
+
+        @Override
+        public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<MacAddress> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterMac)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<MacAddress> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+    }
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
new file mode 100644
index 0000000..15a7555
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router",
+        description = "Update external peer router")
+public class UpdateExternalPeerRouterCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "ip address", description = "ip address",
+            required = true, multiValued = false)
+    private String ipAddress = null;
+
+    @Argument(index = 1, name = "mac address", description = "mac address",
+            required = true, multiValued = false)
+    private String macAddress = null;
+
+    @Argument(index = 2, name = "vlan id", description = "vlan id",
+            required = true, multiValued = false)
+    private String vlanId = null;
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+    private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+    private static final String NONE = "None";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        IpAddress externalPeerIpAddress = IpAddress.valueOf(
+                IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+        if (service.externalPeerRouters().isEmpty()) {
+            print(NO_ELEMENT);
+            return;
+        } else if (service.externalPeerRouters().stream()
+                .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+            print(NO_ELEMENT);
+            return;
+        }
+        try {
+            if (vlanId.equals(NONE)) {
+                service.updateExternalPeerRouter(externalPeerIpAddress,
+                        MacAddress.valueOf(macAddress),
+                        VlanId.NONE);
+
+            } else {
+                service.updateExternalPeerRouter(externalPeerIpAddress,
+                        MacAddress.valueOf(macAddress),
+                        VlanId.vlanId(vlanId));
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
+
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
new file mode 100644
index 0000000..eed62e7
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router macc address.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router-vlan",
+        description = "Updates external peer router vlan")
+public class UpdateExternalPeerRouterVlanCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "ip address", description = "ip address",
+            required = true, multiValued = false)
+    private String ipAddress = null;
+
+    @Argument(index = 1, name = "vlan id", description = "vlan id",
+            required = true, multiValued = false)
+    private String vlanId = null;
+
+    private static final String FORMAT = "%-20s%-20s%-20s";
+    private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+    private static final String NONE = "None";
+
+    @Override
+    protected void execute() {
+        OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+        IpAddress externalPeerIpAddress = IpAddress.valueOf(
+                IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+        if (service.externalPeerRouters().isEmpty()) {
+            print(NO_ELEMENT);
+            return;
+        } else if (service.externalPeerRouters().stream()
+                .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+            print(NO_ELEMENT);
+            return;
+        }
+
+        try {
+            if (vlanId.equals(NONE)) {
+                service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.NONE);
+            } else {
+                service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.vlanId(vlanId));
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+        print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+        List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+        for (ExternalPeerRouter router: routers) {
+            print(FORMAT, router.externalPeerRouterIp(),
+                    router.externalPeerRouterMac().toString(),
+                    router.externalPeerRouterVlanId());
+        }
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
new file mode 100644
index 0000000..0768859
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Vlan Id Completer.
+ */
+public class VlanIdCompleter implements Completer {
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+        Set<VlanId> set = osNetService.externalPeerRouters().stream()
+                .map(ExternalPeerRouter::externalPeerRouterVlanId)
+                .collect(Collectors.toSet());
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<VlanId> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
new file mode 100644
index 0000000..fb18502
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+
+import java.util.Objects;
+
+/**
+ * Implementation of external peer router.
+ */
+public class DefaultExternalPeerRouter implements ExternalPeerRouter {
+
+    private IpAddress externalPeerRouterIp;
+    private MacAddress externalPeerRouterMac;
+    private VlanId externalPeerRouterVlanId;
+
+    public DefaultExternalPeerRouter(IpAddress externalPeerRouterIp,
+                                     MacAddress externalPeerRouterMac,
+                                     VlanId externalPeerRouterVlanId) {
+        this.externalPeerRouterIp = externalPeerRouterIp;
+        this.externalPeerRouterMac = externalPeerRouterMac;
+        this.externalPeerRouterVlanId = externalPeerRouterVlanId;
+    }
+
+    @Override
+    public IpAddress externalPeerRouterIp() {
+        return this.externalPeerRouterIp;
+    }
+    @Override
+    public MacAddress externalPeerRouterMac() {
+        return this.externalPeerRouterMac;
+    }
+    @Override
+    public VlanId externalPeerRouterVlanId() {
+        return this.externalPeerRouterVlanId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof DefaultExternalPeerRouter) {
+            DefaultExternalPeerRouter that = (DefaultExternalPeerRouter) obj;
+            return Objects.equals(externalPeerRouterIp, that.externalPeerRouterIp) &&
+                    Objects.equals(externalPeerRouterMac, that.externalPeerRouterMac) &&
+                    Objects.equals(externalPeerRouterVlanId, that.externalPeerRouterVlanId);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(externalPeerRouterIp,
+                externalPeerRouterMac,
+                externalPeerRouterVlanId);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("externalPeerRouterIp", externalPeerRouterIp)
+                .add("externalPeerRouterMac", externalPeerRouterMac)
+                .add("externalPeerRouterVlanId", externalPeerRouterVlanId)
+                .toString();
+    }
+}
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
index 4442c9a..19650e3 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
@@ -285,7 +285,7 @@
         public void event(MapEvent<String, Network> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack network updated {}", event.newValue());
+                    log.debug("OpenStack network updated");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_NETWORK_UPDATED,
@@ -293,7 +293,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack network created {}", event.newValue());
+                    log.debug("OpenStack network created");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_NETWORK_CREATED,
@@ -301,7 +301,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack network removed {}", event.oldValue());
+                    log.debug("OpenStack network removed");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_NETWORK_REMOVED,
@@ -321,7 +321,7 @@
         public void event(MapEvent<String, Subnet> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack subnet updated {}", event.newValue());
+                    log.debug("OpenStack subnet updated");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_SUBNET_UPDATED,
@@ -330,7 +330,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack subnet created {}", event.newValue());
+                    log.debug("OpenStack subnet created");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_SUBNET_CREATED,
@@ -339,7 +339,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack subnet removed {}", event.oldValue());
+                    log.debug("OpenStack subnet removed");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_SUBNET_REMOVED,
@@ -360,7 +360,7 @@
         public void event(MapEvent<String, Port> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack port updated {}", event.newValue());
+                    log.debug("OpenStack port updated");
                     eventExecutor.execute(() -> {
                         Port oldPort = event.oldValue().value();
                         Port newPort = event.newValue().value();
@@ -371,7 +371,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack port created {}", event.newValue());
+                    log.debug("OpenStack port created");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_PORT_CREATED,
@@ -380,7 +380,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack port removed {}", event.oldValue());
+                    log.debug("OpenStack port removed");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackNetworkEvent(
                                 OPENSTACK_PORT_REMOVED,
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
index 0397e8a..62fc7ac 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
@@ -25,6 +25,7 @@
 import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
 import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
 import org.onosproject.openstacknetworking.api.OpenstackRouterStore;
 import org.onosproject.openstacknetworking.api.OpenstackRouterStoreDelegate;
@@ -94,6 +95,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final MapEventListener<String, Router> routerMapListener = new OpenstackRouterMapListener();
@@ -271,7 +275,7 @@
         public void event(MapEvent<String, Router> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack router updated {}", event.newValue());
+                    log.debug("OpenStack router updated");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_UPDATED,
@@ -280,7 +284,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack router created {}", event.newValue());
+                    log.debug("OpenStack router created");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_CREATED,
@@ -288,7 +292,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack router removed {}", event.oldValue());
+                    log.debug("OpenStack router removed");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_REMOVED,
@@ -324,7 +328,7 @@
         public void event(MapEvent<String, RouterInterface> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack router interface updated {}", event.newValue());
+                    log.debug("OpenStack router interface updated");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_INTERFACE_UPDATED,
@@ -333,7 +337,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack router interface created {}", event.newValue());
+                    log.debug("OpenStack router interface created");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_INTERFACE_ADDED,
@@ -342,7 +346,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack router interface removed {}", event.oldValue());
+                    log.debug("OpenStack router interface removed");
                     eventExecutor.execute(() -> {
                         notifyDelegate(new OpenstackRouterEvent(
                                 OPENSTACK_ROUTER_INTERFACE_REMOVED,
@@ -363,7 +367,7 @@
         public void event(MapEvent<String, NetFloatingIP> event) {
             switch (event.type()) {
                 case UPDATE:
-                    log.debug("OpenStack floating IP updated {}", event.newValue());
+                    log.debug("OpenStack floating IP updated");
                     eventExecutor.execute(() -> {
                         Router osRouter = Strings.isNullOrEmpty(
                                 event.newValue().value().getRouterId()) ?
@@ -377,7 +381,7 @@
                     });
                     break;
                 case INSERT:
-                    log.debug("OpenStack floating IP created {}", event.newValue());
+                    log.debug("OpenStack floating IP created");
                     eventExecutor.execute(() -> {
                         Router osRouter = Strings.isNullOrEmpty(
                                 event.newValue().value().getRouterId()) ?
@@ -390,7 +394,7 @@
                     });
                     break;
                 case REMOVE:
-                    log.debug("OpenStack floating IP removed {}", event.oldValue());
+                    log.debug("OpenStack floating IP removed");
                     eventExecutor.execute(() -> {
                         Router osRouter = Strings.isNullOrEmpty(
                                 event.oldValue().value().getRouterId()) ?
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
index 6f10112..8d16bce 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
@@ -23,20 +23,45 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketService;
 import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
 import org.openstack4j.model.network.Network;
 import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
 import org.openstack4j.model.network.Subnet;
 import org.slf4j.Logger;
 
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -51,6 +76,7 @@
  * Provides implementation of administering and interfacing OpenStack network,
  * subnet, and port.
  */
+
 @Service
 @Component(immediate = true)
 public class OpenstackNetworkManager
@@ -77,21 +103,57 @@
     private static final String ERR_NULL_PORT_ID = "OpenStack port ID cannot be null";
     private static final String ERR_NULL_PORT_NET_ID = "OpenStack port network ID cannot be null";
 
+    private static final String ERR_NOT_FOUND = " does not exist";
     private static final String ERR_IN_USE = " still in use";
+    private static final String ERR_DUPLICATE = " already exists";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OpenstackNetworkStore osNetworkStore;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+
     private final OpenstackNetworkStoreDelegate delegate = new InternalNetworkStoreDelegate();
 
+    private ConsistentMap<String, ExternalPeerRouter> externalPeerRouterMap;
+
+    private static final KryoNamespace SERIALIZER_EXTERNAL_PEER_ROUTER_MAP = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(ExternalPeerRouter.class)
+            .register(DefaultExternalPeerRouter.class)
+            .register(MacAddress.class)
+            .register(IpAddress.class)
+            .register(VlanId.class)
+            .build();
+
+    private ApplicationId appId;
+
+
     @Activate
     protected void activate() {
-        coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+        appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+
         osNetworkStore.setDelegate(delegate);
         log.info("Started");
+
+        externalPeerRouterMap = storageService.<String, ExternalPeerRouter>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_EXTERNAL_PEER_ROUTER_MAP))
+                .withName("external-routermap")
+                .withApplicationId(appId)
+                .build();
     }
 
     @Deactivate
@@ -256,12 +318,12 @@
                 .stream()
                 .filter(p -> p.getId().contains(portName.substring(3)))
                 .findFirst();
-        return osPort.isPresent() ? osPort.get() : null;
+        return osPort.orElse(null);
     }
 
     @Override
     public Set<Port> ports() {
-        return osNetworkStore.ports();
+        return ImmutableSet.copyOf(osNetworkStore.ports());
     }
 
     @Override
@@ -272,6 +334,175 @@
         return ImmutableSet.copyOf(osPorts);
     }
 
+    @Override
+    public ExternalPeerRouter externalPeerRouter(IpAddress ipAddress) {
+        if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+            return externalPeerRouterMap.get(ipAddress.toString()).value();
+        }
+        return null;
+    }
+
+    @Override
+    public void deriveExternalPeerRouterMac(ExternalGateway externalGateway, Router router) {
+        log.info("deriveExternalPeerRouterMac called");
+
+        IpAddress sourceIp = getExternalGatewaySourceIp(externalGateway, router);
+        IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+
+        if (sourceIp == null || targetIp == null) {
+            log.warn("Failed to derive external router mac address because source IP {} or target IP {} is null",
+                    sourceIp, targetIp);
+            return;
+        }
+
+        if (externalPeerRouterMap.containsKey(targetIp.toString()) &&
+                !externalPeerRouterMap.get(
+                        targetIp.toString()).value().externalPeerRouterMac().equals(MacAddress.NONE)) {
+            return;
+        }
+
+        MacAddress sourceMac = Constants.DEFAULT_GATEWAY_MAC;
+        Ethernet ethRequest = ARP.buildArpRequest(sourceMac.toBytes(),
+                sourceIp.toOctets(),
+                targetIp.toOctets(),
+                VlanId.NO_VID);
+
+        if (osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY).isEmpty()) {
+            log.warn("There's no complete gateway");
+            return;
+        }
+        OpenstackNode gatewayNode = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+                .stream()
+                .findFirst()
+                .orElse(null);
+
+        if (gatewayNode == null) {
+            return;
+        }
+
+        String upLinkPort = gatewayNode.uplinkPort();
+
+        org.onosproject.net.Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
+                .filter(p -> Objects.equals(p.annotations().value(PORT_NAME), upLinkPort))
+                .findAny().orElse(null);
+
+        if (port == null) {
+            log.warn("There's no uplink port for gateway node {}", gatewayNode.toString());
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(port.number())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                gatewayNode.intgBridge(),
+                treatment,
+                ByteBuffer.wrap(ethRequest.serialize())));
+
+        externalPeerRouterMap.put(
+                targetIp.toString(), new DefaultExternalPeerRouter(targetIp, MacAddress.NONE, VlanId.NONE));
+
+        log.info("Initializes external peer router map with peer router IP {}", targetIp.toString());
+    }
+
+    @Override
+    public void deleteExternalPeerRouter(ExternalGateway externalGateway) {
+        IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+        if (targetIp == null) {
+            return;
+        }
+
+        if (externalPeerRouterMap.containsKey(targetIp.toString())) {
+            externalPeerRouterMap.remove(targetIp.toString());
+        }
+    }
+
+    private IpAddress getExternalGatewaySourceIp(ExternalGateway externalGateway, Router router) {
+        Port exGatewayPort = ports(externalGateway.getNetworkId())
+                .stream()
+                .filter(port -> Objects.equals(port.getDeviceId(), router.getId()))
+                .findAny().orElse(null);
+        if (exGatewayPort == null) {
+            log.warn("no external gateway port for router({})", router.getName());
+            return null;
+        }
+
+        IP ipAddress = exGatewayPort.getFixedIps().stream().findFirst().orElse(null);
+
+        return ipAddress == null ? null : IpAddress.valueOf(ipAddress.getIpAddress());
+    }
+
+    private IpAddress getExternalPeerRouterIp(ExternalGateway externalGateway) {
+        Optional<Subnet> externalSubnet = subnets(externalGateway.getNetworkId())
+                .stream()
+                .findFirst();
+
+        if (externalSubnet.isPresent()) {
+            return IpAddress.valueOf(externalSubnet.get().getGateway());
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void updateExternalPeerRouterMac(IpAddress ipAddress, MacAddress macAddress) {
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+                new DefaultExternalPeerRouter(ipAddress, macAddress, existing.externalPeerRouterVlanId()));
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+        log.info("Updated external peer router map {}",
+                externalPeerRouterMap.get(ipAddress.toString()).value().toString());
+    }
+
+
+    @Override
+    public void updateExternalPeerRouter(IpAddress ipAddress, MacAddress macAddress, VlanId vlanId) {
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+                new DefaultExternalPeerRouter(ipAddress, macAddress, vlanId));
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+    }
+
+    @Override
+    public MacAddress externalPeerRouterMac(ExternalGateway externalGateway) {
+        IpAddress ipAddress = getExternalPeerRouterIp(externalGateway);
+
+        if (ipAddress == null) {
+            return null;
+        }
+        if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+            return externalPeerRouterMap.get(ipAddress.toString()).value().externalPeerRouterMac();
+        } else {
+            throw new NoSuchElementException();
+        }
+    }
+
+    @Override
+    public void updateExternalPeerRouterVlan(IpAddress ipAddress, VlanId vlanId) {
+
+        try {
+            externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) -> {
+                return new DefaultExternalPeerRouter(ipAddress, existing.externalPeerRouterMac(), vlanId);
+            });
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+    }
+
+    @Override
+    public Set<ExternalPeerRouter> externalPeerRouters() {
+        Set<ExternalPeerRouter> externalPeerRouters = externalPeerRouterMap.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(externalPeerRouters);
+    }
+
     private boolean isNetworkInUse(String netId) {
         return !subnets(netId).isEmpty() && !ports(netId).isEmpty();
     }
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
index c527f9e..98fdbd9 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -26,6 +26,7 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.packet.DefaultOutboundPacket;
@@ -90,36 +91,50 @@
 
     private void processArpPacket(PacketContext context, Ethernet ethernet) {
         ARP arp = (ARP) ethernet.getPayload();
-        if (arp.getOpCode() != ARP.OP_REQUEST) {
-            return;
+        if (arp.getOpCode() == ARP.OP_REQUEST) {
+            if (log.isTraceEnabled()) {
+                log.trace("ARP request received from {} for {}",
+                        Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
+                        Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
+            }
+
+            IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+            if (!isServiceIp(targetIp.getIp4Address())) {
+                log.trace("Unknown target ARP request for {}, ignore it", targetIp);
+                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())));
+
+            context.block();
+        } else if (arp.getOpCode() == ARP.OP_REPLY) {
+            PortNumber receivedPortNum = context.inPacket().receivedFrom().port();
+            log.debug("ARP reply ip: {}, mac: {}",
+                    Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+                    MacAddress.valueOf(arp.getSenderHardwareAddress()));
+            try {
+                if (receivedPortNum.equals(
+                        osNodeService.node(context.inPacket().receivedFrom().deviceId()).uplinkPortNum())) {
+                    osNetworkService.updateExternalPeerRouterMac(
+                            Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+                            MacAddress.valueOf(arp.getSenderHardwareAddress()));
+                }
+            } catch (Exception e) {
+                log.error("Exception occurred because of {}", e.toString());
+            }
         }
 
-        if (log.isTraceEnabled()) {
-            log.trace("ARP request received from {} for {}",
-                    Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
-                    Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
-        }
-
-        IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
-        if (!isServiceIp(targetIp.getIp4Address())) {
-            log.trace("Unknown target ARP request for {}, ignore it", targetIp);
-            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())));
-
-        context.block();
     }
 
     private class InternalPacketProcessor implements PacketProcessor {
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
index dd2e887..0a915b7 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
@@ -38,7 +38,6 @@
 import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
-import org.onosproject.core.GroupId;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
@@ -207,7 +206,6 @@
 
     private void routerUpdated(Router osRouter) {
         ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
-
         osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
             Network network = osNetworkService.network(osNetworkService.subnet(iface.getSubnetId())
                     .getNetworkId());
@@ -215,10 +213,12 @@
         });
 
         if (exGateway == null) {
+            osNetworkService.deleteExternalPeerRouter(exGateway);
             osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
                 setSourceNat(osRouter, iface, false);
             });
         } else {
+            osNetworkService.deriveExternalPeerRouterMac(exGateway, osRouter);
             osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
                 setSourceNat(osRouter, iface, exGateway.isEnableSnat());
             });
@@ -384,6 +384,12 @@
     }
 
     private void setGatewayIcmp(Subnet osSubnet, boolean install) {
+        OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+        if (sourceNatGateway == null) {
+            return;
+        }
+
         if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
             // do nothing if no gateway is set
             return;
@@ -397,7 +403,7 @@
                         .filter(cNode -> cNode.dataIp() != null)
                         .forEach(cNode -> setRulesToGatewayWithDstIp(
                                 cNode,
-                                cNode.gatewayGroupId(NetworkMode.VXLAN),
+                                sourceNatGateway,
                                 network.getProviderSegID(),
                                 IpAddress.valueOf(osSubnet.getGateway()),
                                 NetworkMode.VXLAN,
@@ -408,7 +414,7 @@
                         .filter(cNode -> cNode.vlanPortNum() != null)
                         .forEach(cNode -> setRulesToGatewayWithDstIp(
                                 cNode,
-                                cNode.gatewayGroupId(NetworkMode.VLAN),
+                                sourceNatGateway,
                                 network.getProviderSegID(),
                                 IpAddress.valueOf(osSubnet.getGateway()),
                                 NetworkMode.VLAN,
@@ -620,7 +626,11 @@
     private void setRulesToGateway(OpenstackNode osNode, String segmentId, IpPrefix srcSubnet,
                                    NetworkType networkType, boolean install) {
         TrafficTreatment treatment;
-        GroupId groupId;
+        OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+        if (sourceNatGateway == null) {
+            return;
+        }
 
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
@@ -630,11 +640,9 @@
         switch (networkType) {
             case VXLAN:
                 sBuilder.matchTunnelId(Long.parseLong(segmentId));
-                groupId = osNode.gatewayGroupId(NetworkMode.VXLAN);
                 break;
             case VLAN:
                 sBuilder.matchVlanId(VlanId.vlanId(segmentId));
-                groupId = osNode.gatewayGroupId(NetworkMode.VLAN);
                 break;
             default:
                 final String error = String.format(
@@ -644,7 +652,12 @@
         }
 
         treatment = DefaultTrafficTreatment.builder()
-                .group(groupId)
+                .extension(buildExtension(
+                        deviceService,
+                        osNode.intgBridge(),
+                        sourceNatGateway.dataIp().getIp4Address()),
+                        osNode.intgBridge())
+                .setOutput(osNode.tunnelPortNum())
                 .build();
 
         osFlowRuleService.setRule(
@@ -685,7 +698,7 @@
                 install);
     }
 
-    private void setRulesToGatewayWithDstIp(OpenstackNode osNode, GroupId groupId,
+    private void setRulesToGatewayWithDstIp(OpenstackNode osNode, OpenstackNode sourceNatGateway,
                                             String segmentId, IpAddress dstIp,
                                             NetworkMode networkMode, boolean install) {
         TrafficSelector selector;
@@ -704,7 +717,12 @@
         }
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .group(groupId)
+                .extension(buildExtension(
+                        deviceService,
+                        osNode.intgBridge(),
+                        sourceNatGateway.dataIp().getIp4Address()),
+                        osNode.intgBridge())
+                .setOutput(osNode.tunnelPortNum())
                 .build();
 
         osFlowRuleService.setRule(
@@ -929,7 +947,9 @@
                             event.routerIface()));
                     break;
                 case OPENSTACK_ROUTER_GATEWAY_ADDED:
+                    log.debug("Router external gateway {} added", event.externalGateway().getNetworkId());
                 case OPENSTACK_ROUTER_GATEWAY_REMOVED:
+                    log.debug("Router external gateway {} removed", event.externalGateway().getNetworkId());
                 case OPENSTACK_FLOATING_IP_CREATED:
                 case OPENSTACK_FLOATING_IP_UPDATED:
                 case OPENSTACK_FLOATING_IP_REMOVED:
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
index a07cdef..7beb11d 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -24,6 +24,7 @@
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.TCP;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
@@ -183,19 +184,25 @@
         IpAddress srcIp = IpAddress.valueOf(iPacket.getSourceAddress());
         Subnet srcSubnet = getSourceSubnet(srcInstPort, srcIp);
         IpAddress externalGatewayIp = getExternalIp(srcSubnet);
+
         if (externalGatewayIp == null) {
             return;
         }
 
+        MacAddress externalPeerRouterMac = getExternalPeerRouterMac(srcSubnet);
+        if (externalPeerRouterMac == null) {
+            return;
+        }
+
         populateSnatFlowRules(context.inPacket(),
                 srcInstPort,
                 TpPort.tpPort(patPort),
-                externalGatewayIp);
+                externalGatewayIp, externalPeerRouterMac);
 
         packetOut(eth.duplicate(),
                 packetIn.receivedFrom().deviceId(),
                 patPort,
-                externalGatewayIp);
+                externalGatewayIp, externalPeerRouterMac);
     }
 
     private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
@@ -209,6 +216,32 @@
         return osNetworkService.subnet(fixedIp.getSubnetId());
     }
 
+    private MacAddress getExternalPeerRouterMac(Subnet srcSubnet) {
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            // this subnet is not connected to the router
+            log.trace(ERR_PACKETIN + "source subnet(ID:{}, CIDR:{}) has no router",
+                    srcSubnet.getId(), srcSubnet.getCidr());
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter == null) {
+            return null;
+        }
+        if (osRouter.getExternalGatewayInfo() == null) {
+            // this router does not have external connectivity
+            log.trace(ERR_PACKETIN + "router({}) has no external gateway",
+                    osRouter.getName());
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+        return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+    }
     private IpAddress getExternalIp(Subnet srcSubnet) {
         RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
                 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
@@ -251,7 +284,7 @@
     }
 
     private void populateSnatFlowRules(InboundPacket packetIn, InstancePort srcInstPort,
-                                       TpPort patPort, IpAddress externalIp) {
+                                       TpPort patPort, IpAddress externalIp, MacAddress externalPeerRouterMac) {
         Network osNet = osNetworkService.network(srcInstPort.networkId());
         if (osNet == null) {
             final String error = String.format(ERR_PACKETIN + "network %s not found",
@@ -269,13 +302,15 @@
         setUpstreamRules(osNet.getProviderSegID(),
                 osNet.getNetworkType(),
                 externalIp,
+                externalPeerRouterMac,
                 patPort,
                 packetIn);
     }
 
     private void setDownstreamRules(InstancePort srcInstPort, String segmentId,
                                     NetworkType networkType,
-                                    IpAddress externalIp, TpPort patPort,
+                                    IpAddress externalIp,
+                                    TpPort patPort,
                                     InboundPacket packetIn) {
         IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
         IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
@@ -357,7 +392,8 @@
     }
 
     private void setUpstreamRules(String segmentId, NetworkType networkType,
-                                  IpAddress externalIp, TpPort patPort,
+                                  IpAddress externalIp, MacAddress externalPeerRouterMac,
+                                  TpPort patPort,
                                   InboundPacket packetIn) {
         IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
 
@@ -389,14 +425,14 @@
                 sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
                         .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
                 tBuilder.setTcpSrc(patPort)
-                        .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
+                        .setEthDst(externalPeerRouterMac);
                 break;
             case IPv4.PROTOCOL_UDP:
                 UDP udpPacket = (UDP) iPacket.getPayload();
                 sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
                         .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
                 tBuilder.setUdpSrc(patPort)
-                        .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
+                        .setEthDst(externalPeerRouterMac);
                 break;
             default:
                 log.debug("Unsupported IPv4 protocol {}");
@@ -407,7 +443,7 @@
         osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
             TrafficTreatment.Builder tmpBuilder =
                     DefaultTrafficTreatment.builder(tBuilder.build());
-            tmpBuilder.setOutput(gNode.patchPortNum());
+            tmpBuilder.setOutput(gNode.uplinkPortNum());
 
             osFlowRuleService.setRule(
                     appId,
@@ -421,7 +457,7 @@
     }
 
     private void packetOut(Ethernet ethPacketIn, DeviceId srcDevice, int patPort,
-                           IpAddress externalIp) {
+                           IpAddress externalIp, MacAddress externalPeerRouterMac) {
         IPv4 iPacket = (IPv4) ethPacketIn.getPayload();
         switch (iPacket.getProtocol()) {
             case IPv4.PROTOCOL_TCP:
@@ -446,7 +482,7 @@
         iPacket.setSourceAddress(externalIp.toString());
         iPacket.resetChecksum();
         iPacket.setParent(ethPacketIn);
-        ethPacketIn.setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC);
+        ethPacketIn.setDestinationMACAddress(externalPeerRouterMac);
         ethPacketIn.setPayload(iPacket);
         ethPacketIn.resetChecksum();
 
@@ -458,7 +494,7 @@
         }
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(srcNode.patchPortNum()).build();
+                .setOutput(srcNode.uplinkPortNum()).build();
         packetService.emit(new DefaultOutboundPacket(
                 srcDevice,
                 treatment,
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
index 5b7cde6..179c3ff 100644
--- a/apps/openstacknetworking/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/openstacknetworking/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -42,5 +42,23 @@
         <command>
             <action class="org.onosproject.openstacknetworking.cli.OpenstackSyncRulesCommand"/>
         </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.ExternalPeerRouterListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterCommand"/>
+            <completers>
+                <ref component-id="ipAddressCompleter"/>
+                <ref component-id="macAddressCompleter"/>
+                <ref component-id="vlanIdCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterVlanCommand"/>
+        </command>
     </command-bundle>
+
+    <bean id="ipAddressCompleter" class="org.onosproject.openstacknetworking.cli.IpAddressCompleter"/>
+    <bean id="macAddressCompleter" class="org.onosproject.openstacknetworking.cli.MacAddressCompleter"/>
+    <bean id="vlanIdCompleter" class="org.onosproject.openstacknetworking.cli.VlanIdCompleter"/>
 </blueprint>
diff --git a/apps/openstacknetworking/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java b/apps/openstacknetworking/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
index 15ec873..866aa55 100644
--- a/apps/openstacknetworking/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
+++ b/apps/openstacknetworking/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
@@ -15,18 +15,28 @@
  */
 package org.onosproject.openstacknetworking.impl;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.util.concurrent.MoreExecutors;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
+import org.onosproject.cluster.ClusterServiceAdapter;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreServiceAdapter;
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.event.Event;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.packet.PacketServiceAdapter;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
 import org.onosproject.store.service.TestStorageService;
 import org.openstack4j.model.network.Network;
 import org.openstack4j.model.network.Port;
@@ -36,10 +46,23 @@
 import org.openstack4j.openstack.networking.domain.NeutronSubnet;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.*;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_UPDATED;
+import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
 
 /**
  * Unit tests for OpenStack network manager.
@@ -101,7 +124,12 @@
         osNetworkStore.activate();
 
         target = new OpenstackNetworkManager();
-        target.coreService = new TestCoreService();
+        TestUtils.setField(target, "coreService", new TestCoreService());
+        TestUtils.setField(target, "packetService", new PacketServiceAdapter());
+        TestUtils.setField(target, "deviceService", new DeviceServiceAdapter());
+        TestUtils.setField(target, "storageService", new TestStorageService());
+        TestUtils.setField(target, "clusterService", new ClusterServiceAdapter());
+        TestUtils.setField(target, "openstacknodeService", new TestOpenstackNodeManager());
         target.osNetworkStore = osNetworkStore;
         target.addListener(testListener);
         target.activate();
@@ -560,6 +588,75 @@
         }
     }
 
+    private static class TestOpenstackNodeManager implements OpenstackNodeService, OpenstackNodeAdminService {
+        Map<String, OpenstackNode> osNodeMap = Maps.newHashMap();
+        List<OpenstackNodeListener> listeners = Lists.newArrayList();
+
+        @Override
+        public Set<OpenstackNode> nodes() {
+            return ImmutableSet.copyOf(osNodeMap.values());
+        }
+
+        @Override
+        public Set<OpenstackNode> nodes(OpenstackNode.NodeType type) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.type() == type)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<OpenstackNode> completeNodes() {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.state() == COMPLETE)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<OpenstackNode> completeNodes(OpenstackNode.NodeType type) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> osNode.type() == type && osNode.state() == COMPLETE)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public OpenstackNode node(String hostname) {
+            return osNodeMap.get(hostname);
+        }
+
+        @Override
+        public OpenstackNode node(DeviceId deviceId) {
+            return osNodeMap.values().stream()
+                    .filter(osNode -> Objects.equals(osNode.intgBridge(), deviceId) ||
+                            Objects.equals(osNode.ovsdb(), deviceId))
+                    .findFirst().orElse(null);
+        }
+
+        @Override
+        public void addListener(OpenstackNodeListener listener) {
+            listeners.add(listener);
+        }
+
+        @Override
+        public void removeListener(OpenstackNodeListener listener) {
+            listeners.remove(listener);
+        }
+
+        @Override
+        public void createNode(OpenstackNode osNode) {
+            osNodeMap.put(osNode.hostname(), osNode);
+        }
+
+        @Override
+        public void updateNode(OpenstackNode osNode) {
+            osNodeMap.put(osNode.hostname(), osNode);
+        }
+
+        @Override
+        public OpenstackNode removeNode(String hostname) {
+            return null;
+        }
+    }
+
     private static class TestOpenstackNetworkListener implements OpenstackNetworkListener {
         private List<OpenstackNetworkEvent> events = Lists.newArrayList();
 
diff --git a/apps/openstacknode/api/src/main/java/org/onosproject/openstacknode/api/OpenstackNode.java b/apps/openstacknode/api/src/main/java/org/onosproject/openstacknode/api/OpenstackNode.java
index f1e2bc5..2ecfefe 100644
--- a/apps/openstacknode/api/src/main/java/org/onosproject/openstacknode/api/OpenstackNode.java
+++ b/apps/openstacknode/api/src/main/java/org/onosproject/openstacknode/api/OpenstackNode.java
@@ -151,6 +151,13 @@
     String uplinkPort();
 
     /**
+     * Returns the uplink port number.
+     *
+     * @return uplink port number
+     */
+    PortNumber uplinkPortNum();
+
+    /**
      * Returns new openstack node instance with given state.
      *
      * @param newState updated state
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
index 6650be6..f208529 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
@@ -122,6 +122,21 @@
     }
 
     @Override
+    public PortNumber uplinkPortNum() {
+        if (uplinkPort == null) {
+            return null;
+        }
+
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        Port port = deviceService.getPorts(intgBridge).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), uplinkPort))
+                .findAny().orElse(null);
+
+        return port != null ? port.number() : null;
+
+    }
+    @Override
     public PortNumber tunnelPortNum() {
         if (dataIp == null) {
             return null;