Initial implementation of gateway load balancer for SONA app

Change-Id: Idd03646d637acd448985eb6e62204a8a9d759867
diff --git a/apps/openstacknetworking/app/BUCK b/apps/openstacknetworking/app/BUCK
index b4c5f92..34933f4 100644
--- a/apps/openstacknetworking/app/BUCK
+++ b/apps/openstacknetworking/app/BUCK
@@ -27,6 +27,7 @@
     '//core/common:onos-core-common-tests',
     '//web/api:onos-rest-tests',
     '//lib:TEST_REST',
+    '//apps/openstacknode/app:onos-apps-openstacknode-app-tests',
 ]
 
 osgi_jar_with_tests (
diff --git a/apps/openstacknetworking/app/pom.xml b/apps/openstacknetworking/app/pom.xml
index 832d298..eea170c 100644
--- a/apps/openstacknetworking/app/pom.xml
+++ b/apps/openstacknetworking/app/pom.xml
@@ -47,8 +47,9 @@
         </dependency>
         <dependency>
             <groupId>org.onosproject</groupId>
-            <artifactId>onos-apps-openstacknetworking-api</artifactId>
+            <artifactId>onos-apps-openstacknode-app</artifactId>
             <version>${project.version}</version>
+            <classifier>tests</classifier>
         </dependency>
         <dependency>
             <groupId>org.onosproject</groupId>
@@ -109,11 +110,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.onosproject</groupId>
-            <artifactId>onos-apps-openstacknode-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
             <groupId>javax.ws.rs</groupId>
             <artifactId>javax.ws.rs-api</artifactId>
         </dependency>
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
index c72b511..dced0df 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -16,7 +16,9 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 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;
@@ -54,8 +56,10 @@
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.openstacknetworking.api.Constants;
 import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
 import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
 import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
 import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
 import org.onosproject.openstacknetworking.api.OpenstackRouterService;
@@ -92,6 +96,7 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
 import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
 import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getGwByComputeDevId;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -123,6 +128,9 @@
     protected OpenstackNodeService osNodeService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -132,6 +140,9 @@
     protected OpenstackFlowRuleService osFlowRuleService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService configService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -231,10 +242,20 @@
                 return;
             }
 
+            OpenstackNode gw = getGwByTargetMac(osNodeService.completeNodes(GATEWAY), targetMac);
+
+            if (gw == null) {
+                return;
+            }
+
+            // if the ARP packet_in received from non-relevant GWs, we simply ignore it
+            if (!Objects.equals(gw.intgBridge(), context.inPacket().receivedFrom().deviceId())) {
+                return;
+            }
+
             Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
                     targetMac, ethernet);
 
-
             TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                     .setOutput(context.inPacket().receivedFrom().port()).build();
 
