Recover dual-homed host failover using pair link
In addition,
- Block ARP/NDP on pair port from being sent to controller
- Block DAD on pair port from flooded back to the originated host
- Minor refactoring
Change-Id: I3d697a06cb7ed3b56baa5d490197c155fe6969f0
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
index d9900c2..c0c7a04 100644
--- a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
@@ -16,6 +16,8 @@
package org.onosproject.segmentrouting;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -35,6 +37,8 @@
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
import org.onosproject.net.config.NetworkConfigRegistryAdapter;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
@@ -52,6 +56,7 @@
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.segmentrouting.config.DeviceConfiguration;
+import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
import java.util.Map;
import java.util.Objects;
@@ -90,10 +95,13 @@
// Device
private static final DeviceId DEV1 = DeviceId.deviceId("of:0000000000000001");
private static final DeviceId DEV2 = DeviceId.deviceId("of:0000000000000002");
+ private static final DeviceId DEV3 = DeviceId.deviceId("of:000000000000003");
+ private static final DeviceId DEV4 = DeviceId.deviceId("of:000000000000004");
// Port
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
private static final PortNumber P3 = PortNumber.portNumber(3);
+ private static final PortNumber P9 = PortNumber.portNumber(9);
// Connect Point
private static final ConnectPoint CP11 = new ConnectPoint(DEV1, P1);
private static final HostLocation HOST_LOC11 = new HostLocation(CP11, 0);
@@ -105,10 +113,19 @@
private static final HostLocation HOST_LOC21 = new HostLocation(CP21, 0);
private static final ConnectPoint CP22 = new ConnectPoint(DEV2, P2);
private static final HostLocation HOST_LOC22 = new HostLocation(CP22, 0);
+ // Connect Point for dual-homed host failover
+ private static final ConnectPoint CP31 = new ConnectPoint(DEV3, P1);
+ private static final HostLocation HOST_LOC31 = new HostLocation(CP31, 0);
+ private static final ConnectPoint CP41 = new ConnectPoint(DEV4, P1);
+ private static final HostLocation HOST_LOC41 = new HostLocation(CP41, 0);
+ private static final ConnectPoint CP39 = new ConnectPoint(DEV3, P9);
+ private static final ConnectPoint CP49 = new ConnectPoint(DEV4, P9);
// Interface VLAN
private static final VlanId INTF_VLAN_UNTAGGED = VlanId.vlanId((short) 10);
private static final Set<VlanId> INTF_VLAN_TAGGED = Sets.newHashSet(VlanId.vlanId((short) 20));
private static final VlanId INTF_VLAN_NATIVE = VlanId.vlanId((short) 30);
+ private static final Set<VlanId> INTF_VLAN_PAIR = Sets.newHashSet(VlanId.vlanId((short) 10),
+ VlanId.vlanId((short) 20), VlanId.vlanId((short) 30));
// Interface subnet
private static final IpPrefix INTF_PREFIX1 = IpPrefix.valueOf("10.0.1.254/24");
private static final IpPrefix INTF_PREFIX2 = IpPrefix.valueOf("10.0.2.254/24");
@@ -130,6 +147,7 @@
srManager.routingRulePopulator = new MockRoutingRulePopulator();
srManager.interfaceService = new MockInterfaceService();
srManager.hostService = new MockHostService();
+ srManager.cfgService = new MockNetworkConfigRegistry();
hostHandler = new HostHandler(srManager);
@@ -280,7 +298,7 @@
Sets.newHashSet(HOST_LOC13), Sets.newHashSet(HOST_IP11), false);
// Add a host
- // Expect: add a new routing rule. no change to bridging rule.
+ // Expect: add one new routing rule, one new bridging rule
hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
assertEquals(1, routingTable.size());
assertNotNull(routingTable.get(new RoutingTableKey(DEV1, HOST_IP11.toIpPrefix())));
@@ -371,6 +389,88 @@
}
@Test
+ public void testDualHomingSingleLocationFail() throws Exception {
+ Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+ Sets.newHashSet(HOST_LOC31, HOST_LOC41), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+ Host host2 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+ Sets.newHashSet(HOST_LOC31), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+
+ // Add a host
+ // Expect: add four new routing rules, two new bridging rules
+ hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
+ assertEquals(4, routingTable.size());
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(2, bridgingTable.size());
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+ // Host becomes single-homed
+ // Expect: redirect flows from host location to pair link
+ hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host2, host1));
+ assertEquals(4, routingTable.size());
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(2, bridgingTable.size());
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ assertEquals(P9, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+ // Host becomes dual-homed again
+ // Expect: Redirect flows from pair link back to host location
+ hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host1, host2));
+ assertEquals(4, routingTable.size());
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(2, bridgingTable.size());
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ }
+
+ @Test
+ public void testDualHomingBothLocationFail() throws Exception {
+ Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+ Sets.newHashSet(HOST_LOC31, HOST_LOC41), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+ Host host2 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+ Sets.newHashSet(HOST_LOC31), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+
+ // Add a host
+ // Expect: add four new routing rules, two new bridging rules
+ hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
+ assertEquals(4, routingTable.size());
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(2, bridgingTable.size());
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+ // Host becomes single-homed
+ // Expect: redirect flows from host location to pair link
+ hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host2, host1));
+ assertEquals(4, routingTable.size());
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+ assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+ assertEquals(2, bridgingTable.size());
+ assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+ assertEquals(P9, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+ // Host loses both locations
+ // Expect: Remove last location and all previous redirection flows
+ hostHandler.processHostRemovedEvent(new HostEvent(HostEvent.Type.HOST_REMOVED, host2));
+ assertEquals(0, routingTable.size());
+ assertEquals(0, bridgingTable.size());
+ }
+
+ @Test
public void testHostUpdated() throws Exception {
Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
Sets.newHashSet(HOST_LOC11), Sets.newHashSet(HOST_IP11), false);
@@ -501,12 +601,51 @@
} else if (CP22.equals(cp)) {
intf = new Interface(null, CP22, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
INTF_VLAN_UNTAGGED, null, null);
+ } else if (CP31.equals(cp)) {
+ intf = new Interface(null, CP31, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+ INTF_VLAN_UNTAGGED, null, null);
+ } else if (CP39.equals(cp)) {
+ intf = new Interface(null, CP39, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+ null, INTF_VLAN_PAIR, null);
+ } else if (CP41.equals(cp)) {
+ intf = new Interface(null, CP41, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+ INTF_VLAN_UNTAGGED, null, null);
+ } else if (CP49.equals(cp)) {
+ intf = new Interface(null, CP49, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+ null, INTF_VLAN_PAIR, null);
}
-
return Objects.nonNull(intf) ? Sets.newHashSet(intf) : Sets.newHashSet();
}
}
+ class MockNetworkConfigRegistry extends NetworkConfigRegistryAdapter {
+ @Override
+ public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+ if (subject instanceof DeviceId) {
+ DeviceId deviceId = (DeviceId) subject;
+ ObjectMapper mapper = new ObjectMapper();
+ ConfigApplyDelegate delegate = new MockCfgDelegate();
+ JsonNode emptyTree = new ObjectMapper().createObjectNode();
+
+ SegmentRoutingDeviceConfig config = new SegmentRoutingDeviceConfig();
+ config.init(deviceId, "host-handler-test", emptyTree, mapper, delegate);
+ config.setPairDeviceId(subject.equals(DEV3) ? DEV4 : DEV3)
+ .setPairLocalPort(P9);
+
+ return (C) config;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ class MockCfgDelegate implements ConfigApplyDelegate {
+ @Override
+ public void onApply(@SuppressWarnings("rawtypes") Config config) {
+ config.apply();
+ }
+ }
+
class MockFlowObjectiveService extends FlowObjectiveServiceAdapter {
@Override
public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {