[CORD-1628] DHCP relay app configuration change for multiple servers

Change-Id: I13747d8e6451658dfd9344c428b6aad11bf5af62
(cherry picked from commit 483ac6f0689220425061056e748230efe6d8a1b2)
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index 0305cd8..deeaa36 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -17,8 +17,11 @@
 
 package org.onosproject.dhcprelay;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
@@ -36,8 +39,11 @@
 import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.dhcp.DhcpRelayAgentOption;
 import org.onosproject.dhcprelay.api.DhcpHandler;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.routeservice.Route;
@@ -100,6 +106,8 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected HostService hostService;
 
+    private InternalHostListener hostListener = new InternalHostListener();
+
     private Ip4Address dhcpServerIp = null;
     // dhcp server may be connected directly to the SDN network or
     // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
@@ -110,6 +118,18 @@
     private VlanId dhcpConnectVlan = null;
     private Ip4Address dhcpGatewayIp = null;
 
+    @Activate
+    protected void activate() {
+        hostService.addListener(hostListener);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        hostService.removeListener(hostListener);
+        this.dhcpConnectMac = null;
+        this.dhcpConnectVlan = null;
+    }
+
     @Override
     public void setDhcpServerIp(IpAddress dhcpServerIp) {
         checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
@@ -160,6 +180,64 @@
     }
 
     @Override
+    public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+        if (configs.size() == 0) {
+            // no config to update
+            return;
+        }
+
+        // TODO: currently we pick up first DHCP server config.
+        // Will use other server configs in the future for HA.
+        DhcpServerConfig serverConfig = configs.iterator().next();
+        checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
+                   "Connect point not exists");
+        checkState(serverConfig.getDhcpServerIp4().isPresent(),
+                   "IP of DHCP server not exists");
+        Ip4Address oldServerIp = this.dhcpServerIp;
+        Ip4Address oldGatewayIp = this.dhcpGatewayIp;
+
+        // stop monitoring gateway or server
+        if (oldGatewayIp != null) {
+            hostService.stopMonitoringIp(oldGatewayIp);
+        } else if (oldServerIp != null) {
+            hostService.stopMonitoringIp(oldServerIp);
+        }
+
+        this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
+        this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
+        this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
+
+        // reset server mac and vlan
+        this.dhcpConnectMac = null;
+        this.dhcpConnectVlan = null;
+
+        log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
+        log.info("DHCP server IP: " + this.dhcpServerIp);
+
+        IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
+        String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
+
+        if (ipToProbe == null) {
+            log.warn("Server IP not set, can't probe it");
+            return;
+        }
+
+        log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+        hostService.startMonitoringIp(ipToProbe);
+
+        Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+        if (!hosts.isEmpty()) {
+            Host host = hosts.iterator().next();
+            this.dhcpConnectVlan = host.vlan();
+            this.dhcpConnectMac = host.mac();
+        }
+    }
+
+    @Override
+    public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+        log.warn("Indirect config feature for DHCPv4 handler not implement yet");
+    }
+
     public void processDhcpPacket(PacketContext context, BasePacket payload) {
         checkNotNull(payload, "DHCP payload can't be null");
         checkState(payload instanceof DHCP, "Payload is not a DHCP");
@@ -813,4 +891,96 @@
         }
         packetService.emit(o);
     }
+
+    class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            switch (event.type()) {
+                case HOST_ADDED:
+                case HOST_UPDATED:
+                    hostUpdated(event.subject());
+                    break;
+                case HOST_REMOVED:
+                    hostRemoved(event.subject());
+                    break;
+                case HOST_MOVED:
+                    hostMoved(event.subject());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Handle host move.
+     * If the host DHCP server or gateway and it moved to the location different
+     * to user configured, unsets the connect mac and vlan
+     *
+     * @param host the host
+     */
+    private void hostMoved(Host host) {
+        if (this.dhcpServerConnectPoint == null) {
+            return;
+        }
+        if (this.dhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
+                    !host.locations().contains(this.dhcpServerConnectPoint)) {
+                this.dhcpConnectMac = null;
+                this.dhcpConnectVlan = null;
+            }
+            return;
+        }
+        if (this.dhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.dhcpServerIp) &&
+                    !host.locations().contains(this.dhcpServerConnectPoint)) {
+                this.dhcpConnectMac = null;
+                this.dhcpConnectVlan = null;
+            }
+        }
+    }
+
+    /**
+     * Handle host updated.
+     * If the host is DHCP server or gateway, update connect mac and vlan.
+     *
+     * @param host the host
+     */
+    private void hostUpdated(Host host) {
+        if (this.dhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
+                this.dhcpConnectMac = host.mac();
+                this.dhcpConnectVlan = host.vlan();
+            }
+            return;
+        }
+        if (this.dhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.dhcpServerIp)) {
+                this.dhcpConnectMac = host.mac();
+                this.dhcpConnectVlan = host.vlan();
+            }
+        }
+    }
+
+    /**
+     * Handle host removed.
+     * If the host is DHCP server or gateway, unset connect mac and vlan.
+     *
+     * @param host the host
+     */
+    private void hostRemoved(Host host) {
+        if (this.dhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
+                this.dhcpConnectMac = null;
+                this.dhcpConnectVlan = null;
+            }
+            return;
+        }
+        if (this.dhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.dhcpServerIp)) {
+                this.dhcpConnectMac = null;
+                this.dhcpConnectVlan = null;
+            }
+        }
+    }
 }