@@ -314,14 +335,72 @@
 
     /**
      * Installs static ARP rules used in ARP BROAD_CAST mode.
+     *
+     * @param gateway gateway node
+     * @param install flow rule installation flag
+     */
+    private void setFloatingIpArpRuleForGateway(OpenstackNode gateway, boolean install) {
+        if (arpMode.equals(ARP_BROADCAST_MODE)) {
+
+            Set<OpenstackNode> completedGws = osNodeService.completeNodes(GATEWAY);
+            Set<OpenstackNode> finalGws = Sets.newConcurrentHashSet();
+            finalGws.addAll(ImmutableSet.copyOf(completedGws));
+
+            if (install) {
+                if (completedGws.contains(gateway)) {
+                    if (completedGws.size() > 1) {
+                        finalGws.remove(gateway);
+                        osRouterService.floatingIps().forEach(fip -> {
+                            if (fip.getPortId() != null) {
+                                setFloatingIpArpRule(fip, finalGws, false);
+                                finalGws.add(gateway);
+                            }
+                        });
+                    }
+                    osRouterService.floatingIps().forEach(fip -> {
+                        if (fip.getPortId() != null) {
+                            setFloatingIpArpRule(fip, finalGws, true);
+                        }
+                    });
+                } else {
+                    log.warn("Detected node should be included in completed gateway set");
+                }
+            } else {
+                if (!completedGws.contains(gateway)) {
+                    finalGws.add(gateway);
+                    osRouterService.floatingIps().forEach(fip -> {
+                        if (fip.getPortId() != null) {
+                            setFloatingIpArpRule(fip, finalGws, false);
+                        }
+                    });
+                    finalGws.remove(gateway);
+                    if (completedGws.size() >= 1) {
+                        osRouterService.floatingIps().forEach(fip -> {
+                            if (fip.getPortId() != null) {
+                                setFloatingIpArpRule(fip, finalGws, true);
+                            }
+                        });
+                    }
+                } else {
+                    log.warn("Detected node should NOT be included in completed gateway set");
+                }
+            }
+        }
+    }
+
+    /**
+     * Installs static ARP rules used in ARP BROAD_CAST mode.
      * Note that, those rules will be only matched ARP_REQUEST packets,
      * used for telling gateway node the mapped MAC address of requested IP,
      * without the helps from controller.
      *
      * @param fip       floating IP address
+     * @param gateways  a set of gateway nodes
      * @param install   flow rule installation flag
      */
