CORD-60 Support dynamic vSG creation/deletion

We no longer need to configure /32 IP in interfaces.
SR will push a per-host route when discovering a host
with IP address(es) that does not belong to configured subnet.

Also includes:
- HostHandler refactoring

Change-Id: Ic1ad42d1ccdfee32be85f49e6fc94d9026000ffc
diff --git a/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index e9a4706..05bba2d 100644
--- a/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -16,13 +16,15 @@
 
 package org.onosproject.segmentrouting;
 
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
-import org.onosproject.core.CoreService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostLocation;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -46,7 +48,6 @@
 public class HostHandler {
     private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
     private final SegmentRoutingManager srManager;
-    private CoreService coreService;
     private HostService hostService;
     private FlowObjectiveService flowObjectiveService;
 
@@ -57,7 +58,6 @@
      */
     public HostHandler(SegmentRoutingManager srManager) {
         this.srManager = srManager;
-        coreService = srManager.coreService;
         hostService = srManager.hostService;
         flowObjectiveService = srManager.flowObjectiveService;
     }
@@ -65,45 +65,204 @@
     protected void readInitialHosts(DeviceId devId) {
         hostService.getHosts().forEach(host -> {
             DeviceId deviceId = host.location().deviceId();
+            // The host does not attach to this device
             if (!deviceId.equals(devId)) {
-                // not an attached host to this device
                 return;
             }
-            MacAddress mac = host.mac();
-            VlanId vlanId = host.vlan();
-            PortNumber port = host.location().port();
-            Set<IpAddress> ips = host.ipAddresses();
-            log.debug("Attached Host {}/{} is added at {}:{}", mac, vlanId,
-                      deviceId, port);
+            processHostAddedEventInternal(host.mac(), host.vlan(),
+                    host.location(), host.ipAddresses());
+        });
+    }
 
+    protected void processHostAddedEvent(HostEvent event) {
+        processHostAddedEventInternal(event.subject().mac(), event.subject().vlan(),
+                event.subject().location(), event.subject().ipAddresses());
+    }
+
+    private void processHostAddedEventInternal(MacAddress mac, VlanId vlanId,
+            HostLocation location, Set<IpAddress> ips) {
+        DeviceId deviceId = location.deviceId();
+        PortNumber port = location.port();
+        log.info("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port);
+
+        if (!srManager.deviceConfiguration.suppressHost().contains(location)) {
             // Populate bridging table entry
+            log.debug("Populate L2 table entry for host {} at {}:{}",
+                    mac, deviceId, port);
             ForwardingObjective.Builder fob =
-                    getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
+                    hostFwdObjBuilder(deviceId, mac, vlanId, port);
             if (fob == null) {
-                log.warn("Aborting host bridging & routing table entries due "
-                         + "to error for dev:{} host:{}", deviceId, host);
+                log.warn("Fail to create fwd obj for host {}/{}. Abort.", mac, vlanId);
                 return;
             }
             ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} populated", host),
+                    (objective) -> log.debug("Host rule for {}/{} populated", mac, vlanId),
                     (objective, error) ->
-                            log.warn("Failed to populate host rule for {}: {}", host, error));
+                            log.warn("Failed to populate host rule for {}/{}: {}", mac, vlanId, error));
             flowObjectiveService.forward(deviceId, fob.add(context));
 
-            // Populate IP table entry
             ips.forEach(ip -> {
+                // Populate IP table entry
                 if (ip.isIp4()) {
+                    addPerHostRoute(location, ip.getIp4Address());
                     srManager.routingRulePopulator.populateIpRuleForHost(
                             deviceId, ip.getIp4Address(), mac, port);
                 }
             });
-        });
+        }
     }
 
