blob: 07e619f97f5beb8fbdc0083c443510795eba3338 [file] [log] [blame]
Charles Chan1eaf4802016-04-18 13:44:03 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chan1eaf4802016-04-18 13:44:03 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting;
18
Charles Chan12a8a842020-02-14 13:23:57 -080019import com.google.common.collect.Lists;
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070020import org.onlab.packet.EthType;
Charles Chan1eaf4802016-04-18 13:44:03 -070021import org.onlab.packet.IpAddress;
Jonghwan Hyune5ef7622017-08-25 17:48:36 -070022import org.onlab.packet.IpPrefix;
Charles Chan1eaf4802016-04-18 13:44:03 -070023import org.onlab.packet.MacAddress;
24import org.onlab.packet.VlanId;
piercf557922019-05-17 20:47:06 +020025import org.onlab.util.PredictableExecutor;
Charles Chan12a8a842020-02-14 13:23:57 -080026import org.onlab.util.Tools;
Charles Chan10b0fb72017-02-02 16:20:42 -080027import org.onosproject.net.ConnectPoint;
Charles Chan1eaf4802016-04-18 13:44:03 -070028import org.onosproject.net.DeviceId;
Charles Chan370a65b2016-05-10 17:29:47 -070029import org.onosproject.net.Host;
Charles Chanc22cef32016-04-29 14:38:22 -070030import org.onosproject.net.HostLocation;
Charles Chan1eaf4802016-04-18 13:44:03 -070031import org.onosproject.net.PortNumber;
Charles Chan12a8a842020-02-14 13:23:57 -080032import org.onosproject.net.flowobjective.Objective;
Charles Chan1eaf4802016-04-18 13:44:03 -070033import org.onosproject.net.host.HostEvent;
34import org.onosproject.net.host.HostService;
Charles Chanc6bcdf92018-06-01 16:33:48 -070035import org.onosproject.net.host.ProbeMode;
Charles Chan12a8a842020-02-14 13:23:57 -080036import org.onosproject.segmentrouting.phasedrecovery.api.Phase;
37import org.onosproject.segmentrouting.phasedrecovery.api.PhasedRecoveryService;
Charles Chan1eaf4802016-04-18 13:44:03 -070038import org.slf4j.Logger;
39import org.slf4j.LoggerFactory;
40
Saurav Dasf9332192017-02-18 14:05:44 -080041import com.google.common.collect.Sets;
Charles Chan3ed34d82017-06-22 18:03:14 -070042
Saurav Dase6c448a2018-01-18 12:07:33 -080043import java.util.HashSet;
Charles Chan12a8a842020-02-14 13:23:57 -080044import java.util.List;
Charles Chan3ed34d82017-06-22 18:03:14 -070045import java.util.Optional;
Charles Chan1eaf4802016-04-18 13:44:03 -070046import java.util.Set;
Charles Chan12a8a842020-02-14 13:23:57 -080047import java.util.concurrent.CompletableFuture;
48import java.util.concurrent.ExecutionException;
49import java.util.concurrent.TimeUnit;
Charles Chand9265a32017-06-16 15:19:24 -070050import java.util.stream.Collectors;
51
52import static com.google.common.base.Preconditions.checkArgument;
piercf557922019-05-17 20:47:06 +020053import static org.onlab.util.Tools.groupedThreads;
Charles Chan1eaf4802016-04-18 13:44:03 -070054
55/**
56 * Handles host-related events.
57 */
58public class HostHandler {
59 private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
Charles Chan873661e2017-11-30 15:37:50 -080060
Charles Chan114aec72017-06-19 14:00:53 -070061 protected final SegmentRoutingManager srManager;
Charles Chan1eaf4802016-04-18 13:44:03 -070062 private HostService hostService;
piercf557922019-05-17 20:47:06 +020063 // Host workers - 0 will leverage available processors
64 private static final int DEFAULT_THREADS = 0;
65 protected PredictableExecutor hostWorkers;
Charles Chan1eaf4802016-04-18 13:44:03 -070066
67 /**
68 * Constructs the HostHandler.
69 *
70 * @param srManager Segment Routing manager
71 */
Charles Chand9265a32017-06-16 15:19:24 -070072 HostHandler(SegmentRoutingManager srManager) {
Charles Chan1eaf4802016-04-18 13:44:03 -070073 this.srManager = srManager;
Charles Chan1eaf4802016-04-18 13:44:03 -070074 hostService = srManager.hostService;
piercf557922019-05-17 20:47:06 +020075 this.hostWorkers = new PredictableExecutor(DEFAULT_THREADS,
76 groupedThreads("onos/sr", "h-worker-%d", log));
77 }
78
79 /**
80 * Shutdowns the workers.
81 */
82 void terminate() {
83 hostWorkers.shutdown();
Charles Chan1eaf4802016-04-18 13:44:03 -070084 }
85
Charles Chandebfea32016-10-24 14:52:01 -070086 protected void init(DeviceId devId) {
Charles Chan12a8a842020-02-14 13:23:57 -080087 log.info("Initializing hosts on {}", devId);
88 List<CompletableFuture<Void>> hostFutures = Lists.newArrayList();
89
piercf557922019-05-17 20:47:06 +020090 // Init hosts in parallel using hostWorkers executor
Charles Chan12a8a842020-02-14 13:23:57 -080091 hostService.getHosts().forEach(host -> {
92 hostFutures.add(hostWorkers.submit(() -> initHost(host, devId), host.id().hashCode()));
93 });
94
95 log.debug("{} hostFutures for {}", hostFutures.size(), devId);
96 CompletableFuture<Void> allHostFuture = CompletableFuture.allOf(hostFutures.toArray(new CompletableFuture[0]));
97 CompletableFuture<Void> timeoutFuture =
98 Tools.completeAfter(PhasedRecoveryService.PAIR_TIMEOUT, TimeUnit.SECONDS);
99
100 allHostFuture.runAfterEitherAsync(timeoutFuture, () -> {
101 if (allHostFuture.isDone()) {
102 log.info("{} hosts initialized. Move {} to the next phase", hostFutures.size(), devId);
103 } else {
104 log.info("Timeout reached. Move {} to the next phase", devId);
105 }
106 srManager.phasedRecoveryService.setPhase(devId, Phase.INFRA);
107 });
Charles Chanc22cef32016-04-29 14:38:22 -0700108 }
Charles Chan1eaf4802016-04-18 13:44:03 -0700109
piercf557922019-05-17 20:47:06 +0200110 private void initHost(Host host, DeviceId deviceId) {
Charles Chan12a8a842020-02-14 13:23:57 -0800111 List<CompletableFuture<Objective>> locationFutures = Lists.newArrayList();
112
Charles Chana2ccb582019-11-25 09:47:22 -0800113 effectiveLocations(host).forEach(location -> {
piercf557922019-05-17 20:47:06 +0200114 if (location.deviceId().equals(deviceId) ||
115 location.deviceId().equals(srManager.getPairDeviceId(deviceId).orElse(null))) {
Charles Chan12a8a842020-02-14 13:23:57 -0800116 locationFutures.addAll(processHostAddedAtLocation(host, location));
piercf557922019-05-17 20:47:06 +0200117 }
118 });
Charles Chan12a8a842020-02-14 13:23:57 -0800119
120 log.debug("{} locationFutures for {}", locationFutures.size(), host);
121
122 // Waiting for all locationFutures to be completed.
123 // This is a blocking operation but it is fine since this is run in a separate thread
124 try {
125 CompletableFuture.allOf(locationFutures.toArray(new CompletableFuture[0]))
126 .thenApply(objectives -> locationFutures.stream()
127 .map(CompletableFuture::join)
128 .collect(Collectors.toList())
129 )
130 .get();
131 } catch (InterruptedException | ExecutionException e) {
132 log.warn("Exception caught when executing locationFutures");
133 locationFutures.forEach(future -> future.cancel(false));
134 }
piercf557922019-05-17 20:47:06 +0200135 }
136
Charles Chand9265a32017-06-16 15:19:24 -0700137 void processHostAddedEvent(HostEvent event) {
piercf557922019-05-17 20:47:06 +0200138 Host host = event.subject();
139 hostWorkers.execute(() -> processHostAdded(host), host.id().hashCode());
Charles Chanc22cef32016-04-29 14:38:22 -0700140 }
141
Charles Chand9265a32017-06-16 15:19:24 -0700142 private void processHostAdded(Host host) {
Charles Chana2ccb582019-11-25 09:47:22 -0800143 effectiveLocations(host).forEach(location -> processHostAddedAtLocation(host, location));
Saurav Dase6c448a2018-01-18 12:07:33 -0800144 // ensure dual-homed host locations have viable uplinks
Charles Chana2ccb582019-11-25 09:47:22 -0800145 if (effectiveLocations(host).size() > 1 || srManager.singleHomedDown) {
146 effectiveLocations(host).forEach(loc -> {
Saurav Dase6c448a2018-01-18 12:07:33 -0800147 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
Saurav Dasec683dc2018-04-27 18:42:30 -0700148 srManager.linkHandler.checkUplinksForHost(loc);
Saurav Dase6c448a2018-01-18 12:07:33 -0800149 }
150 });
151 }
Charles Chan1eaf4802016-04-18 13:44:03 -0700152 }
153
Charles Chan12a8a842020-02-14 13:23:57 -0800154 List<CompletableFuture<Objective>> processHostAddedAtLocation(Host host, HostLocation location) {
Charles Chana2ccb582019-11-25 09:47:22 -0800155 checkArgument(effectiveLocations(host).contains(location), "{} is not a location of {}", location, host);
Charles Chand9265a32017-06-16 15:19:24 -0700156
Charles Chanceb2a2e2017-09-12 18:57:47 -0700157 MacAddress hostMac = host.mac();
158 VlanId hostVlanId = host.vlan();
Charles Chana2ccb582019-11-25 09:47:22 -0800159 Set<HostLocation> locations = effectiveLocations(host);
Charles Chand9265a32017-06-16 15:19:24 -0700160 Set<IpAddress> ips = host.ipAddresses();
Charles Chanceb2a2e2017-09-12 18:57:47 -0700161 log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
Charles Chand9265a32017-06-16 15:19:24 -0700162
Charles Chan12a8a842020-02-14 13:23:57 -0800163 List<CompletableFuture<Objective>> objectiveFutures = Lists.newArrayList();
164
165 // TODO Phased recovery does not trace double tagged hosts
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700166 if (isDoubleTaggedHost(host)) {
167 ips.forEach(ip ->
168 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
169 host.innerVlan(), hostVlanId, host.tpid(), ip, false)
170 );
171 } else {
Charles Chan12a8a842020-02-14 13:23:57 -0800172 objectiveFutures.add(
173 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false)
174 );
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700175 ips.forEach(ip ->
Charles Chan12a8a842020-02-14 13:23:57 -0800176 objectiveFutures.add(
177 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
178 )
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700179 );
180 }
Charles Chanceb2a2e2017-09-12 18:57:47 -0700181
182 // Use the pair link temporarily before the second location of a dual-homed host shows up.
183 // This do not affect single-homed hosts since the flow will be blocked in
184 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
185 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
Charles Chana2ccb582019-11-25 09:47:22 -0800186 if (effectiveLocations(host).stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
Saurav Dasec683dc2018-04-27 18:42:30 -0700187 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
Charles Chanceb2a2e2017-09-12 18:57:47 -0700188 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
189 // when the host is untagged
Charles Chan868c9572018-06-15 18:54:18 -0700190 VlanId vlanId = vlanForPairPort(hostVlanId, location);
191 if (vlanId == null) {
192 return;
193 }
Charles Chanceb2a2e2017-09-12 18:57:47 -0700194
Charles Chan12a8a842020-02-14 13:23:57 -0800195 objectiveFutures.add(
196 processBridgingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, false)
197 );
198 ips.forEach(ip ->
199 objectiveFutures.add(
200 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
201 )
202 );
Charles Chan873661e2017-11-30 15:37:50 -0800203
204 if (srManager.activeProbing) {
205 probe(host, location, pairDeviceId, pairRemotePort);
206 }
Charles Chanceb2a2e2017-09-12 18:57:47 -0700207 });
208 }
209 });
Ruchi Sahota07869322019-05-09 17:26:14 -0400210
pierventrea3989be2021-01-08 16:43:17 +0100211 // NOTE: that we use the nexthop vlanId to retrieve the nextId
212 // while the vlanId used to program the L3 unicast chain
213 // is derived from the port configuration. In case of
214 // a tagged interface we use host vlanId. Host vlan should
215 // be part of the tags configured for that port. See the
216 // code in DefaultGroupHandler.updateL3UcastGroupBucket
Ruchi Sahota07869322019-05-09 17:26:14 -0400217 int nextId = srManager.getMacVlanNextObjectiveId(location.deviceId(), hostMac, hostVlanId, null, false);
218 if (nextId != -1) {
Ruchi Sahota07869322019-05-09 17:26:14 -0400219 log.debug(" Updating next objective for device {}, host {}/{}, port {}, nextid {}",
pierventrea3989be2021-01-08 16:43:17 +0100220 location.deviceId(), hostMac, hostVlanId, location.port(), nextId);
221 srManager.updateMacVlanTreatment(location.deviceId(), hostMac, hostVlanId,
Ruchi Sahota07869322019-05-09 17:26:14 -0400222 location.port(), nextId);
223 }
Charles Chan12a8a842020-02-14 13:23:57 -0800224
225 log.debug("{} objectiveFutures for {}", objectiveFutures.size(), location);
226 return objectiveFutures;
Charles Chand9265a32017-06-16 15:19:24 -0700227 }
228
229 void processHostRemovedEvent(HostEvent event) {
piercf557922019-05-17 20:47:06 +0200230 Host host = event.subject();
231 hostWorkers.execute(() -> processHostRemoved(host), host.id().hashCode());
Charles Chan41f5ec02016-06-13 18:54:31 -0700232 }
233
Charles Chand9265a32017-06-16 15:19:24 -0700234 private void processHostRemoved(Host host) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700235 MacAddress hostMac = host.mac();
236 VlanId hostVlanId = host.vlan();
Charles Chana2ccb582019-11-25 09:47:22 -0800237 Set<HostLocation> locations = effectiveLocations(host);
Charles Chan41f5ec02016-06-13 18:54:31 -0700238 Set<IpAddress> ips = host.ipAddresses();
Charles Chan3ed34d82017-06-22 18:03:14 -0700239 log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
Charles Chanc22cef32016-04-29 14:38:22 -0700240
Charles Chan910be6a2017-08-23 14:46:43 -0700241 locations.forEach(location -> {
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700242 if (isDoubleTaggedHost(host)) {
243 ips.forEach(ip ->
244 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
245 host.innerVlan(), hostVlanId, host.tpid(), ip, true)
246 );
247 } else {
248 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
249 ips.forEach(ip ->
Charles Chand66d6712018-03-29 16:03:41 -0700250 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700251 );
252 }
Charles Chan3ed34d82017-06-22 18:03:14 -0700253
254 // Also remove redirection flows on the pair device if exists.
255 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Saurav Dasec683dc2018-04-27 18:42:30 -0700256 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
Charles Chand66d6712018-03-29 16:03:41 -0700257 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700258 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
259 // when the host is untagged
Charles Chan868c9572018-06-15 18:54:18 -0700260 VlanId vlanId = vlanForPairPort(hostVlanId, location);
261 if (vlanId == null) {
262 return;
263 }
Charles Chan3ed34d82017-06-22 18:03:14 -0700264
265 processBridgingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId, true);
266 ips.forEach(ip ->
267 processRoutingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId,
268 ip, true));
269 }
Charles Chanbd84dd52018-06-21 19:07:12 -0700270
271 // Delete prefix from sr-device-subnet when the next hop host is removed
272 srManager.routeService.getRouteTables().forEach(tableId -> {
273 srManager.routeService.getRoutes(tableId).forEach(routeInfo -> {
274 if (routeInfo.allRoutes().stream().anyMatch(rr -> ips.contains(rr.nextHop()))) {
275 log.debug("HostRemoved. removeSubnet {}, {}", location, routeInfo.prefix());
276 srManager.deviceConfiguration.removeSubnet(location, routeInfo.prefix());
277 }
278 });
279 });
Ruchi Sahota07869322019-05-09 17:26:14 -0400280
Charles Chand9265a32017-06-16 15:19:24 -0700281 });
Charles Chanc22cef32016-04-29 14:38:22 -0700282 }
283
Charles Chand9265a32017-06-16 15:19:24 -0700284 void processHostMovedEvent(HostEvent event) {
Charles Chan4b5769a2018-04-25 18:51:46 -0400285 Host host = event.subject();
piercf557922019-05-17 20:47:06 +0200286 hostWorkers.execute(() -> processHostMovedEventInternal(event), host.id().hashCode());
287 }
288
289 private void processHostMovedEventInternal(HostEvent event) {
Charles Chana2ccb582019-11-25 09:47:22 -0800290 // This method will be called when one of the following value has changed:
291 // (1) locations (2) auxLocations or (3) both locations and auxLocations.
292 // We only need to proceed when effectiveLocation has changed.
293 Set<HostLocation> newLocations = effectiveLocations(event.subject());
294 Set<HostLocation> prevLocations = effectiveLocations(event.prevSubject());
295
296 if (newLocations.equals(prevLocations)) {
297 log.info("effectiveLocations of {} has not changed. Skipping {}", event.subject().id(), event);
298 return;
299 }
300
piercf557922019-05-17 20:47:06 +0200301 Host host = event.subject();
Charles Chana2ccb582019-11-25 09:47:22 -0800302 Host prevHost = event.prevSubject();
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700303 MacAddress hostMac = host.mac();
304 VlanId hostVlanId = host.vlan();
Charles Chana2ccb582019-11-25 09:47:22 -0800305 Set<IpAddress> prevIps = prevHost.ipAddresses();
Charles Chan4b5769a2018-04-25 18:51:46 -0400306 Set<IpAddress> newIps = host.ipAddresses();
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700307 EthType hostTpid = host.tpid();
308 boolean doubleTaggedHost = isDoubleTaggedHost(host);
Charles Chan4b5769a2018-04-25 18:51:46 -0400309
Charles Chan873661e2017-11-30 15:37:50 -0800310 log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Charles Chand9265a32017-06-16 15:19:24 -0700311 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
312 .collect(Collectors.toSet());
Charles Chanc22cef32016-04-29 14:38:22 -0700313
Charles Chand9265a32017-06-16 15:19:24 -0700314 // For each old location
Charles Chand66d6712018-03-29 16:03:41 -0700315 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan3ed34d82017-06-22 18:03:14 -0700316 // Remove routing rules for old IPs
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700317 Sets.difference(prevIps, newIps).forEach(ip -> {
318 if (doubleTaggedHost) {
319 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
320 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
321 } else {
Charles Chan3ed34d82017-06-22 18:03:14 -0700322 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700323 ip, true);
324 }
325 });
Charles Chan3ed34d82017-06-22 18:03:14 -0700326
327 // Redirect the flows to pair link if configured
328 // Note: Do not continue removing any rule
329 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
Saurav Dasec683dc2018-04-27 18:42:30 -0700330 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(prevLocation.deviceId());
Charles Chan6d43c162018-10-11 12:35:36 -0700331 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
332 newLocations.stream().anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
333 newLocations.stream().noneMatch(location -> location.deviceId().equals(prevLocation.deviceId()))) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700334 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
335 // when the host is untagged
336 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
337
338 processBridgingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId, false);
339 newIps.forEach(ip ->
340 processRoutingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId,
341 ip, false));
342 return;
343 }
Charles Chand9265a32017-06-16 15:19:24 -0700344
Charles Chanfbcb8812018-04-18 18:41:05 -0700345 // Remove flows for unchanged IPs only when the host moves from a switch to another.
Charles Chand9265a32017-06-16 15:19:24 -0700346 // Otherwise, do not remove and let the adding part update the old flow
347 if (!newDeviceIds.contains(prevLocation.deviceId())) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700348 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700349 Sets.intersection(prevIps, newIps).forEach(ip -> {
350 if (doubleTaggedHost) {
351 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
352 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
353 } else {
354 processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
355 hostMac, hostVlanId, ip, true);
356 }
357 });
Charles Chand9265a32017-06-16 15:19:24 -0700358 }
359
360 // Remove bridging rules if new interface vlan is different from old interface vlan
361 // Otherwise, do not remove and let the adding part update the old flow
362 if (newLocations.stream().noneMatch(newLocation -> {
363 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
364 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
365 // Host is tagged and the new location has the host vlan in vlan-tagged
Charles Chan098ca202018-05-01 11:50:20 -0700366 return srManager.interfaceService.getTaggedVlanId(newLocation).contains(hostVlanId) ||
Charles Chand9265a32017-06-16 15:19:24 -0700367 (oldAssignedVlan != null && newAssignedVlan != null &&
368 // Host is untagged and the new location has the same assigned vlan
369 oldAssignedVlan.equals(newAssignedVlan));
370 })) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700371 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chand9265a32017-06-16 15:19:24 -0700372 }
373
374 // Remove routing rules for unchanged IPs if none of the subnet of new location contains
375 // the IP. Otherwise, do not remove and let the adding part update the old flow
376 Sets.intersection(prevIps, newIps).forEach(ip -> {
377 if (newLocations.stream().noneMatch(newLocation ->
378 srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700379 if (doubleTaggedHost) {
380 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
381 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
382 } else {
383 processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
384 hostMac, hostVlanId, ip, true);
385 }
Charles Chand9265a32017-06-16 15:19:24 -0700386 }
Charles Chanc22cef32016-04-29 14:38:22 -0700387 });
Charles Chand9265a32017-06-16 15:19:24 -0700388 });
389
390 // For each new location, add all new IPs.
Charles Chanfbcb8812018-04-18 18:41:05 -0700391 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan3ed34d82017-06-22 18:03:14 -0700392 processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700393 newIps.forEach(ip -> {
394 if (doubleTaggedHost) {
395 processDoubleTaggedRoutingRule(newLocation.deviceId(), newLocation.port(),
396 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
397 } else {
Charles Chan3ed34d82017-06-22 18:03:14 -0700398 processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700399 ip, false);
400 }
401 });
Charles Chan4b5769a2018-04-25 18:51:46 -0400402
403 // Probe on pair device when host move
404 // Majorly for the 2nd step of [1A/x, 1B/x] -> [1A/x, 1B/y] -> [1A/y, 1B/y]
405 // But will also cover [1A/x] -> [1A/y] -> [1A/y, 1B/y]
406 if (srManager.activeProbing) {
Charles Chan6e90fbb2018-07-23 15:27:34 -0700407
Charles Chan4b5769a2018-04-25 18:51:46 -0400408 srManager.getPairDeviceId(newLocation.deviceId()).ifPresent(pairDeviceId ->
409 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort ->
410 probe(host, newLocation, pairDeviceId, pairRemotePort)
411 )
412 );
413 }
Charles Chand9265a32017-06-16 15:19:24 -0700414 });
415
416 // For each unchanged location, add new IPs and remove old IPs.
Charles Chanfbcb8812018-04-18 18:41:05 -0700417 Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700418 Sets.difference(prevIps, newIps).forEach(ip -> {
419 if (doubleTaggedHost) {
420 processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
421 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
422 } else {
423 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
424 hostMac, hostVlanId, ip, true);
425 }
426 });
Charles Chand9265a32017-06-16 15:19:24 -0700427
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700428 Sets.difference(newIps, prevIps).forEach(ip -> {
429 if (doubleTaggedHost) {
430 processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
431 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
432 } else {
433 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
434 hostMac, hostVlanId, ip, false);
435 }
436 });
Charles Chan6e90fbb2018-07-23 15:27:34 -0700437
438 // Verify existing location and see if it is still valid
439 srManager.probingService.probeHost(host, unchangedLocation, ProbeMode.VERIFY);
Charles Chand9265a32017-06-16 15:19:24 -0700440 });
Saurav Dase6c448a2018-01-18 12:07:33 -0800441
442 // ensure dual-homed host locations have viable uplinks
Saurav Dasec683dc2018-04-27 18:42:30 -0700443 if (newLocations.size() > prevLocations.size() || srManager.singleHomedDown) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800444 newLocations.forEach(loc -> {
445 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
Saurav Dasec683dc2018-04-27 18:42:30 -0700446 srManager.linkHandler.checkUplinksForHost(loc);
Saurav Dase6c448a2018-01-18 12:07:33 -0800447 }
448 });
449 }
Charles Chanc22cef32016-04-29 14:38:22 -0700450 }
451
Charles Chand9265a32017-06-16 15:19:24 -0700452 void processHostUpdatedEvent(HostEvent event) {
Charles Chan873661e2017-11-30 15:37:50 -0800453 Host host = event.subject();
piercf557922019-05-17 20:47:06 +0200454 hostWorkers.execute(() -> processHostUpdatedEventInternal(event), host.id().hashCode());
455 }
456
457 private void processHostUpdatedEventInternal(HostEvent event) {
458 Host host = event.subject();
Charles Chan873661e2017-11-30 15:37:50 -0800459 MacAddress hostMac = host.mac();
460 VlanId hostVlanId = host.vlan();
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700461 EthType hostTpid = host.tpid();
Charles Chana2ccb582019-11-25 09:47:22 -0800462 Set<HostLocation> locations = effectiveLocations(host);
Charles Chanc22cef32016-04-29 14:38:22 -0700463 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chan873661e2017-11-30 15:37:50 -0800464 Set<IpAddress> newIps = host.ipAddresses();
Charles Chanb3c1faf2017-11-20 08:46:24 -0800465 log.info("Host {}/{} is updated", hostMac, hostVlanId);
Charles Chanc22cef32016-04-29 14:38:22 -0700466
Charles Chand66d6712018-03-29 16:03:41 -0700467 locations.forEach(location -> {
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700468 Sets.difference(prevIps, newIps).forEach(ip -> {
469 if (isDoubleTaggedHost(host)) {
470 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
471 host.innerVlan(), hostVlanId, hostTpid, ip, true);
472 } else {
473 processRoutingRule(location.deviceId(), location.port(), hostMac,
474 hostVlanId, ip, true);
475 }
476 });
477 Sets.difference(newIps, prevIps).forEach(ip -> {
478 if (isDoubleTaggedHost(host)) {
479 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
480 host.innerVlan(), hostVlanId, hostTpid, ip, false);
481 } else {
482 processRoutingRule(location.deviceId(), location.port(), hostMac,
483 hostVlanId, ip, false);
484 }
485 });
Charles Chand9265a32017-06-16 15:19:24 -0700486 });
Charles Chanb3c1faf2017-11-20 08:46:24 -0800487
488 // Use the pair link temporarily before the second location of a dual-homed host shows up.
489 // This do not affect single-homed hosts since the flow will be blocked in
490 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
Charles Chan873661e2017-11-30 15:37:50 -0800491 locations.forEach(location ->
Charles Chanb3c1faf2017-11-20 08:46:24 -0800492 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
Charles Chand66d6712018-03-29 16:03:41 -0700493 if (locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
Charles Chan873661e2017-11-30 15:37:50 -0800494 Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
495 Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
496
Saurav Dasec683dc2018-04-27 18:42:30 -0700497 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
Charles Chanb3c1faf2017-11-20 08:46:24 -0800498 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
499 // when the host is untagged
Charles Chan868c9572018-06-15 18:54:18 -0700500 VlanId vlanId = vlanForPairPort(hostVlanId, location);
501 if (vlanId == null) {
502 return;
503 }
Charles Chanb3c1faf2017-11-20 08:46:24 -0800504
Charles Chan873661e2017-11-30 15:37:50 -0800505 ipsToRemove.forEach(ip ->
Charles Chanb3c1faf2017-11-20 08:46:24 -0800506 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, true)
507 );
Charles Chan873661e2017-11-30 15:37:50 -0800508 ipsToAdd.forEach(ip ->
Charles Chanb3c1faf2017-11-20 08:46:24 -0800509 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
510 );
Charles Chan873661e2017-11-30 15:37:50 -0800511
512 if (srManager.activeProbing) {
513 probe(host, location, pairDeviceId, pairRemotePort);
514 }
Charles Chanb3c1faf2017-11-20 08:46:24 -0800515 });
516 }
Charles Chan873661e2017-11-30 15:37:50 -0800517 })
518 );
519 }
520
521 /**
522 * When a non-pair port comes up, probe each host on the pair device if
523 * (1) the host is tagged and the tagged vlan of current port contains host vlan; or
524 * (2) the host is untagged and the internal vlan is the same on the host port and current port.
525 *
526 * @param cp connect point
527 */
Charles Chana2ccb582019-11-25 09:47:22 -0800528 // TODO Current implementation does not handle dual-homed hosts with auxiliary locations.
Charles Chan873661e2017-11-30 15:37:50 -0800529 void processPortUp(ConnectPoint cp) {
Saurav Dasec683dc2018-04-27 18:42:30 -0700530 if (cp.port().equals(srManager.getPairLocalPort(cp.deviceId()).orElse(null))) {
Charles Chan873661e2017-11-30 15:37:50 -0800531 return;
532 }
533 if (srManager.activeProbing) {
534 srManager.getPairDeviceId(cp.deviceId())
piercf557922019-05-17 20:47:06 +0200535 .ifPresent(pairDeviceId -> srManager.hostService.getConnectedHosts(pairDeviceId).forEach(
536 host -> hostWorkers.execute(() -> probingIfNecessary(host, pairDeviceId, cp),
537 host.id().hashCode())));
538 }
539 }
540
Charles Chana2ccb582019-11-25 09:47:22 -0800541 // TODO Current implementation does not handle dual-homed hosts with auxiliary locations.
piercf557922019-05-17 20:47:06 +0200542 private void probingIfNecessary(Host host, DeviceId pairDeviceId, ConnectPoint cp) {
543 if (isHostInVlanOfPort(host, pairDeviceId, cp)) {
544 srManager.probingService.probeHost(host, cp, ProbeMode.DISCOVER);
Charles Chan873661e2017-11-30 15:37:50 -0800545 }
546 }
547
548 /**
549 * Checks if given host located on given device id matches VLAN config of current port.
550 *
551 * @param host host to check
552 * @param deviceId device id to check
553 * @param cp current connect point
554 * @return true if the host located at deviceId matches the VLAN config on cp
555 */
556 private boolean isHostInVlanOfPort(Host host, DeviceId deviceId, ConnectPoint cp) {
557 VlanId internalVlan = srManager.getInternalVlanId(cp);
Charles Chan098ca202018-05-01 11:50:20 -0700558 Set<VlanId> taggedVlan = srManager.interfaceService.getTaggedVlanId(cp);
Charles Chan873661e2017-11-30 15:37:50 -0800559
560 return taggedVlan.contains(host.vlan()) ||
Charles Chana2ccb582019-11-25 09:47:22 -0800561 (internalVlan != null && effectiveLocations(host).stream()
Charles Chan873661e2017-11-30 15:37:50 -0800562 .filter(l -> l.deviceId().equals(deviceId))
563 .map(srManager::getInternalVlanId)
564 .anyMatch(internalVlan::equals));
565 }
566
567 /**
568 * Send a probe on all locations with the same VLAN on pair device, excluding pair port.
569 *
570 * @param host host to probe
571 * @param location newly discovered host location
572 * @param pairDeviceId pair device id
573 * @param pairRemotePort pair remote port
574 */
Charles Chana2ccb582019-11-25 09:47:22 -0800575 // TODO Current implementation does not handle dual-homed hosts with auxiliary locations.
Charles Chan873661e2017-11-30 15:37:50 -0800576 private void probe(Host host, ConnectPoint location, DeviceId pairDeviceId, PortNumber pairRemotePort) {
Mayank Tiwarif7942402018-10-29 18:27:35 -0400577 //Check if the host still exists in the host store
578 if (hostService.getHost(host.id()) == null) {
579 log.debug("Host entry for host {} no more present. Aborting hostprobe discover for this host", host.id());
580 return;
581 }
582
Charles Chan873661e2017-11-30 15:37:50 -0800583 VlanId vlanToProbe = host.vlan().equals(VlanId.NONE) ?
584 srManager.getInternalVlanId(location) : host.vlan();
Mayank Tiwarif7942402018-10-29 18:27:35 -0400585 if (srManager.symmetricProbing) {
586 srManager.interfaceService.getInterfaces().stream()
587 .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
588 i.vlanUntagged().equals(vlanToProbe) ||
589 i.vlanNative().equals(vlanToProbe))
590 .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
591 .filter(i -> i.connectPoint().port().equals(location.port()))
592 .forEach(i -> {
593 log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
594 srManager.probingService.probeHost(host, i.connectPoint(), ProbeMode.DISCOVER);
595 });
596 } else {
597 srManager.interfaceService.getInterfaces().stream()
598 .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
599 i.vlanUntagged().equals(vlanToProbe) ||
600 i.vlanNative().equals(vlanToProbe))
601 .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
602 .filter(i -> !i.connectPoint().port().equals(pairRemotePort))
603 .forEach(i -> {
604 log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
605 srManager.probingService.probeHost(host, i.connectPoint(), ProbeMode.DISCOVER);
606 });
607 }
Charles Chanc22cef32016-04-29 14:38:22 -0700608 }
609
610 /**
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700611 * Populates or revokes a bridging rule on given deviceId that matches given mac,
612 * given vlan and output to given port.
Charles Chan9595e6a2017-06-15 19:25:25 -0700613 *
614 * @param deviceId device ID
615 * @param port port
616 * @param mac mac address
617 * @param vlanId VLAN ID
618 * @param revoke true to revoke the rule; false to populate
Charles Chan12a8a842020-02-14 13:23:57 -0800619 * @return future that includes the flow objective if succeeded, null if otherwise
Charles Chan9595e6a2017-06-15 19:25:25 -0700620 */
Charles Chan12a8a842020-02-14 13:23:57 -0800621 private CompletableFuture<Objective> processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
Charles Chan9595e6a2017-06-15 19:25:25 -0700622 VlanId vlanId, boolean revoke) {
Charles Chand66d6712018-03-29 16:03:41 -0700623 log.info("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
Charles Chan9595e6a2017-06-15 19:25:25 -0700624 mac, vlanId, deviceId, port);
625
Charles Chand66d6712018-03-29 16:03:41 -0700626 if (!revoke) {
Charles Chan12a8a842020-02-14 13:23:57 -0800627 return srManager.defaultRoutingHandler.populateBridging(deviceId, port, mac, vlanId);
Charles Chand66d6712018-03-29 16:03:41 -0700628 } else {
Charles Chan12a8a842020-02-14 13:23:57 -0800629 return srManager.defaultRoutingHandler.revokeBridging(deviceId, port, mac, vlanId);
Charles Chan9595e6a2017-06-15 19:25:25 -0700630 }
Charles Chan9595e6a2017-06-15 19:25:25 -0700631 }
632
633 /**
634 * Populate or revoke a routing rule on given deviceId that matches given ip,
635 * set destination mac to given mac, set vlan to given vlan and output to given port.
636 *
637 * @param deviceId device ID
638 * @param port port
639 * @param mac mac address
640 * @param vlanId VLAN ID
641 * @param ip IP address
642 * @param revoke true to revoke the rule; false to populate
Charles Chan12a8a842020-02-14 13:23:57 -0800643 * @return future that includes the flow objective if succeeded, null if otherwise
Charles Chan9595e6a2017-06-15 19:25:25 -0700644 */
Charles Chan12a8a842020-02-14 13:23:57 -0800645 private CompletableFuture<Objective> processRoutingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
Charles Chan9595e6a2017-06-15 19:25:25 -0700646 VlanId vlanId, IpAddress ip, boolean revoke) {
647 ConnectPoint location = new ConnectPoint(deviceId, port);
Charles Chand9265a32017-06-16 15:19:24 -0700648 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
649 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
Charles Chan12a8a842020-02-14 13:23:57 -0800650 return CompletableFuture.completedFuture(null);
Charles Chan9595e6a2017-06-15 19:25:25 -0700651 }
Charles Chan9595e6a2017-06-15 19:25:25 -0700652
Charles Chand9265a32017-06-16 15:19:24 -0700653 log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
654 if (revoke) {
Charles Chan12a8a842020-02-14 13:23:57 -0800655 return srManager.defaultRoutingHandler.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
Charles Chand9265a32017-06-16 15:19:24 -0700656 } else {
Charles Chan12a8a842020-02-14 13:23:57 -0800657 return srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
Charles Chan370a65b2016-05-10 17:29:47 -0700658 }
Charles Chan370a65b2016-05-10 17:29:47 -0700659 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700660
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700661 /**
662 * Populate or revoke a routing rule and egress rules on given deviceId that matches given IP,
663 * to set destination mac to given mac, set inner vlan and outer vlan to given vlans,
664 * set outer TPID, and output to given port.
665 *
666 * @param deviceId device ID
667 * @param port port
668 * @param mac mac address
669 * @param innerVlan inner VLAN ID
670 * @param outerVlan outer VLAN ID
671 * @param outerTpid outer TPID
672 * @param ip IP address
673 * @param revoke true to revoke the rule; false to populate
674 */
675 private void processDoubleTaggedRoutingRule(DeviceId deviceId, PortNumber port,
676 MacAddress mac, VlanId innerVlan,
677 VlanId outerVlan, EthType outerTpid,
678 IpAddress ip, boolean revoke) {
Charles Chanfbca55e2018-07-24 16:40:35 -0700679 if (!srManager.routeDoubleTaggedHosts) {
680 log.debug("Routing for double tagged host is disabled. Ignore {}/{}/{}", mac, outerVlan, innerVlan);
Charles Chan4eb73bb2018-07-19 14:55:31 -0700681 return;
682 }
683
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700684 ConnectPoint location = new ConnectPoint(deviceId, port);
685 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
686 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
687 return;
688 }
689 log.info("{} routing rule for double-tagged host {} at {}",
690 revoke ? "Revoking" : "Populating", ip, location);
691 if (revoke) {
692 srManager.defaultRoutingHandler.revokeDoubleTaggedRoute(
693 deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
694 } else {
695 srManager.defaultRoutingHandler.populateDoubleTaggedRoute(
696 deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
697 }
698 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700699
Charles Chan4eb73bb2018-07-19 14:55:31 -0700700 void populateAllDoubleTaggedHost() {
Charles Chanfbca55e2018-07-24 16:40:35 -0700701 log.info("Enabling routing for all double tagged hosts");
Charles Chan4eb73bb2018-07-19 14:55:31 -0700702 Sets.newHashSet(srManager.hostService.getHosts()).stream().filter(this::isDoubleTaggedHost)
Charles Chana2ccb582019-11-25 09:47:22 -0800703 .forEach(h -> effectiveLocations(h).forEach(l ->
Charles Chan4eb73bb2018-07-19 14:55:31 -0700704 h.ipAddresses().forEach(i ->
705 processDoubleTaggedRoutingRule(l.deviceId(), l.port(), h.mac(), h.innerVlan(),
706 h.vlan(), h.tpid(), i, false)
707 )
708 )
709 );
710 }
711
712 void revokeAllDoubleTaggedHost() {
Charles Chanfbca55e2018-07-24 16:40:35 -0700713 log.info("Disabling routing for all double tagged hosts");
Charles Chan4eb73bb2018-07-19 14:55:31 -0700714 Sets.newHashSet(srManager.hostService.getHosts()).stream().filter(this::isDoubleTaggedHost)
Charles Chana2ccb582019-11-25 09:47:22 -0800715 .forEach(h -> effectiveLocations(h).forEach(l ->
Charles Chan4eb73bb2018-07-19 14:55:31 -0700716 h.ipAddresses().forEach(i ->
717 processDoubleTaggedRoutingRule(l.deviceId(), l.port(), h.mac(), h.innerVlan(),
718 h.vlan(), h.tpid(), i, true)
719 )
720 )
721 );
722 }
723
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700724 /**
Charles Chan868c9572018-06-15 18:54:18 -0700725 * Returns VLAN ID to be used to program redirection flow on pair port.
726 *
727 * @param hostVlanId host VLAN ID
728 * @param location host location
729 * @return VLAN ID to be used; Or null if host VLAN does not match the interface config
730 */
731 VlanId vlanForPairPort(VlanId hostVlanId, ConnectPoint location) {
732 VlanId internalVlan = srManager.getInternalVlanId(location);
733 Set<VlanId> taggedVlan = srManager.interfaceService.getTaggedVlanId(location);
734
735 if (!hostVlanId.equals(VlanId.NONE) && taggedVlan.contains(hostVlanId)) {
736 return hostVlanId;
737 } else if (hostVlanId.equals(VlanId.NONE) && internalVlan != null) {
738 return internalVlan;
739 } else {
740 log.warn("VLAN mismatch. hostVlan={}, location={}, internalVlan={}, taggedVlan={}",
741 hostVlanId, location, internalVlan, taggedVlan);
742 return null;
743 }
744 }
745
746 /**
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700747 * Update forwarding objective for unicast bridging and unicast routing.
748 * Also check the validity of updated interface configuration on VLAN.
749 *
750 * @param deviceId device ID that host attaches to
751 * @param portNum port number that host attaches to
752 * @param vlanId Vlan ID configured on the switch port
753 * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
754 * @param install true to populate the objective, false to revoke
755 */
Charles Chana2ccb582019-11-25 09:47:22 -0800756 // TODO Current implementation does not handle dual-homed hosts with auxiliary locations.
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700757 void processIntfVlanUpdatedEvent(DeviceId deviceId, PortNumber portNum, VlanId vlanId,
piercf557922019-05-17 20:47:06 +0200758 boolean popVlan, boolean install) {
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700759 ConnectPoint connectPoint = new ConnectPoint(deviceId, portNum);
760 Set<Host> hosts = hostService.getConnectedHosts(connectPoint);
761
762 if (hosts == null || hosts.size() == 0) {
Charles Chan39b75522018-04-21 00:44:29 -0700763 log.debug("processIntfVlanUpdatedEvent: No hosts connected to {}", connectPoint);
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700764 return;
765 }
766
pierventrea3989be2021-01-08 16:43:17 +0100767 List<CompletableFuture<Void>> hostFutures = Lists.newArrayList();
768 hosts.forEach(host -> hostFutures.add(hostWorkers.submit(() -> processIntfVlanUpdatedEventInternal(
769 host, deviceId, portNum, vlanId, popVlan, install), host.id().hashCode())));
770 // Let's wait for the completion of all hosts
771 try {
772 CompletableFuture.allOf(hostFutures.toArray(new CompletableFuture[0]))
773 .thenApply(objectives -> hostFutures.stream()
774 .map(CompletableFuture::join)
775 .collect(Collectors.toList())
776 )
777 .get();
778 } catch (InterruptedException | ExecutionException e) {
779 log.warn("Exception caught when executing processIntfVlanUpdatedEvent futures");
780 hostFutures.forEach(future -> future.cancel(false));
781 }
piercf557922019-05-17 20:47:06 +0200782 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700783
piercf557922019-05-17 20:47:06 +0200784 private void processIntfVlanUpdatedEventInternal(Host host, DeviceId deviceId, PortNumber portNum,
785 VlanId vlanId, boolean popVlan, boolean install) {
786 MacAddress mac = host.mac();
787 VlanId hostVlanId = host.vlan();
788
pierventrea3989be2021-01-08 16:43:17 +0100789 List<CompletableFuture<Objective>> futures = Lists.newArrayList();
Shibu Vijayakumar632dd642018-03-01 15:45:59 -0800790 if (!install) {
791 // Do not check the host validity. Just remove all rules corresponding to the vlan id
792 // Revoke forwarding objective for bridging to the host
pierventrea3989be2021-01-08 16:43:17 +0100793 futures.add(srManager.defaultRoutingHandler.updateBridging(deviceId, portNum, mac, vlanId,
794 popVlan, false));
Shibu Vijayakumar632dd642018-03-01 15:45:59 -0800795
796 // Revoke forwarding objective and corresponding simple Next objective
797 // for each Host and IP address connected to given port
798 host.ipAddresses().forEach(ipAddress ->
pierventrea3989be2021-01-08 16:43:17 +0100799 futures.add(srManager.defaultRoutingHandler.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
800 mac, vlanId, popVlan, false)));
Shibu Vijayakumar632dd642018-03-01 15:45:59 -0800801 } else {
802 // Check whether the host vlan is valid for new interface configuration
803 if ((!popVlan && hostVlanId.equals(vlanId)) ||
804 (popVlan && hostVlanId.equals(VlanId.NONE))) {
pierventrea3989be2021-01-08 16:43:17 +0100805 futures.add(srManager.defaultRoutingHandler.updateBridging(deviceId, portNum, mac, vlanId,
806 popVlan, true));
Shibu Vijayakumar632dd642018-03-01 15:45:59 -0800807 // Update Forwarding objective and corresponding simple Next objective
808 // for each Host and IP address connected to given port
809 host.ipAddresses().forEach(ipAddress ->
pierventrea3989be2021-01-08 16:43:17 +0100810 futures.add(srManager.defaultRoutingHandler.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
811 mac, vlanId, popVlan, true)));
Shibu Vijayakumar632dd642018-03-01 15:45:59 -0800812 }
piercf557922019-05-17 20:47:06 +0200813 }
pierventrea3989be2021-01-08 16:43:17 +0100814 // Let's wait for the completion of update procedure
815 try {
816 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
817 .thenApply(objectives -> futures.stream()
818 .map(CompletableFuture::join)
819 .collect(Collectors.toList())
820 )
821 .get();
822 } catch (InterruptedException | ExecutionException e) {
823 log.warn("Exception caught when executing processIntfVlanUpdatedEventInternal futures");
824 futures.forEach(future -> future.cancel(false));
825 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700826 }
827
828 /**
829 * Populate or revoke routing rule for each host, according to the updated
830 * subnet configuration on the interface.
831 * @param cp connect point of the updated interface
832 * @param ipPrefixSet IP Prefixes added or removed
833 * @param install true if IP Prefixes added, false otherwise
834 */
Charles Chana2ccb582019-11-25 09:47:22 -0800835 // TODO Current implementation does not handle dual-homed hosts with auxiliary locations.
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700836 void processIntfIpUpdatedEvent(ConnectPoint cp, Set<IpPrefix> ipPrefixSet, boolean install) {
837 Set<Host> hosts = hostService.getConnectedHosts(cp);
838
839 if (hosts == null || hosts.size() == 0) {
Charles Chan39b75522018-04-21 00:44:29 -0700840 log.debug("processIntfIpUpdatedEvent: No hosts connected to {}", cp);
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700841 return;
842 }
843
844 // Check whether the host IP address is in the interface's subnet
piercf557922019-05-17 20:47:06 +0200845 hosts.forEach(host -> hostWorkers.execute(() -> processIntfIpUpdatedEventInternal(
846 host, cp, ipPrefixSet, install)));
847 }
848
849 private void processIntfIpUpdatedEventInternal(Host host, ConnectPoint cp, Set<IpPrefix> ipPrefixSet,
850 boolean install) {
851 host.ipAddresses().forEach(hostIpAddress -> {
852 ipPrefixSet.forEach(ipPrefix -> {
853 if (install && ipPrefix.contains(hostIpAddress)) {
854 srManager.defaultRoutingHandler.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
855 host.mac(), host.vlan(), cp.port(), true);
856 } else if (!install && ipPrefix.contains(hostIpAddress)) {
857 srManager.defaultRoutingHandler.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
858 host.mac(), host.vlan(), cp.port(), true);
859 }
860 });
861 });
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700862 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800863
864 /**
865 * Returns the set of portnumbers on the given device that are part of the
866 * locations for dual-homed hosts.
867 *
868 * @param deviceId the given deviceId
869 * @return set of port numbers on given device that are dual-homed host
870 * locations. May be empty if no dual homed hosts are connected to
871 * the given device
872 */
873 Set<PortNumber> getDualHomedHostPorts(DeviceId deviceId) {
874 Set<PortNumber> dualHomedLocations = new HashSet<>();
875 srManager.hostService.getConnectedHosts(deviceId).stream()
Charles Chana2ccb582019-11-25 09:47:22 -0800876 .filter(host -> effectiveLocations(host).size() == 2)
877 .forEach(host -> effectiveLocations(host).stream()
Saurav Dase6c448a2018-01-18 12:07:33 -0800878 .filter(loc -> loc.deviceId().equals(deviceId))
879 .forEach(loc -> dualHomedLocations.add(loc.port())));
880 return dualHomedLocations;
881 }
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -0700882
883 /**
884 * Checks if the given host is double-tagged or not.
885 *
886 * @param host host to check
887 * @return true if it is double-tagged, false otherwise
888 */
889 private boolean isDoubleTaggedHost(Host host) {
890 return !host.innerVlan().equals(VlanId.NONE);
891 }
892
Charles Chana2ccb582019-11-25 09:47:22 -0800893 /**
894 * Returns effective locations of given host.
895 *
896 * @param host host to check
897 * @return auxLocations of the host if exists, or locations of the host otherwise.
898 */
899 Set<HostLocation> effectiveLocations(Host host) {
900 return (host.auxLocations() != null) ? host.auxLocations() : host.locations();
901 }
902
Charles Chan1eaf4802016-04-18 13:44:03 -0700903}