-    private void setFloatingIpArpRule(NetFloatingIP fip, boolean install) {
+    private synchronized void setFloatingIpArpRule(NetFloatingIP fip,
+                                                   Set<OpenstackNode> gateways,
+                                                   boolean install) {
         if (arpMode.equals(ARP_BROADCAST_MODE)) {
 
             if (fip == null) {
@@ -346,6 +425,12 @@
 
             MacAddress targetMac = MacAddress.valueOf(macString);
 
+            OpenstackNode gw = getGwByTargetMac(gateways, targetMac);
+
+            if (gw == null) {
+                return;
+            }
+
             TrafficSelector selector = DefaultTrafficSelector.builder()
                     .matchEthType(EthType.EtherType.ARP.ethType().toShort())
                     .matchArpOp(ARP.OP_REQUEST)
@@ -359,16 +444,14 @@
                     .setOutput(PortNumber.IN_PORT)
                     .build();
 
-            osNodeService.completeNodes(GATEWAY).forEach(n ->
-                    osFlowRuleService.setRule(
-                            appId,
-                            n.intgBridge(),
-                            selector,
-                            treatment,
-                            PRIORITY_ARP_GATEWAY_RULE,
-                            GW_COMMON_TABLE,
-                            install
-                    )
+            osFlowRuleService.setRule(
+                    appId,
+                    gw.intgBridge(),
+                    selector,
+                    treatment,
+                    PRIORITY_ARP_GATEWAY_RULE,
+                    GW_COMMON_TABLE,
+                    install
             );
 
             if (install) {
@@ -381,6 +464,17 @@
         }
     }
 
+    // a helper method
+    private OpenstackNode getGwByTargetMac(Set<OpenstackNode> gateways,
+                                           MacAddress targetMac) {
+        InstancePort instPort = instancePortService.instancePort(targetMac);
+        OpenstackNode gw = null;
+        if (instPort != null && instPort.deviceId() != null) {
+            gw = getGwByComputeDevId(gateways, instPort.deviceId());
+        }
+        return gw;
+    }
+
     /**
      * An internal router event listener, intended to install/uninstall
      * ARP rules for forwarding packets created from floating IPs.
@@ -396,6 +490,9 @@
 
         @Override
         public void event(OpenstackRouterEvent event) {
+
+            Set<OpenstackNode> completedGws = osNodeService.completeNodes(GATEWAY);
+
             switch (event.type()) {
                 case OPENSTACK_ROUTER_CREATED:
                     eventExecutor.execute(() ->
@@ -424,13 +521,13 @@
                 case OPENSTACK_FLOATING_IP_ASSOCIATED:
                     eventExecutor.execute(() ->
                         // associate a floating IP with an existing VM
-                        setFloatingIpArpRule(event.floatingIp(), true)
+                        setFloatingIpArpRule(event.floatingIp(), completedGws, true)
                     );
                     break;
                 case OPENSTACK_FLOATING_IP_DISASSOCIATED:
                     eventExecutor.execute(() ->
                         // disassociate a floating IP with the existing VM
-                        setFloatingIpArpRule(event.floatingIp(), false)
+                        setFloatingIpArpRule(event.floatingIp(), completedGws, false)
                     );
                     break;
                 case OPENSTACK_FLOATING_IP_CREATED:
@@ -441,7 +538,7 @@
                         // associated with any port of VM, then we will set
                         // floating IP related ARP rules to gateway node
                         if (!Strings.isNullOrEmpty(osFip.getPortId())) {
-                            setFloatingIpArpRule(osFip, true);
+                            setFloatingIpArpRule(osFip, completedGws, true);
                         }
                     });
                     break;
@@ -453,7 +550,7 @@
                         // still associated with any port of VM, then we will
                         // remove floating IP related ARP rules from gateway node
                         if (!Strings.isNullOrEmpty(osFip.getPortId())) {
-                            setFloatingIpArpRule(event.floatingIp(), false);
+                            setFloatingIpArpRule(event.floatingIp(), completedGws, false);
                         }
                     });
                     break;
@@ -564,7 +661,8 @@
                 }
                 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
                     eventExecutor.execute(() ->
-                        setFloatingIpArpRule(fip, false)
+                        setFloatingIpArpRule(fip,
+                                osNodeService.completeNodes(GATEWAY), false)
                     );
                 }
             }
@@ -594,11 +692,14 @@
                 case OPENSTACK_NODE_COMPLETE:
                     if (osNode.type().equals(GATEWAY)) {
                         setDefaultArpRule(osNode, true);
+                        setFloatingIpArpRuleForGateway(osNode, true);
                     }
+
                     break;
                 case OPENSTACK_NODE_INCOMPLETE:
                     if (osNode.type().equals(GATEWAY)) {
                         setDefaultArpRule(osNode, false);
+                        setFloatingIpArpRuleForGateway(osNode, false);
                     }
                     break;
                 default:
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
index 42fd73d..dfe20b8 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -16,7 +16,9 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 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;
@@ -80,6 +82,7 @@
 import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
 import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
 import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getGwByComputeDevId;
 import static org.onosproject.openstacknetworking.util.RulePopulatorUtil.buildExtension;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 
@@ -158,7 +161,7 @@
     }
 
     private void setFloatingIpRules(NetFloatingIP floatingIp, Port osPort,
-                                    boolean install) {
+                                    OpenstackNode gateway, boolean install) {
         Network osNet = osNetworkService.network(osPort.getNetworkId());
         if (osNet == null) {
             final String errorFormat = ERR_FLOW + "no network(%s) exists";
@@ -192,7 +195,7 @@
             throw new IllegalStateException(errorFormat);
         }
 
-        setComputeNodeToGateway(instPort, osNet, install);
+        setComputeNodeToGateway(instPort, osNet, gateway, install);
         setDownstreamRules(floatingIp, osNet, instPort, externalPeerRouter, install);
         setUpstreamRules(floatingIp, osNet, instPort, externalPeerRouter, install);
         log.trace("Succeeded to set flow rules for floating ip {}:{} and install: {}",
@@ -201,7 +204,57 @@
                 install);
     }
 
-    private void setComputeNodeToGateway(InstancePort instPort, Network osNet, boolean install) {
+    private synchronized void setComputeNodeToGateway(InstancePort instPort,
+                                                      Network osNet,
+                                                      OpenstackNode gateway,
+                                                      boolean install) {
+
+        Set<OpenstackNode> completedGws = osNodeService.completeNodes(GATEWAY);
+        Set<OpenstackNode> finalGws = Sets.newConcurrentHashSet();
+        finalGws.addAll(ImmutableSet.copyOf(completedGws));
+
+        if (gateway == null) {
+            // these are floating IP related cases...
+            setComputeNodeToGatewayHelper(instPort, osNet,
+                    ImmutableSet.copyOf(finalGws), install);
+        } else {
+            // these are openstack node related cases...
+            if (install) {
+                if (completedGws.contains(gateway)) {
+                    if (completedGws.size() > 1) {
+                        finalGws.remove(gateway);
+                        setComputeNodeToGatewayHelper(instPort, osNet,
+                                ImmutableSet.copyOf(finalGws), false);
+                        finalGws.add(gateway);
+                    }
+
+                    setComputeNodeToGatewayHelper(instPort, osNet,
+                            ImmutableSet.copyOf(finalGws), true);
+                } else {
+                    log.warn("Detected node should be included in completed gateway set");
+                }
+            } else {
+                if (!completedGws.contains(gateway)) {
+                    finalGws.add(gateway);
+                    setComputeNodeToGatewayHelper(instPort, osNet,
+                            ImmutableSet.copyOf(finalGws), false);
+                    finalGws.remove(gateway);
+                    if (completedGws.size() >= 1) {
+                        setComputeNodeToGatewayHelper(instPort, osNet,
+                                ImmutableSet.copyOf(finalGws), true);
+                    }
+                } else {
+                    log.warn("Detected node should NOT be included in completed gateway set");
+                }
+            }
+        }
+    }
+
+    // a helper method
+    private void setComputeNodeToGatewayHelper(InstancePort instPort,
+                                               Network osNet,
+                                               Set<OpenstackNode> gateways,
+                                               boolean install) {
         TrafficTreatment treatment;
 
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
@@ -223,7 +276,8 @@
                 throw new IllegalStateException(error);
         }
 
-        OpenstackNode selectedGatewayNode = selectGatewayNode();
+        OpenstackNode selectedGatewayNode = getGwByComputeDevId(gateways, instPort.deviceId());
+
         if (selectedGatewayNode == null) {
             final String errorFormat = ERR_FLOW + "no gateway node selected";
             throw new IllegalStateException(errorFormat);
@@ -248,11 +302,6 @@
         log.trace("Succeeded to set flow rules from compute node to gateway on compute node");
     }
 
-    private OpenstackNode selectGatewayNode() {
-        //TODO support multiple loadbalancing options.
-        return osNodeService.completeNodes(GATEWAY).stream().findAny().orElse(null);
-    }
-
     private void setDownstreamRules(NetFloatingIP floatingIp, Network osNet,
                                     InstancePort instPort, ExternalPeerRouter externalPeerRouter,
                                     boolean install) {
@@ -454,7 +503,7 @@
         }
         // set floating IP rules only if the port is associated to a VM
         if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
-            setFloatingIpRules(osFip, osPort, true);
+            setFloatingIpRules(osFip, osPort, null, true);
         }
     }
 
@@ -467,7 +516,7 @@
         }
         // set floating IP rules only if the port is associated to a VM
         if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
-            setFloatingIpRules(osFip, osPort, false);
+            setFloatingIpRules(osFip, osPort, null, false);
         }
     }
 
@@ -561,14 +610,51 @@
                                 log.warn("Failed to set floating IP {}", fip.getId());
                                 continue;
                             }
-                            setFloatingIpRules(fip, osPort, true);
+                            setFloatingIpRules(fip, osPort, event.subject(), true);
                         }
                     });
                     break;
-                case OPENSTACK_NODE_CREATED:
-                case OPENSTACK_NODE_UPDATED:
-                case OPENSTACK_NODE_REMOVED:
                 case OPENSTACK_NODE_INCOMPLETE:
+
+                    // we only purge the routing related rules stored in each
+                    // compute node when gateway node becomes unavailable
+                    if (!event.subject().type().equals(GATEWAY))  {
+                        return;
+                    }
+
+                    eventExecutor.execute(() -> {
+                        for (NetFloatingIP fip : osRouterService.floatingIps()) {
+                            if (Strings.isNullOrEmpty(fip.getPortId())) {
+                                continue;
+                            }
+                            Port osPort = osNetworkService.port(fip.getPortId());
+                            if (osPort == null) {
+                                log.warn("Failed to set floating IP {}", fip.getId());
+                                continue;
+                            }
+                            Network osNet = osNetworkService.network(osPort.getNetworkId());
+                            if (osNet == null) {
+                                final String errorFormat = ERR_FLOW + "no network(%s) exists";
+                                final String error = String.format(errorFormat,
+                                        fip.getFloatingIpAddress(),
+                                        osPort.getNetworkId());
+                                throw new IllegalStateException(error);
+                            }
+                            MacAddress srcMac = MacAddress.valueOf(osPort.getMacAddress());
+                            log.trace("Mac address of openstack port: {}", srcMac);
+                            InstancePort instPort = instancePortService.instancePort(srcMac);
+
+                            if (instPort == null) {
+                                final String errorFormat = ERR_FLOW + "no host(MAC:%s) found";
+                                final String error = String.format(errorFormat,
+                                        fip.getFloatingIpAddress(), srcMac);
+                                throw new IllegalStateException(error);
+                            }
+
+                            setComputeNodeToGateway(instPort, osNet, event.subject(), false);
+                        }
+                    });
+                    break;
                 default:
                     // do nothing
                     break;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
index ffd5ab7..98fc0a9 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
@@ -18,12 +18,19 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacknode.api.OpenstackNode;
 import org.openstack4j.core.transport.ObjectMapperSingleton;
 import org.openstack4j.model.ModelEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
 
 import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
 
@@ -79,4 +86,43 @@
             throw new IllegalStateException();
         }
     }
+
+    /**
+     * Obtains the gateway node by device in compute node. Note that the gateway
+     * node is determined by device's device identifier.
+     *
+     * @param gws           a collection of gateway nodes
+     * @param deviceId      device identifier
+     * @return a gateway node
+     */
+    public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
+        int numOfGw = gws.size();
+
+        if (numOfGw == 0) {
+            return null;
+        }
+
+        int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
+
+        return getGwByIndex(gws, gwIndex);
+    }
+
+    private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
+        Map<String, OpenstackNode> hashMap = new HashMap<>();
+        gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
+        TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
+        Iterator<String> iteratorKey = treeMap.keySet().iterator();
+
+        int intIndex = 0;
+        OpenstackNode gw = null;
+        while (iteratorKey.hasNext()) {
+            String key = iteratorKey.next();
+
+            if (intIndex == index) {
+                gw = treeMap.get(key);
+            }
+            intIndex++;
+        }
+        return gw;
+    }
 }
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtilTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtilTest.java
index a6c4bc3..e33637e 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtilTest.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtilTest.java
@@ -16,17 +16,32 @@
 package org.onosproject.openstacknetworking.util;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.testing.EqualsTester;
 import org.apache.commons.io.IOUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.impl.OpenstackNodeTest;
 import org.openstack4j.model.network.NetFloatingIP;
 import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getGwByComputeDevId;
 
 public final class OpenstackNetworkingUtilTest {
 
@@ -56,4 +71,58 @@
                 OpenstackNetworkingUtil.jsonToModelEntity(is, NeutronFloatingIP.class);
         new EqualsTester().addEqualityGroup(floatingIp, floatingIp2).testEquals();
     }
+
+    /**
+     * Tests the getGwByComputeDevId method.
+     */
+    @Test
+    public void testGetGwByComputeDevId() {
+        Set<OpenstackNode> gws = Sets.newConcurrentHashSet();
+        gws.add(genGateway(1));
+        gws.add(genGateway(2));
+        gws.add(genGateway(3));
+
+        Set<OpenstackNode> cloneOfGws = ImmutableSet.copyOf(gws);
+
+        Map<String, Integer> gwCountMap = Maps.newConcurrentMap();
+        int numOfDev = 99;
+
+        for (int i = 1; i < 1 + numOfDev; i++) {
+            OpenstackNode gw = getGwByComputeDevId(gws, genDeviceId(i));
+
+            if (gwCountMap.get(gw.hostname()) == null) {
+                gwCountMap.put(gw.hostname(), 1);
+            } else {
+                gwCountMap.compute(gw.hostname(), (k, v) -> v + 1);
+            }
+
+            new EqualsTester().addEqualityGroup(
+                    getGwByComputeDevId(gws, genDeviceId(i)),
+                    getGwByComputeDevId(cloneOfGws, genDeviceId(i)))
+                    .testEquals();
+        }
+
+        int sum = gwCountMap.values().stream().mapToInt(Integer::intValue).sum();
+        assertEquals(numOfDev, sum);
+    }
+
+    private OpenstackNode genGateway(int index) {
+
+        Device intgBrg = InternalOpenstackNodeTest.createDevice(index);
+
+        String hostname = "gateway-" + index;
+        IpAddress ip = Ip4Address.valueOf("10.10.10." + index);
+        NodeState state = NodeState.COMPLETE;
+        String uplinkPort = "eth0";
+        return InternalOpenstackNodeTest.createNode(hostname,
+                OpenstackNode.NodeType.GATEWAY, intgBrg, ip, uplinkPort, state);
+
+    }
+
+    private DeviceId genDeviceId(int index) {
+        return DeviceId.deviceId("of:compute-" + index);
+    }
+
+    protected class InternalOpenstackNodeTest extends OpenstackNodeTest {
+    }
 }