-    private ForwardingObjective.Builder getForwardingObjectiveBuilder(
+    protected void processHostRemoveEvent(HostEvent event) {
+        MacAddress mac = event.subject().mac();
+        VlanId vlanId = event.subject().vlan();
+        HostLocation location = event.subject().location();
+        DeviceId deviceId = location.deviceId();
+        PortNumber port = location.port();
+        Set<IpAddress> ips = event.subject().ipAddresses();
+        log.debug("Host {}/{} is removed from {}:{}", mac, vlanId, deviceId, port);
+
+        if (!srManager.deviceConfiguration.suppressHost()
+                .contains(new ConnectPoint(deviceId, port))) {
+            // Revoke bridging table entry
+            ForwardingObjective.Builder fob =
+                    hostFwdObjBuilder(deviceId, mac, vlanId, port);
+            if (fob == null) {
+                log.warn("Fail to create fwd obj for host {}/{}. Abort.", mac, vlanId);
+                return;
+            }
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("Host rule for {} revoked", event.subject()),
+                    (objective, error) ->
+                            log.warn("Failed to revoke host rule for {}: {}", event.subject(), error));
+            flowObjectiveService.forward(deviceId, fob.remove(context));
+
+            // Revoke IP table entry
+            ips.forEach(ip -> {
+                if (ip.isIp4()) {
+                    removePerHostRoute(location, ip.getIp4Address());
+                    srManager.routingRulePopulator.revokeIpRuleForHost(
+                            deviceId, ip.getIp4Address(), mac, port);
+                }
+            });
+        }
+    }
+
+    protected void processHostMovedEvent(HostEvent event) {
+        MacAddress mac = event.subject().mac();
+        VlanId vlanId = event.subject().vlan();
+        HostLocation prevLocation = event.prevSubject().location();
+        DeviceId prevDeviceId = prevLocation.deviceId();
+        PortNumber prevPort = prevLocation.port();
+        Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
+        HostLocation newLocation = event.subject().location();
+        DeviceId newDeviceId = newLocation.deviceId();
+        PortNumber newPort = newLocation.port();
+        Set<IpAddress> newIps = event.subject().ipAddresses();
+        log.debug("Host {}/{} is moved from {}:{} to {}:{}",
+                mac, vlanId, prevDeviceId, prevPort, newDeviceId, newPort);
+
+        if (!srManager.deviceConfiguration.suppressHost()
+                .contains(new ConnectPoint(prevDeviceId, prevPort))) {
+            // Revoke previous bridging table entry
+            ForwardingObjective.Builder prevFob =
+                    hostFwdObjBuilder(prevDeviceId, mac, vlanId, prevPort);
+            if (prevFob == null) {
+                log.warn("Fail to create fwd obj for host {}/{}. Abort.", mac, vlanId);
+                return;
+            }
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("Host rule for {} revoked", event.subject()),
+                    (objective, error) ->
+                            log.warn("Failed to revoke host rule for {}: {}", event.subject(), error));
+            flowObjectiveService.forward(prevDeviceId, prevFob.remove(context));
+
+            // Revoke previous IP table entry
+            prevIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    removePerHostRoute(prevLocation, ip.getIp4Address());
+                    srManager.routingRulePopulator.revokeIpRuleForHost(
+                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
+                }
+            });
+        }
+
+        if (!srManager.deviceConfiguration.suppressHost()
+                .contains(new ConnectPoint(newDeviceId, newPort))) {
+            // Populate new bridging table entry
+            ForwardingObjective.Builder newFob =
+                    hostFwdObjBuilder(newDeviceId, mac, vlanId, newPort);
+            if (newFob == null) {
+                log.warn("Fail to create fwd obj for host {}/{}. Abort.", mac, vlanId);
+                return;
+            }
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("Host rule for {} populated", event.subject()),
+                    (objective, error) ->
+                            log.warn("Failed to populate host rule for {}: {}", event.subject(), error));
+            flowObjectiveService.forward(newDeviceId, newFob.add(context));
+
+            // Populate new IP table entry
+            newIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    addPerHostRoute(newLocation, ip.getIp4Address());
+                    srManager.routingRulePopulator.populateIpRuleForHost(
+                            newDeviceId, ip.getIp4Address(), mac, newPort);
+                }
+            });
+        }
+    }
+
+    protected void processHostUpdatedEvent(HostEvent event) {
+        MacAddress mac = event.subject().mac();
+        VlanId vlanId = event.subject().vlan();
+        HostLocation prevLocation = event.prevSubject().location();
+        DeviceId prevDeviceId = prevLocation.deviceId();
+        PortNumber prevPort = prevLocation.port();
+        Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
+        HostLocation newLocation = event.subject().location();
+        DeviceId newDeviceId = newLocation.deviceId();
+        PortNumber newPort = newLocation.port();
+        Set<IpAddress> newIps = event.subject().ipAddresses();
+        log.debug("Host {}/{} is updated", mac, vlanId);
+
+        if (!srManager.deviceConfiguration.suppressHost()
+                .contains(new ConnectPoint(prevDeviceId, prevPort))) {
+            // Revoke previous IP table entry
+            prevIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    removePerHostRoute(prevLocation, ip.getIp4Address());
+                    srManager.routingRulePopulator.revokeIpRuleForHost(
+                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
+                }
+            });
+        }
+
+        if (!srManager.deviceConfiguration.suppressHost()
+                .contains(new ConnectPoint(newDeviceId, newPort))) {
+            // Populate new IP table entry
+            newIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    addPerHostRoute(newLocation, ip.getIp4Address());
+                    srManager.routingRulePopulator.populateIpRuleForHost(
+                            newDeviceId, ip.getIp4Address(), mac, newPort);
+                }
+            });
+        }
+    }
+
+    /**
+     * Generates the forwarding objective builder for the host rules.
+     *
+     * @param deviceId Device that host attaches to
+     * @param mac MAC address of the host
+     * @param vlanId VLAN ID of the host
+     * @param outport Port that host attaches to
+     * @return Forwarding objective builder
+     */
+    private ForwardingObjective.Builder hostFwdObjBuilder(
             DeviceId deviceId, MacAddress mac, VlanId vlanId,
             PortNumber outport) {
-        // Get assigned VLAN for the subnet
+        // Get assigned VLAN for the subnets
         VlanId outvlan = null;
         Ip4Prefix subnet = srManager.deviceConfiguration.getPortSubnet(deviceId, outport);
         if (subnet == null) {
@@ -115,10 +274,10 @@
         // match rule
         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
         sbuilder.matchEthDst(mac);
-            /*
-             * Note: for untagged packets, match on the assigned VLAN.
-             *       for tagged packets, match on its incoming VLAN.
-             */
+        /*
+         * Note: for untagged packets, match on the assigned VLAN.
+         *       for tagged packets, match on its incoming VLAN.
+         */
         if (vlanId.equals(VlanId.NONE)) {
             sbuilder.matchVlanId(outvlan);
         } else {
@@ -151,150 +310,38 @@
                 .makePermanent();
     }
 
-    protected void processHostAddedEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
-        DeviceId deviceId = event.subject().location().deviceId();
-        PortNumber port = event.subject().location().port();
-        Set<IpAddress> ips = event.subject().ipAddresses();
-        log.info("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port);
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(deviceId, port))) {
-            // Populate bridging table entry
-            log.debug("Populate L2 table entry for host {} at {}:{}",
-                    mac, deviceId, port);
-            ForwardingObjective.Builder fob =
-                    getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} populated", event.subject()),
-                    (objective, error) ->
-                            log.warn("Failed to populate host rule for {}: {}", event.subject(), error));
-            flowObjectiveService.forward(deviceId, fob.add(context));
-
-            // Populate IP table entry
-            ips.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.populateIpRuleForHost(
-                            deviceId, ip.getIp4Address(), mac, port);
-                }
-            });
+    /**
+     * Add per host route to subnet list and populate the flow rule if the host
+     * does not belong to the configured subnet.
+     *
+     * @param location location of the host being added
+     * @param ip IP address of the host being added
+     */
+    private void addPerHostRoute(ConnectPoint location, Ip4Address ip) {
+        Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortSubnet(
+                location.deviceId(), location.port());
+        if (portSubnet != null && !portSubnet.contains(ip)) {
+            Ip4Prefix ip4Prefix = ip.toIpPrefix().getIp4Prefix();
+            srManager.deviceConfiguration.addSubnet(location, ip4Prefix);
+            srManager.defaultRoutingHandler.populateSubnet(location,
+                    ImmutableSet.of(ip4Prefix));
         }
     }
 
-    protected void processHostRemoveEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
-        DeviceId deviceId = event.subject().location().deviceId();
-        PortNumber port = event.subject().location().port();
-        Set<IpAddress> ips = event.subject().ipAddresses();
-        log.debug("Host {}/{} is removed from {}:{}", mac, vlanId, deviceId, port);
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(deviceId, port))) {
-            // Revoke bridging table entry
-            ForwardingObjective.Builder fob =
-                    getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} revoked", event.subject()),
-                    (objective, error) ->
-                            log.warn("Failed to revoke host rule for {}: {}", event.subject(), error));
-            flowObjectiveService.forward(deviceId, fob.remove(context));
-
-            // Revoke IP table entry
-            ips.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.revokeIpRuleForHost(
-                            deviceId, ip.getIp4Address(), mac, port);
-                }
-            });
-        }
-    }
-
-    protected void processHostMovedEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
-        DeviceId prevDeviceId = event.prevSubject().location().deviceId();
-        PortNumber prevPort = event.prevSubject().location().port();
-        Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
-        DeviceId newDeviceId = event.subject().location().deviceId();
-        PortNumber newPort = event.subject().location().port();
-        Set<IpAddress> newIps = event.subject().ipAddresses();
-        log.debug("Host {}/{} is moved from {}:{} to {}:{}",
-                mac, vlanId, prevDeviceId, prevPort, newDeviceId, newPort);
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(prevDeviceId, prevPort))) {
-            // Revoke previous bridging table entry
-            ForwardingObjective.Builder prevFob =
-                    getForwardingObjectiveBuilder(prevDeviceId, mac, vlanId, prevPort);
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} revoked", event.subject()),
-                    (objective, error) ->
-                            log.warn("Failed to revoke host rule for {}: {}", event.subject(), error));
-            flowObjectiveService.forward(prevDeviceId, prevFob.remove(context));
-
-            // Revoke previous IP table entry
-            prevIps.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.revokeIpRuleForHost(
-                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
-                }
-            });
-        }
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(newDeviceId, newPort))) {
-            // Populate new bridging table entry
-            ForwardingObjective.Builder newFob =
-                    getForwardingObjectiveBuilder(newDeviceId, mac, vlanId, newPort);
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} populated", event.subject()),
-                    (objective, error) ->
-                            log.warn("Failed to populate host rule for {}: {}", event.subject(), error));
-            flowObjectiveService.forward(newDeviceId, newFob.add(context));
-
-            // Populate new IP table entry
-            newIps.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.populateIpRuleForHost(
-                            newDeviceId, ip.getIp4Address(), mac, newPort);
-                }
-            });
-        }
-    }
-
-    protected void processHostUpdatedEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
-        DeviceId prevDeviceId = event.prevSubject().location().deviceId();
-        PortNumber prevPort = event.prevSubject().location().port();
-        Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
-        DeviceId newDeviceId = event.subject().location().deviceId();
-        PortNumber newPort = event.subject().location().port();
-        Set<IpAddress> newIps = event.subject().ipAddresses();
-        log.debug("Host {}/{} is updated", mac, vlanId);
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(prevDeviceId, prevPort))) {
-            // Revoke previous IP table entry
-            prevIps.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.revokeIpRuleForHost(
-                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
-                }
-            });
-        }
-
-        if (!srManager.deviceConfiguration.suppressHost()
-                .contains(new ConnectPoint(newDeviceId, newPort))) {
-            // Populate new IP table entry
-            newIps.forEach(ip -> {
-                if (ip.isIp4()) {
-                    srManager.routingRulePopulator.populateIpRuleForHost(
-                            newDeviceId, ip.getIp4Address(), mac, newPort);
-                }
-            });
+    /**
+     * Remove per host route from subnet list and revoke the flow rule if the
+     * host does not belong to the configured subnet.
+     *
+     * @param location location of the host being removed
+     * @param ip IP address of the host being removed
+     */
+    private void removePerHostRoute(ConnectPoint location, Ip4Address ip) {
+        Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortSubnet(
+                location.deviceId(), location.port());
+        if (portSubnet != null && !portSubnet.contains(ip)) {
+            Ip4Prefix ip4Prefix = ip.toIpPrefix().getIp4Prefix();
+            srManager.deviceConfiguration.removeSubnet(location, ip4Prefix);
+            srManager.defaultRoutingHandler.revokeSubnet(ImmutableSet.of(ip4Prefix));
         }
     }
 }