Adding a component config to take down single homed host ports when all uplinks are lost.

In addition:
   - handle port updates that may be lost if mastership changes at same time
   - fix javadoc in DeviceService

Change-Id: I032909e8ab9564cf9c978b1d66abd3ab32c814d7
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index e8f7019..8b83f24 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -1224,6 +1224,7 @@
         private MastershipEvent me;
         private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
         private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
+        private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
 
         MasterChange(DeviceId devId, MastershipEvent me) {
             this.devId = devId;
@@ -1261,12 +1262,16 @@
                 return;
             }
 
+            long lepe = Instant.now().toEpochMilli()
+                    - srManager.lastEdgePortEvent.toEpochMilli();
+            boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
+
             // if it gets here, then mastership change is likely due to onos
             // instance failure, or network partition in onos cluster
             // normally a mastership change like this does not require re-programming
             // but if topology changes happen at the same time then we may miss events
             if (!isRoutingStable() && clusterEvent) {
-                log.warn("Mastership changed for dev: {}/{} while programming "
+                log.warn("Mastership changed for dev: {}/{} while programming route-paths "
                         + "due to clusterEvent {} ms ago .. attempting full reroute",
                          devId, me.roleInfo(), lce);
                 if (srManager.mastershipService.isLocalMaster(devId)) {
@@ -1278,6 +1283,19 @@
                 // XXX right now we have no fine-grained way to only make changes
                 // for the route paths affected by this device.
                 populateAllRoutingRules();
+
+            } else if (edgePortEvent && clusterEvent) {
+                log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
+                        + "while edge-port event happened {} ms ago "
+                        + " .. reprogramming all edge-ports",
+                         devId, me.roleInfo(), lce, lepe);
+                if (shouldProgram(devId)) {
+                    srManager.deviceService.getPorts(devId).stream()
+                        .filter(p -> srManager.interfaceService
+                                .isConfigured(new ConnectPoint(devId, p.number())))
+                        .forEach(p -> srManager.processPortUpdated(devId, p));
+                }
+
             } else {
                 log.debug("Stable route-paths .. full reroute not attempted for "
                         + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 5fdbf69..6b49215 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -76,10 +76,10 @@
     private void processHostAdded(Host host) {
         host.locations().forEach(location -> processHostAddedAtLocation(host, location));
         // ensure dual-homed host locations have viable uplinks
-        if (host.locations().size() > 1) {
+        if (host.locations().size() > 1 || srManager.singleHomedDown) {
             host.locations().forEach(loc -> {
                 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
-                    srManager.linkHandler.checkUplinksForDualHomedHosts(loc);
+                    srManager.linkHandler.checkUplinksForHost(loc);
                 }
             });
         }
@@ -104,7 +104,7 @@
         // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
         srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
             if (host.locations().stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
-                srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
+                srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
                     // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                     //       when the host is untagged
                     VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
@@ -140,7 +140,7 @@
 
             // Also remove redirection flows on the pair device if exists.
             Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
-            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
+            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
             if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
                 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                 //       when the host is untagged
@@ -182,7 +182,7 @@
             // Redirect the flows to pair link if configured
             // Note: Do not continue removing any rule
             Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
-            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
+            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(prevLocation.deviceId());
             if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
                     .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
                 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
@@ -254,10 +254,10 @@
         });
 
         // ensure dual-homed host locations have viable uplinks
-        if (newLocations.size() > prevLocations.size()) {
+        if (newLocations.size() > prevLocations.size() || srManager.singleHomedDown) {
             newLocations.forEach(loc -> {
                 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
-                    srManager.linkHandler.checkUplinksForDualHomedHosts(loc);
+                    srManager.linkHandler.checkUplinksForHost(loc);
                 }
             });
         }
@@ -290,7 +290,7 @@
                     Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
                     Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
 
-                    srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
+                    srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
                         // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                         //       when the host is untagged
                         VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
@@ -319,7 +319,7 @@
      * @param cp connect point
      */
     void processPortUp(ConnectPoint cp) {
-        if (cp.port().equals(srManager.getPairLocalPorts(cp.deviceId()).orElse(null))) {
+        if (cp.port().equals(srManager.getPairLocalPort(cp.deviceId()).orElse(null))) {
             return;
         }
         if (srManager.activeProbing) {
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/LinkHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/LinkHandler.java
index 23caadc..5e41dc5 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/LinkHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/LinkHandler.java
@@ -23,6 +23,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.HostLocation;
@@ -160,7 +161,7 @@
 
             if (srManager.mastershipService.isLocalMaster(ulink.src().deviceId())) {
                 // handle edge-ports for dual-homed hosts
-                updateDualHomedHostPorts(ulink, true);
+                updateHostPorts(ulink, true);
 
                 // It's possible that linkUp causes no route-path change as ECMP graph does
                 // not change if the link is a parallel link (same src-dst as
@@ -207,7 +208,7 @@
         updateSeenLink(link, false);
         // handle edge-ports for dual-homed hosts
         if (srManager.mastershipService.isLocalMaster(link.src().deviceId())) {
-            updateDualHomedHostPorts(link, false);
+            updateHostPorts(link, false);
         }
 
         // device availability check helps to ensure that multiple link-removed
@@ -329,13 +330,15 @@
 
     /**
      * Administratively enables or disables edge ports if the link that was
-     * added or removed was the only uplink port from an edge device. Only edge
-     * ports that belong to dual-homed hosts are considered.
+     * added or removed was the only uplink port from an edge device. Edge ports
+     * that belong to dual-homed hosts are always processed. In addition,
+     * single-homed host ports are optionally processed depending on the
+     * singleHomedDown property.
      *
      * @param link the link to be processed
      * @param added true if link was added, false if link was removed
      */
-    private void updateDualHomedHostPorts(Link link, boolean added) {
+    private void updateHostPorts(Link link, boolean added) {
         if (added) {
             DeviceConfiguration devConfig = srManager.deviceConfiguration;
             try {
@@ -361,20 +364,38 @@
                 return;
             }
             // find dual homed hosts on this dev to disable
-            Set<PortNumber> dhp = srManager.hostHandler
-                    .getDualHomedHostPorts(link.src().deviceId());
-            log.warn("Link src {} -->dst {} removed was the last uplink, "
-                    + "disabling  dual homed ports:  {}", link.src().deviceId(),
-                     link.dst().deviceId(), (dhp.isEmpty()) ? "no ports" : dhp);
-            dhp.forEach(pnum -> srManager.deviceAdminService
-                        .changePortState(link.src().deviceId(), pnum, false));
-            if (!dhp.isEmpty()) {
+            DeviceId dev = link.src().deviceId();
+            Set<PortNumber> dp = srManager.hostHandler
+                    .getDualHomedHostPorts(dev);
+            log.warn("Link src {} --> dst {} removed was the last uplink, "
+                    + "disabling  dual homed ports:  {}", dev,
+                     link.dst().deviceId(), (dp.isEmpty()) ? "no ports" : dp);
+            dp.forEach(pnum -> srManager.deviceAdminService
+                        .changePortState(dev, pnum, false));
+            if (srManager.singleHomedDown) {
+                // get all configured ports and down them if they haven't already
+                // been downed
+                srManager.deviceService.getPorts(dev).stream()
+                    .filter(p -> p.isEnabled() && !dp.contains(p.number()))
+                    .filter(p -> srManager.interfaceService
+                            .isConfigured(new ConnectPoint(dev, p.number())))
+                    .filter(p -> !srManager.deviceConfiguration
+                            .isPairLocalPort(dev, p.number()))
+                    .forEach(p -> {
+                        log.warn("Last uplink gone src {} -> dst {} .. removing "
+                                + "configured port {}", p.number());
+                        srManager.deviceAdminService
+                            .changePortState(dev, p.number(), false);
+                        dp.add(p.number());
+                    });
+            }
+            if (!dp.isEmpty()) {
                 // update global store
-                Set<PortNumber> p = downedPortStore.get(link.src().deviceId());
+                Set<PortNumber> p = downedPortStore.get(dev);
                 if (p == null) {
-                    p = dhp;
+                    p = dp;
                 } else {
-                    p.addAll(dhp);
+                    p.addAll(dp);
                 }
                 downedPortStore.put(link.src().deviceId(), p);
             }
@@ -632,11 +653,12 @@
 
     /**
      * Administratively disables the host location switchport if the edge device
-     * has no viable uplinks.
+     * has no viable uplinks. The caller needs to determine if such behavior is
+     * desired for the single or dual-homed host.
      *
-     * @param loc one of the locations of the dual-homed host
+     * @param loc the host location
      */
-    void checkUplinksForDualHomedHosts(HostLocation loc) {
+    void checkUplinksForHost(HostLocation loc) {
         try {
             for (Link l : srManager.linkService.getDeviceLinks(loc.deviceId())) {
                 if (srManager.deviceConfiguration.isEdgeDevice(l.dst().deviceId())
@@ -651,8 +673,7 @@
                     + "config " + e.getMessage());
             return;
         }
-        log.warn("Dual homed host location {} has no valid uplinks; "
-                + "disabling  dual homed port", loc);
+        log.warn("Host location {} has no valid uplinks disabling port", loc);
         srManager.deviceAdminService.changePortState(loc.deviceId(), loc.port(),
                                                      false);
         Set<PortNumber> p = downedPortStore.get(loc.deviceId());
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index 9f97987..d930923 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -213,7 +213,7 @@
 
                 // Also remove redirection flows on the pair device if exists.
                 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
-                Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
+                Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
                 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
                     // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                     //       when the host is untagged
@@ -245,7 +245,7 @@
                 // Redirect the flows to pair link if configured
                 // Note: Do not continue removing any rule
                 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
-                Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
+                Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(prevLocation.deviceId());
                 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
                         .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
                     // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 06b650d..9774a61 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -1193,7 +1193,7 @@
             srManager.flowObjectiveService.forward(deviceId, obj);
         });
 
-        srManager.getPairLocalPorts(deviceId).ifPresent(port -> {
+        srManager.getPairLocalPort(deviceId).ifPresent(port -> {
             ForwardingObjective pairFwdObj;
             // Do not punt ARP packets from pair port
             pairFwdObj = arpFwdObjective(port, false, PacketPriority.CONTROL.priorityValue() + 1)
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 0322201..c12918b 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -226,6 +226,11 @@
             label = "Enable active probing to discover dual-homed hosts.")
     boolean activeProbing = true;
 
+    @Property(name = "singleHomedDown", boolValue = false,
+            label = "Enable administratively taking down single-homed hosts "
+                    + "when all uplinks are gone")
+    boolean singleHomedDown = false;
+
     ArpHandler arpHandler = null;
     IcmpHandler icmpHandler = null;
     IpHandler ipHandler = null;
@@ -342,6 +347,8 @@
      */
     public static final VlanId INTERNAL_VLAN = VlanId.vlanId((short) 4094);
 
+    Instant lastEdgePortEvent = Instant.EPOCH;
+
     @Activate
     protected void activate(ComponentContext context) {
         appId = coreService.registerApplication(APP_NAME);
@@ -540,11 +547,29 @@
 
         String strActiveProving = Tools.get(properties, "activeProbing");
         boolean expectActiveProbing = Boolean.parseBoolean(strActiveProving);
-
         if (expectActiveProbing != activeProbing) {
             activeProbing = expectActiveProbing;
             log.info("{} active probing", activeProbing ? "Enabling" : "Disabling");
         }
+
+        String strSingleHomedDown = Tools.get(properties, "singleHomedDown");
+        boolean expectSingleHomedDown = Boolean.parseBoolean(strSingleHomedDown);
+        if (expectSingleHomedDown != singleHomedDown) {
+            singleHomedDown = expectSingleHomedDown;
+            log.info("{} downing of single homed hosts for lost uplinks",
+                     singleHomedDown ? "Enabling" : "Disabling");
+            if (singleHomedDown && linkHandler != null) {
+                hostService.getHosts().forEach(host -> host.locations()
+                        .forEach(loc -> {
+                            if (interfaceService.isConfigured(loc)) {
+                                linkHandler.checkUplinksForHost(loc);
+                            }
+                        }));
+            } else {
+                log.warn("Disabling singleHomedDown does not re-enable already "
+                        + "downed ports for single-homed hosts");
+            }
+        }
     }
 
     @Override
@@ -879,7 +904,7 @@
      * @param deviceId device ID
      * @return optional pair device ID. Might be empty if pair device is not configured
      */
-    Optional<PortNumber> getPairLocalPorts(DeviceId deviceId) {
+    public Optional<PortNumber> getPairLocalPort(DeviceId deviceId) {
         SegmentRoutingDeviceConfig deviceConfig =
                 cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class);
         return Optional.ofNullable(deviceConfig).map(SegmentRoutingDeviceConfig::pairLocalPort);
@@ -1087,7 +1112,7 @@
                              event.subject(),
                              ((DeviceEvent) event).port(),
                              event.type());
-                    processPortUpdated(((Device) event.subject()),
+                    processPortUpdatedInternal(((Device) event.subject()),
                                        ((DeviceEvent) event).port());
                 } else if (event.type() == TopologyEvent.Type.TOPOLOGY_CHANGED) {
                     // Process topology event, needed for all modules relying on
@@ -1298,73 +1323,88 @@
                 .forEach(entry -> dsNextObjStore.remove(entry.getKey()));
     }
 
-    private void processPortUpdated(Device device, Port port) {
+    private void processPortUpdatedInternal(Device device, Port port) {
         if (deviceConfiguration == null || !deviceConfiguration.isConfigured(device.id())) {
             log.warn("Device configuration uploading. Not handling port event for"
                     + "dev: {} port: {}", device.id(), port.number());
             return;
         }
 
+        if (interfaceService.isConfigured(new ConnectPoint(device.id(), port.number()))) {
+            lastEdgePortEvent = Instant.now();
+        }
+
         if (!mastershipService.isLocalMaster(device.id()))  {
             log.debug("Not master for dev:{} .. not handling port updated event"
                     + "for port {}", device.id(), port.number());
             return;
         }
+        processPortUpdated(device.id(), port);
+    }
 
+    /**
+     * Adds or remove filtering rules for the given switchport. If switchport is
+     * an edge facing port, additionally handles host probing and broadcast
+     * rules. Must be called by local master of device.
+     *
+     * @param deviceId the device identifier
+     * @param port the port to update
+     */
+    void processPortUpdated(DeviceId deviceId, Port port) {
         // first we handle filtering rules associated with the port
         if (port.isEnabled()) {
             log.info("Switchport {}/{} enabled..programming filters",
-                     device.id(), port.number());
-            routingRulePopulator.processSinglePortFilters(device.id(), port.number(), true);
+                     deviceId, port.number());
+            routingRulePopulator.processSinglePortFilters(deviceId, port.number(), true);
         } else {
             log.info("Switchport {}/{} disabled..removing filters",
-                     device.id(), port.number());
-            routingRulePopulator.processSinglePortFilters(device.id(), port.number(), false);
+                     deviceId, port.number());
+            routingRulePopulator.processSinglePortFilters(deviceId, port.number(), false);
         }
 
         // portUpdated calls are for ports that have gone down or up. For switch
         // to switch ports, link-events should take care of any re-routing or
         // group editing necessary for port up/down. Here we only process edge ports
         // that are already configured.
-        ConnectPoint cp = new ConnectPoint(device.id(), port.number());
+        ConnectPoint cp = new ConnectPoint(deviceId, port.number());
         VlanId untaggedVlan = getUntaggedVlanId(cp);
         VlanId nativeVlan = getNativeVlanId(cp);
         Set<VlanId> taggedVlans = getTaggedVlanId(cp);
 
         if (untaggedVlan == null && nativeVlan == null && taggedVlans.isEmpty()) {
             log.debug("Not handling port updated event for non-edge port (unconfigured) "
-                    + "dev/port: {}/{}", device.id(), port.number());
+                    + "dev/port: {}/{}", deviceId, port.number());
             return;
         }
         if (untaggedVlan != null) {
-            processEdgePort(device, port, untaggedVlan, true);
+            processEdgePort(deviceId, port, untaggedVlan, true);
         }
         if (nativeVlan != null) {
-            processEdgePort(device, port, nativeVlan, true);
+            processEdgePort(deviceId, port, nativeVlan, true);
         }
         if (!taggedVlans.isEmpty()) {
-            taggedVlans.forEach(tag -> processEdgePort(device, port, tag, false));
+            taggedVlans.forEach(tag -> processEdgePort(deviceId, port, tag, false));
         }
     }
 
-    private void processEdgePort(Device device, Port port, VlanId vlanId,
+    private void processEdgePort(DeviceId deviceId, Port port, VlanId vlanId,
                                  boolean popVlan) {
         boolean portUp = port.isEnabled();
         if (portUp) {
-            log.info("Device:EdgePort {}:{} is enabled in vlan: {}", device.id(),
+            log.info("Device:EdgePort {}:{} is enabled in vlan: {}", deviceId,
                      port.number(), vlanId);
-            hostHandler.processPortUp(new ConnectPoint(device.id(), port.number()));
+            hostHandler.processPortUp(new ConnectPoint(deviceId, port.number()));
         } else {
-            log.info("Device:EdgePort {}:{} is disabled in vlan: {}", device.id(),
+            log.info("Device:EdgePort {}:{} is disabled in vlan: {}", deviceId,
                      port.number(), vlanId);
         }
 
-        DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id());
+        DefaultGroupHandler groupHandler = groupHandlerMap.get(deviceId);
         if (groupHandler != null) {
             groupHandler.processEdgePort(port.number(), vlanId, popVlan, portUp);
         } else {
             log.warn("Group handler not found for dev:{}. Not handling edge port"
-                    + " {} event for port:{}", device.id(),
+                    + " {} event for port:{}", deviceId,
                     (portUp) ? "UP" : "DOWN", port.number());
         }
     }
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 0282fa4..626109a 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -725,4 +725,7 @@
         }
     }
 
+    public boolean isPairLocalPort(DeviceId devId, PortNumber pnum) {
+        return pnum.equals(srManager.getPairLocalPort(devId).orElse(null));
+    }
 }
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockLinkHandler.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockLinkHandler.java
index 2819c9d..fe5df5d 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockLinkHandler.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockLinkHandler.java
@@ -29,7 +29,7 @@
     }
 
     @Override
-    void checkUplinksForDualHomedHosts(HostLocation loc) {
+    void checkUplinksForHost(HostLocation loc) {
         // currently does nothing - can be extended to be a useful mock when
         // implementing unit tests for link handling
     }
diff --git a/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java b/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java
index 2e42254..bd1602f 100644
--- a/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java
+++ b/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java
@@ -421,6 +421,23 @@
                     .filter(intf -> intf.ipAddressesList().contains(ip))
                     .collect(Collectors.toSet());
         }
+
+        @Override
+        public boolean isConfigured(ConnectPoint connectPoint) {
+            for (Interface intf : AVAILABLE_INTERFACES) {
+                if (!intf.connectPoint().equals(connectPoint)) {
+                    continue;
+                }
+                if (!intf.ipAddressesList().isEmpty()
+                        || intf.vlan() != VlanId.NONE
+                        || intf.vlanNative() != VlanId.NONE
+                        || intf.vlanUntagged() != VlanId.NONE
+                        || !intf.vlanTagged().isEmpty()) {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/device/DeviceService.java b/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
index f86a98a..86d9025 100644
--- a/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
+++ b/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
@@ -187,11 +187,11 @@
 
 
     /**
-     * Indicates how long ago the device connected or disconnected from this
-     * controller instance as a time offset.
+     * Indicates the time at which the given device connected or disconnected
+     * from this controller instance.
      *
      * @param deviceId device identifier
-     * @return time offset in miliseconds
+     * @return time offset in miliseconds from Epoch
      */
     long getLastUpdatedInstant(DeviceId deviceId);
 
diff --git a/core/api/src/main/java/org/onosproject/net/intf/InterfaceService.java b/core/api/src/main/java/org/onosproject/net/intf/InterfaceService.java
index 3e2f742..09743df 100644
--- a/core/api/src/main/java/org/onosproject/net/intf/InterfaceService.java
+++ b/core/api/src/main/java/org/onosproject/net/intf/InterfaceService.java
@@ -87,4 +87,13 @@
      * @return a set of interfaces
      */
     Set<Interface> getMatchingInterfaces(IpAddress ip);
+
+    /**
+     * Returns true if given connectPoint has an IP address or vlan configured
+     * on any of its interfaces.
+     *
+     * @param connectPoint the port on a device
+     * @return true if connectpoint has a configured interface
+     */
+    boolean isConfigured(ConnectPoint connectPoint);
 }
diff --git a/core/api/src/test/java/org/onosproject/net/intf/InterfaceServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/intf/InterfaceServiceAdapter.java
index 8eb2214..b5f1673 100644
--- a/core/api/src/test/java/org/onosproject/net/intf/InterfaceServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/intf/InterfaceServiceAdapter.java
@@ -70,4 +70,9 @@
     public void removeListener(InterfaceListener listener) {
 
     }
+
+    @Override
+    public boolean isConfigured(ConnectPoint connectPoint) {
+        return false;
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/intf/impl/InterfaceManager.java b/core/net/src/main/java/org/onosproject/net/intf/impl/InterfaceManager.java
index 7a28bc48..b4e01b7 100644
--- a/core/net/src/main/java/org/onosproject/net/intf/impl/InterfaceManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intf/impl/InterfaceManager.java
@@ -168,6 +168,23 @@
                 .collect(collectingAndThen(toSet(), ImmutableSet::copyOf));
     }
 
+    @Override
+    public boolean isConfigured(ConnectPoint connectPoint) {
+        Set<Interface> intfs = interfaces.get(connectPoint);
+        if (intfs == null) {
+            return false;
+        }
+        for (Interface intf : intfs) {
+            if (!intf.ipAddressesList().isEmpty() || intf.vlan() != VlanId.NONE
+                    || intf.vlanNative() != VlanId.NONE
+                    || intf.vlanUntagged() != VlanId.NONE
+                    || !intf.vlanTagged().isEmpty()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void updateInterfaces(InterfaceConfig intfConfig) {
         try {
             Set<Interface> old = interfaces.put(intfConfig.subject(),
@@ -268,4 +285,5 @@
             }
         }
     }
+
 }
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index a1d3d4d..f7b2f77 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -1190,6 +1190,7 @@
             return null;
         }
 
+        @Override
         public Set<Interface> getInterfacesByIp(IpAddress ip) {
             if (ip.equals(GW_IFACE_ADDR.ipAddress())) {
                 return ImmutableSet.of(GW_IFACE);
@@ -1222,5 +1223,10 @@
         public void removeListener(InterfaceListener listener) {
 
         }
+
+        @Override
+        public boolean isConfigured(ConnectPoint connectPoint) {
+            return false;
+        }
     }
 }