CORD-537 Added public IP gateways for ARP proxy

- Added public IP gateway and MAC pairs to network config for ARP proxy
- Added vSG as a ONOS host

Change-Id: Ia722ba3843297cec7134da5d64bbf188c22762f8
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
index e071550..4672912 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -16,6 +16,7 @@
 package org.onosproject.cordvtn;
 
 import com.google.common.collect.Lists;
+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;
@@ -156,7 +157,7 @@
     private HostProviderService hostProvider;
     private CordVtnRuleInstaller ruleInstaller;
     private CordVtnArpProxy arpProxy;
-    private volatile MacAddress gatewayMac = MacAddress.NONE;
+    private volatile MacAddress privateGatewayMac = MacAddress.NONE;
 
     /**
      * Creates an cordvtn host location provider.
@@ -305,7 +306,7 @@
 
     @Override
     public void updateVirtualSubscriberGateways(HostId vSgHostId, String serviceVlan,
-                                                Set<IpAddress> vSgIps) {
+                                                Map<IpAddress, MacAddress> vSgs) {
         Host vSgVm = hostService.getHost(vSgHostId);
 
         if (vSgVm == null || !vSgVm.annotations().value(S_TAG).equals(serviceVlan)) {
@@ -313,8 +314,45 @@
             return;
         }
 
-        log.info("Updates vSGs in {} with {}", vSgVm.id(), vSgIps.toString());
-        ruleInstaller.populateSubscriberGatewayRules(vSgVm, vSgIps);
+        log.info("Updates vSGs in {} with {}", vSgVm.id(), vSgs.toString());
+        vSgs.entrySet().stream()
+                .forEach(entry -> addVirtualSubscriberGateway(
+                        vSgVm,
+                        entry.getKey(),
+                        entry.getValue(),
+                        serviceVlan));
+
+        ruleInstaller.populateSubscriberGatewayRules(vSgVm, vSgs.keySet());
+    }
+
+    /**
+     * Adds virtual subscriber gateway to the system.
+     *
+     * @param vSgHost host virtual machine of this vSG
+     * @param vSgIp vSG ip address
+     * @param vSgMac vSG mac address
+     * @param serviceVlan service vlan
+     */
+    public void addVirtualSubscriberGateway(Host vSgHost, IpAddress vSgIp, MacAddress vSgMac, String serviceVlan) {
+        HostId hostId = HostId.hostId(vSgMac);
+        Host host = hostService.getHost(hostId);
+        if (host != null) {
+            log.debug("vSG with {} already exists", vSgMac.toString());
+            return;
+        }
+
+        log.info("vSG with IP({}) MAC({}) detected", vSgIp.toString(), vSgMac.toString());
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(S_TAG, serviceVlan);
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                vSgMac,
+                VlanId.NONE,
+                vSgHost.location(),
+                Sets.newHashSet(vSgIp),
+                annotations.build());
+
+        hostProvider.hostDetected(hostId, hostDesc, false);
     }
 
     /**
@@ -438,24 +476,24 @@
      * Returns public ip addresses of vSGs running inside a give vSG host.
      *
      * @param vSgHost vSG host
-     * @return set of ip address, or empty set
+     * @return map of ip and mac address, or empty map
      */