diff --git a/apps/openstacknetworking/pom.xml b/apps/openstacknetworking/pom.xml
index 1610dd0..a9cb838 100644
--- a/apps/openstacknetworking/pom.xml
+++ b/apps/openstacknetworking/pom.xml
@@ -44,4 +44,22 @@
         <module>app</module>
     </modules>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknetworking-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknode-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknode-app</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
 </project>
diff --git a/apps/openstacknode/app/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java b/apps/openstacknode/app/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java
index fbec333..7deca06 100644
--- a/apps/openstacknode/app/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java
+++ b/apps/openstacknode/app/src/test/java/org/onosproject/openstacknode/impl/OpenstackNodeTest.java
@@ -30,9 +30,9 @@
 /**
  * Provides a set of test OpenstackNode parameters for use with OpenstackNode related tests.
  */
-abstract class OpenstackNodeTest {
+public abstract class OpenstackNodeTest {
 
-    protected static Device createDevice(long devIdNum) {
+    public static Device createDevice(long devIdNum) {
         return new DefaultDevice(new ProviderId("of", "foo"),
                 DeviceId.deviceId(String.format("of:%016d", devIdNum)),
                 SWITCH,
@@ -43,10 +43,10 @@
                 new ChassisId(1));
     }
 
-    protected static OpenstackNode createNode(String hostname, NodeType type,
+    public static OpenstackNode createNode(String hostname, NodeType type,
                                               Device intgBridge, IpAddress ipAddr,
                                               NodeState state) {
-        return org.onosproject.openstacknode.impl.DefaultOpenstackNode.builder()
+        return DefaultOpenstackNode.builder()
                 .hostname(hostname)
                 .type(type)
                 .intgBridge(intgBridge.id())
@@ -56,10 +56,10 @@
                 .build();
     }
 
-    protected static OpenstackNode createNode(String hostname, NodeType type,
+    public static OpenstackNode createNode(String hostname, NodeType type,
                                               Device intgBridge, IpAddress ipAddr,
                                               String uplinkPort, NodeState state) {
-        return org.onosproject.openstacknode.impl.DefaultOpenstackNode.builder()
+        return DefaultOpenstackNode.builder()
                 .hostname(hostname)
                 .type(type)
                 .intgBridge(intgBridge.id())