Dual-homing probing improvements
(1) Active probing mechanism in the following two scenarios
(1-1) Probe all ports on the pair device within the same vlan (excluding the pair port) when the 1st location of a host is learnt
(1-2) Probe again when a device/port goes down and comes up again
* Introduce HostLocationProvingService
- DISCOVER mode: discover potential new locations
- VERIFY mode: verify old locations
* Can be enabled/disabled via component config
* Improve HostHandlerTest to test the probing behavior
(2) Fix an issue that redirection flow doesn't get installed after device re-connects
(3) Temporarily fix a race condition in HostHandler by adding a little bit delay
Change-Id: I33d3fe94a6ca491a88b8e06f65bef11447ead0bf
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 6ac25f3..1379e79 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -35,6 +35,7 @@
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
import org.onosproject.net.host.HostService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,6 +44,9 @@
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@@ -51,8 +55,9 @@
* Handles host-related events.
*/
public class HostHandler {
-
private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
+ static final int HOST_MOVED_DELAY_MS = 1000;
+
protected final SegmentRoutingManager srManager;
private HostService hostService;
private FlowObjectiveService flowObjectiveService;
@@ -71,7 +76,8 @@
protected void init(DeviceId devId) {
hostService.getHosts().forEach(host ->
host.locations().stream()
- .filter(location -> location.deviceId().equals(devId))
+ .filter(location -> location.deviceId().equals(devId) ||
+ location.deviceId().equals(srManager.getPairDeviceId(devId).orElse(null)))
.forEach(location -> processHostAddedAtLocation(host, location))
);
}
@@ -114,6 +120,10 @@
processBridgingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, false);
ips.forEach(ip -> processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId,
ip, false));
+
+ if (srManager.activeProbing) {
+ probe(host, location, pairDeviceId, pairRemotePort);
+ }
});
}
});
@@ -162,8 +172,23 @@
Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Set<HostLocation> newLocations = event.subject().locations();
Set<IpAddress> newIps = event.subject().ipAddresses();
- log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
+ // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
+ // The permanent solution would be introducing CompletableFuture and wait for it
+ if (prevLocations.size() == 1 && newLocations.size() == 2) {
+ log.debug("Delay event handling when host {}/{} moves from 1 to 2 locations", hostMac, hostVlanId);
+ ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+ executorService.schedule(() ->
+ processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps),
+ HOST_MOVED_DELAY_MS, TimeUnit.MILLISECONDS);
+ } else {
+ processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps);
+ }
+ }
+
+ private void processHostMoved(MacAddress hostMac, VlanId hostVlanId, Set<HostLocation> prevLocations,
+ Set<IpAddress> prevIps, Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+ log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
.collect(Collectors.toSet());
@@ -254,11 +279,12 @@
}
void processHostUpdatedEvent(HostEvent event) {
- MacAddress hostMac = event.subject().mac();
- VlanId hostVlanId = event.subject().vlan();
- Set<HostLocation> locations = event.subject().locations();
+ Host host = event.subject();
+ MacAddress hostMac = host.mac();
+ VlanId hostVlanId = host.vlan();
+ Set<HostLocation> locations = host.locations();
Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
- Set<IpAddress> newIps = event.subject().ipAddresses();
+ Set<IpAddress> newIps = host.ipAddresses();
log.info("Host {}/{} is updated", hostMac, hostVlanId);
locations.stream().filter(srManager::isMasterOf).forEach(location -> {
@@ -273,25 +299,94 @@
// Use the pair link temporarily before the second location of a dual-homed host shows up.
// This do not affect single-homed hosts since the flow will be blocked in
// processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
- locations.forEach(location -> {
+ locations.forEach(location ->
srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
+ Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
+ Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
+
srManager.getPairLocalPorts(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);
- Sets.difference(prevIps, newIps).forEach(ip ->
+ ipsToRemove.forEach(ip ->
processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, true)
);
- Sets.difference(newIps, prevIps).forEach(ip ->
+ ipsToAdd.forEach(ip ->
processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
);
+
+ if (srManager.activeProbing) {
+ probe(host, location, pairDeviceId, pairRemotePort);
+ }
});
}
- });
- });
+ })
+ );
+ }
+
+ /**
+ * When a non-pair port comes up, probe each host on the pair device if
+ * (1) the host is tagged and the tagged vlan of current port contains host vlan; or
+ * (2) the host is untagged and the internal vlan is the same on the host port and current port.
+ *
+ * @param cp connect point
+ */
+ void processPortUp(ConnectPoint cp) {
+ if (cp.port().equals(srManager.getPairLocalPorts(cp.deviceId()).orElse(null))) {
+ return;
+ }
+ if (srManager.activeProbing) {
+ srManager.getPairDeviceId(cp.deviceId())
+ .ifPresent(pairDeviceId -> srManager.hostService.getConnectedHosts(pairDeviceId).stream()
+ .filter(host -> isHostInVlanOfPort(host, pairDeviceId, cp))
+ .forEach(host -> srManager.probingService.probeHostLocation(host, cp, ProbeMode.DISCOVER))
+ );
+ }
+ }
+
+ /**
+ * Checks if given host located on given device id matches VLAN config of current port.
+ *
+ * @param host host to check
+ * @param deviceId device id to check
+ * @param cp current connect point
+ * @return true if the host located at deviceId matches the VLAN config on cp
+ */
+ private boolean isHostInVlanOfPort(Host host, DeviceId deviceId, ConnectPoint cp) {
+ VlanId internalVlan = srManager.getInternalVlanId(cp);
+ Set<VlanId> taggedVlan = srManager.getTaggedVlanId(cp);
+
+ return taggedVlan.contains(host.vlan()) ||
+ (internalVlan != null && host.locations().stream()
+ .filter(l -> l.deviceId().equals(deviceId))
+ .map(srManager::getInternalVlanId)
+ .anyMatch(internalVlan::equals));
+ }
+
+ /**
+ * Send a probe on all locations with the same VLAN on pair device, excluding pair port.
+ *
+ * @param host host to probe
+ * @param location newly discovered host location
+ * @param pairDeviceId pair device id
+ * @param pairRemotePort pair remote port
+ */
+ private void probe(Host host, ConnectPoint location, DeviceId pairDeviceId, PortNumber pairRemotePort) {
+ VlanId vlanToProbe = host.vlan().equals(VlanId.NONE) ?
+ srManager.getInternalVlanId(location) : host.vlan();
+ srManager.interfaceService.getInterfaces().stream()
+ .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
+ i.vlanUntagged().equals(vlanToProbe) ||
+ i.vlanNative().equals(vlanToProbe))
+ .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
+ .filter(i -> !i.connectPoint().port().equals(pairRemotePort))
+ .forEach(i -> {
+ log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
+ srManager.probingService.probeHostLocation(host, i.connectPoint(), ProbeMode.DISCOVER);
+ });
}
/**