-    private Set<IpAddress> getSubscriberGatewayIps(Host vSgHost) {
+    private Map<IpAddress, MacAddress> getSubscriberGateways(Host vSgHost) {
         String vPortId = vSgHost.annotations().value(OPENSTACK_PORT_ID);
         String serviceVlan = vSgHost.annotations().value(S_TAG);
 
         OpenstackPort vPort = openstackService.port(vPortId);
         if (vPort == null) {
             log.warn("Failed to get OpenStack port {} for VM {}", vPortId, vSgHost.id());
-            return Sets.newHashSet();
+            return Maps.newHashMap();
         }
 
         if (!serviceVlan.equals(getServiceVlan(vPort))) {
             log.error("Host({}) s-tag does not match with vPort s-tag", vSgHost.id());
-            return Sets.newHashSet();
+            return Maps.newHashMap();
         }
 
-        return vPort.allowedAddressPairs().keySet();
+        return vPort.allowedAddressPairs();
     }
 
     /**
@@ -485,6 +523,11 @@
      */
     private void serviceVmAdded(Host host) {
         String vNetId = host.annotations().value(SERVICE_ID);
+        if (vNetId == null) {
+            // ignore this host, it not a VM we injected or a vSG
+            return;
+        }
+
         OpenstackNetwork vNet = openstackService.network(vNetId);
         if (vNet == null) {
             log.warn("Failed to get OpenStack network {} for VM {}({}).",
@@ -509,19 +552,28 @@
         } else {
             // TODO check if the service needs an update on its group buckets after done CORD-433
             ruleInstaller.updateServiceGroup(service);
-            arpProxy.addServiceIp(service.serviceIp());
+            arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
 
             // sends gratuitous ARP here for the case of adding existing VMs
             // when ONOS or cordvtn app is restarted
-            arpProxy.sendGratuitousArp(service.serviceIp(), gatewayMac, Sets.newHashSet(host));
+            arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(host));
         }
 
         registerDhcpLease(host, service);
         ruleInstaller.populateBasicConnectionRules(host, getTunnelIp(host), vNet);
 
-        if (host.annotations().value(S_TAG) != null) {
+        String serviceVlan = host.annotations().value(S_TAG);
+        if (serviceVlan != null) {
             log.debug("vSG VM detected {}", host.id());
-            ruleInstaller.populateSubscriberGatewayRules(host, getSubscriberGatewayIps(host));
+            Map<IpAddress, MacAddress> vSgs = getSubscriberGateways(host);
+            vSgs.entrySet().stream()
+                    .forEach(entry -> addVirtualSubscriberGateway(
+                            host,
+                            entry.getKey(),
+                            entry.getValue(),
+                            serviceVlan));
+
+            ruleInstaller.populateSubscriberGatewayRules(host, vSgs.keySet());
         }
     }
 
@@ -566,7 +618,7 @@
             ruleInstaller.updateServiceGroup(service);
 
             if (getHostsWithOpenstackNetwork(vNet).isEmpty()) {
-                arpProxy.removeServiceIp(service.serviceIp());
+                arpProxy.removeGateway(service.serviceIp());
             }
         }
     }
@@ -575,14 +627,17 @@
      * Sets service network gateway MAC address and sends out gratuitous ARP to all
      * VMs to update the gateway MAC address.
      *
-     * @param mac mac address
+     * @param newMac mac address to update
      */
-    private void setServiceGatewayMac(MacAddress mac) {
-        if (mac != null && !mac.equals(gatewayMac)) {
-            gatewayMac = mac;
-            log.debug("Set service gateway MAC address to {}", gatewayMac.toString());
+    private void setPrivateGatewayMac(MacAddress newMac) {
+        if (newMac == null || newMac.equals(privateGatewayMac)) {
+            // no updates, do nothing
+            return;
         }
 
+        privateGatewayMac = newMac;
+        log.debug("Set service gateway MAC address to {}", privateGatewayMac.toString());
+
         // TODO get existing service list from XOS and replace the loop below
         Set<String> vNets = Sets.newHashSet();
         hostService.getHosts().forEach(host -> vNets.add(host.annotations().value(SERVICE_ID)));
@@ -591,15 +646,29 @@
         vNets.stream().forEach(vNet -> {
             CordService service = getCordService(CordServiceId.of(vNet));
             if (service != null) {
-                arpProxy.sendGratuitousArp(
-                        service.serviceIp(),
-                        gatewayMac,
-                        service.hosts().keySet());
+                arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
+                arpProxy.sendGratuitousArpForGateway(service.serviceIp(), service.hosts().keySet());
             }
         });
     }
 
     /**
+     * Sets public gateway MAC address.
+     *
+     * @param publicGateways gateway ip and mac address pairs
+     */
+    private void setPublicGatewayMac(Map<IpAddress, MacAddress> publicGateways) {
+        publicGateways.entrySet()
+                .stream()
+                .forEach(entry -> {
+                    arpProxy.addGateway(entry.getKey(), entry.getValue());
+                    log.debug("Added public gateway IP {}, MAC {}",
+                              entry.getKey().toString(), entry.getValue().toString());
+                });
+        // TODO notice gateway MAC change to VMs holds this gateway IP
+    }
+
+    /**
      * Updates configurations.
      */
     private void readConfiguration() {
@@ -609,7 +678,8 @@
             return;
         }
 
-        setServiceGatewayMac(config.gatewayMac());
+        setPrivateGatewayMac(config.privateGatewayMac());
+        setPublicGatewayMac(config.publicGateways());
    }
 
     private class InternalHostListener implements HostListener {
@@ -644,7 +714,7 @@
                 return;
             }
 
-            arpProxy.processArpPacket(context, ethPacket, gatewayMac);
+            arpProxy.processArpPacket(context, ethPacket);
         }
     }