SDFAB-104 Support routing via next hop in single leaf pair topology
The original design principle we adopted while implementing dual-homing is only recovering local failure using pair link.
Routing via next hop is a global thing recovered by updating ECMP hashing.
However, there is no spine in the single leaf pair setup so we need additional logic to recover this using pair link.
Change-Id: I3d648b139038be69656dd86b4c40d12bf10f50b2
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/impl/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index c0ccfb6..dc1a25a 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -23,13 +23,13 @@
import org.onlab.packet.VlanId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
-import org.onosproject.net.HostLocation;
import org.onosproject.net.PortNumber;
import org.onosproject.net.host.HostEvent;
import org.onosproject.routeservice.ResolvedRoute;
import org.onosproject.routeservice.RouteEvent;
import org.onosproject.net.DeviceId;
import org.onosproject.routeservice.RouteInfo;
+import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -88,9 +88,9 @@
return;
}
- ResolvedRoute rr = routes.stream().findFirst().orElse(null);
- if (rr == null) {
+ if (routes.isEmpty()) {
log.warn("No resolved route found. Abort processRouteAddedInternal");
+ return;
}
Set<ConnectPoint> allLocations = Sets.newHashSet();
@@ -102,7 +102,6 @@
log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
-
routes.forEach(route -> {
IpPrefix prefix = route.prefix();
MacAddress nextHopMac = route.nextHopMac();
@@ -115,6 +114,8 @@
log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
nextHopMac, nextHopVlan, location.port(), false);
+
+ processSingleLeafPairIfNeeded(locations, location, prefix, nextHopVlan);
});
});
}
@@ -189,6 +190,8 @@
log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
nextHopMac, nextHopVlan, location.port(), false);
+
+ processSingleLeafPairIfNeeded(locations, location, prefix, nextHopVlan);
});
});
}
@@ -223,19 +226,7 @@
srManager.deviceConfiguration.removeSubnet(location, prefix);
// We don't need to call revokeRoute again since revokeSubnet will remove the prefix
// from all devices, including the ones that next hop attaches to.
-
- // Also remove redirection flows on the pair device if exists.
- Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(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
- VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
-
- log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
- srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
- nextHopMac, vlanId, pairLocalPort.get(), false);
- }
+ // revokeSubnet will also remove flow on the pair device (if exist) pointing to current location.
});
});
}
@@ -245,19 +236,21 @@
MacAddress hostMac = event.subject().mac();
VlanId hostVlanId = event.subject().vlan();
- Set<HostLocation> prevLocations = event.prevSubject().locations();
- Set<HostLocation> newLocations = event.subject().locations();
- Set<ConnectPoint> connectPoints = newLocations.stream()
- .map(l -> (ConnectPoint) l).collect(Collectors.toSet());
+ Set<ConnectPoint> prevLocations = event.prevSubject().locations()
+ .stream().map(h -> (ConnectPoint) h)
+ .collect(Collectors.toSet());
+ Set<ConnectPoint> newLocations = event.subject().locations()
+ .stream().map(h -> (ConnectPoint) h)
+ .collect(Collectors.toSet());
List<Set<IpPrefix>> batchedSubnets =
srManager.deviceConfiguration.getBatchedSubnets(event.subject().id());
- Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
+ Set<DeviceId> newDeviceIds = newLocations.stream().map(ConnectPoint::deviceId)
.collect(Collectors.toSet());
// Set of deviceIDs of the previous locations where the host was connected
// Used to determine if host moved to different connect points
// on same device or moved to a different device altogether
- Set<DeviceId> oldDeviceIds = prevLocations.stream().map(HostLocation::deviceId)
+ Set<DeviceId> oldDeviceIds = prevLocations.stream().map(ConnectPoint::deviceId)
.collect(Collectors.toSet());
// L3 Ucast bucket needs to be updated only once per host
@@ -288,7 +281,7 @@
batchedSubnets.forEach(subnets -> {
log.debug("HostMoved. populateSubnet {}, {}", newLocations, subnets);
- srManager.defaultRoutingHandler.populateSubnet(connectPoints, subnets);
+ srManager.defaultRoutingHandler.populateSubnet(newLocations, subnets);
subnets.forEach(prefix -> {
// For each old location
@@ -304,7 +297,10 @@
srManager.deviceConfiguration.removeSubnet(prevLocation, prefix);
// Do not remove flow from a device if the route is still reachable via its pair device.
- // populateSubnet will update the flow to point to its pair device via spine.
+ // If spine exists,
+ // populateSubnet above will update the flow to point to its pair device via spine.
+ // If spine does not exist,
+ // processSingleLeafPair below will update the flow to point to its pair device via pair port.
DeviceId pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId()).orElse(null);
if (newLocations.stream().anyMatch(n -> n.deviceId().equals(pairDeviceId))) {
return;
@@ -327,6 +323,10 @@
hostMac, hostVlanId, newLocation.port(), false);
}
});
+
+ newLocations.forEach(location -> {
+ processSingleLeafPairIfNeeded(newLocations, location, prefix, hostVlanId);
+ });
});
});
@@ -370,4 +370,45 @@
return Objects.nonNull(srManager.deviceConfiguration) &&
Objects.nonNull(srManager.defaultRoutingHandler);
}
+
+ protected boolean processSingleLeafPairIfNeeded(Set<ConnectPoint> locations, ConnectPoint location, IpPrefix prefix,
+ VlanId nextHopVlan) {
+ // Special handling for single leaf pair
+ if (!srManager.getInfraDeviceIds().isEmpty()) {
+ log.debug("Spine found. Skip single leaf pair handling");
+ return false;
+ }
+ Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
+ if (pairDeviceId.isEmpty()) {
+ log.debug("Pair device of {} not found", location.deviceId());
+ return false;
+ }
+ if (locations.stream().anyMatch(l -> l.deviceId().equals(pairDeviceId.get()))) {
+ log.debug("Pair device has a next hop available. Leave it as is.");
+ return false;
+ }
+ Optional<PortNumber> pairRemotePort = srManager.getPairLocalPort(pairDeviceId.get());
+ if (pairRemotePort.isEmpty()) {
+ log.debug("Pair remote port of {} not found", pairDeviceId.get());
+ return false;
+ }
+ // Use routerMac of the pair device as the next hop
+ MacAddress effectiveMac;
+ try {
+ effectiveMac = srManager.getDeviceMacAddress(location.deviceId());
+ } catch (DeviceConfigNotFoundException e) {
+ log.warn("Abort populateRoute on pair device {}. routerMac not found", pairDeviceId);
+ return false;
+ }
+ // Since the pairLocalPort is trunk port, use assigned vlan of original port
+ // when the host is untagged
+ VlanId effectiveVlan = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
+
+ log.debug("Single leaf pair. populateRoute {}/{}, {}, {}, {}", pairDeviceId, pairRemotePort, prefix,
+ effectiveMac, effectiveVlan);
+ srManager.defaultRoutingHandler.populateRoute(pairDeviceId.get(), prefix,
+ effectiveMac, effectiveVlan, pairRemotePort.get(), false);
+
+ return true;
+ }
}