blob: c62e395d43f1c6d00de5185acf9eac84ba7af161 [file] [log] [blame]
Charles Chand2990362016-04-18 13:44:03 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chand2990362016-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
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070019import org.onlab.packet.EthType;
Charles Chand2990362016-04-18 13:44:03 -070020import org.onlab.packet.IpAddress;
Jonghwan Hyun42fe1052017-08-25 17:48:36 -070021import org.onlab.packet.IpPrefix;
Charles Chand2990362016-04-18 13:44:03 -070022import org.onlab.packet.MacAddress;
23import org.onlab.packet.VlanId;
pierff0a45a2019-05-17 20:47:06 +020024import org.onlab.util.PredictableExecutor;
Charles Chan59cc16d2017-02-02 16:20:42 -080025import org.onosproject.net.ConnectPoint;
Charles Chand2990362016-04-18 13:44:03 -070026import org.onosproject.net.DeviceId;
Charles Chan6ea94fc2016-05-10 17:29:47 -070027import org.onosproject.net.Host;
Charles Chan93e71ba2016-04-29 14:38:22 -070028import org.onosproject.net.HostLocation;
Charles Chand2990362016-04-18 13:44:03 -070029import org.onosproject.net.PortNumber;
Charles Chand2990362016-04-18 13:44:03 -070030import org.onosproject.net.host.HostEvent;
31import org.onosproject.net.host.HostService;
Charles Chanff79dd92018-06-01 16:33:48 -070032import org.onosproject.net.host.ProbeMode;
Charles Chand2990362016-04-18 13:44:03 -070033import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
35
Saurav Das018605f2017-02-18 14:05:44 -080036import com.google.common.collect.Sets;
Charles Chan65238242017-06-22 18:03:14 -070037
Saurav Das45f48152018-01-18 12:07:33 -080038import java.util.HashSet;
Charles Chan65238242017-06-22 18:03:14 -070039import java.util.Optional;
Charles Chand2990362016-04-18 13:44:03 -070040import java.util.Set;
Charles Chanf9a52702017-06-16 15:19:24 -070041import java.util.stream.Collectors;
42
43import static com.google.common.base.Preconditions.checkArgument;
pierff0a45a2019-05-17 20:47:06 +020044import static org.onlab.util.Tools.groupedThreads;
Charles Chand2990362016-04-18 13:44:03 -070045
46/**
47 * Handles host-related events.
48 */
49public class HostHandler {
50 private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
Charles Chan47933752017-11-30 15:37:50 -080051
Charles Chan2e2e3402017-06-19 14:00:53 -070052 protected final SegmentRoutingManager srManager;
Charles Chand2990362016-04-18 13:44:03 -070053 private HostService hostService;
pierff0a45a2019-05-17 20:47:06 +020054 // Host workers - 0 will leverage available processors
55 private static final int DEFAULT_THREADS = 0;
56 protected PredictableExecutor hostWorkers;
Charles Chand2990362016-04-18 13:44:03 -070057
58 /**
59 * Constructs the HostHandler.
60 *
61 * @param srManager Segment Routing manager
62 */
Charles Chanf9a52702017-06-16 15:19:24 -070063 HostHandler(SegmentRoutingManager srManager) {
Charles Chand2990362016-04-18 13:44:03 -070064 this.srManager = srManager;
Charles Chand2990362016-04-18 13:44:03 -070065 hostService = srManager.hostService;
pierff0a45a2019-05-17 20:47:06 +020066 this.hostWorkers = new PredictableExecutor(DEFAULT_THREADS,
67 groupedThreads("onos/sr", "h-worker-%d", log));
68 }
69
70 /**
71 * Shutdowns the workers.
72 */
73 void terminate() {
74 hostWorkers.shutdown();
Charles Chand2990362016-04-18 13:44:03 -070075 }
76
Charles Chan03a73e02016-10-24 14:52:01 -070077 protected void init(DeviceId devId) {
pierff0a45a2019-05-17 20:47:06 +020078 // Init hosts in parallel using hostWorkers executor
79 hostService.getHosts().forEach(
80 host -> hostWorkers.execute(() -> initHost(host, devId), host.id().hashCode())
Charles Chanf9a52702017-06-16 15:19:24 -070081 );
Charles Chan93e71ba2016-04-29 14:38:22 -070082 }
Charles Chand2990362016-04-18 13:44:03 -070083
pierff0a45a2019-05-17 20:47:06 +020084 private void initHost(Host host, DeviceId deviceId) {
85 host.locations().forEach(location -> {
86 if (location.deviceId().equals(deviceId) ||
87 location.deviceId().equals(srManager.getPairDeviceId(deviceId).orElse(null))) {
88 processHostAddedAtLocation(host, location);
89 }
90 });
91 }
92
Charles Chanf9a52702017-06-16 15:19:24 -070093 void processHostAddedEvent(HostEvent event) {
pierff0a45a2019-05-17 20:47:06 +020094 Host host = event.subject();
95 hostWorkers.execute(() -> processHostAdded(host), host.id().hashCode());
Charles Chan93e71ba2016-04-29 14:38:22 -070096 }
97
Charles Chanf9a52702017-06-16 15:19:24 -070098 private void processHostAdded(Host host) {
99 host.locations().forEach(location -> processHostAddedAtLocation(host, location));
Saurav Das45f48152018-01-18 12:07:33 -0800100 // ensure dual-homed host locations have viable uplinks
Saurav Das9a554292018-04-27 18:42:30 -0700101 if (host.locations().size() > 1 || srManager.singleHomedDown) {
Saurav Das45f48152018-01-18 12:07:33 -0800102 host.locations().forEach(loc -> {
103 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
Saurav Das9a554292018-04-27 18:42:30 -0700104 srManager.linkHandler.checkUplinksForHost(loc);
Saurav Das45f48152018-01-18 12:07:33 -0800105 }
106 });
107 }
Charles Chand2990362016-04-18 13:44:03 -0700108 }
109
Charles Chanf9a52702017-06-16 15:19:24 -0700110 void processHostAddedAtLocation(Host host, HostLocation location) {
111 checkArgument(host.locations().contains(location), "{} is not a location of {}", location, host);
112
Charles Chan8e786b52017-09-12 18:57:47 -0700113 MacAddress hostMac = host.mac();
114 VlanId hostVlanId = host.vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700115 Set<HostLocation> locations = host.locations();
116 Set<IpAddress> ips = host.ipAddresses();
Charles Chan8e786b52017-09-12 18:57:47 -0700117 log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
Charles Chanf9a52702017-06-16 15:19:24 -0700118
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700119 if (isDoubleTaggedHost(host)) {
120 ips.forEach(ip ->
121 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
122 host.innerVlan(), hostVlanId, host.tpid(), ip, false)
123 );
124 } else {
125 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
126 ips.forEach(ip ->
Charles Chan2ff1bac2018-03-29 16:03:41 -0700127 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700128 );
129 }
Charles Chan8e786b52017-09-12 18:57:47 -0700130
131 // Use the pair link temporarily before the second location of a dual-homed host shows up.
132 // This do not affect single-homed hosts since the flow will be blocked in
133 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
134 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
Charles Chan2ff1bac2018-03-29 16:03:41 -0700135 if (host.locations().stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
Saurav Das9a554292018-04-27 18:42:30 -0700136 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
Charles Chan8e786b52017-09-12 18:57:47 -0700137 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
138 // when the host is untagged
Charles Chanf76de302018-06-15 18:54:18 -0700139 VlanId vlanId = vlanForPairPort(hostVlanId, location);
140 if (vlanId == null) {
141 return;
142 }
Charles Chan8e786b52017-09-12 18:57:47 -0700143
144 processBridgingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, false);
145 ips.forEach(ip -> processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId,
146 ip, false));
Charles Chan47933752017-11-30 15:37:50 -0800147
148 if (srManager.activeProbing) {
149 probe(host, location, pairDeviceId, pairRemotePort);
150 }
Charles Chan8e786b52017-09-12 18:57:47 -0700151 });
152 }
153 });
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400154
155 int nextId = srManager.getMacVlanNextObjectiveId(location.deviceId(), hostMac, hostVlanId, null, false);
156 if (nextId != -1) {
157 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
158 log.debug(" Updating next objective for device {}, host {}/{}, port {}, nextid {}",
159 location.deviceId(), hostMac, vlanId, location.port(), nextId);
160 srManager.updateMacVlanTreatment(location.deviceId(), hostMac, vlanId,
161 location.port(), nextId);
162 }
Charles Chanf9a52702017-06-16 15:19:24 -0700163 }
164
165 void processHostRemovedEvent(HostEvent event) {
pierff0a45a2019-05-17 20:47:06 +0200166 Host host = event.subject();
167 hostWorkers.execute(() -> processHostRemoved(host), host.id().hashCode());
Charles Chan35fd1a72016-06-13 18:54:31 -0700168 }
169
Charles Chanf9a52702017-06-16 15:19:24 -0700170 private void processHostRemoved(Host host) {
Charles Chan65238242017-06-22 18:03:14 -0700171 MacAddress hostMac = host.mac();
172 VlanId hostVlanId = host.vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700173 Set<HostLocation> locations = host.locations();
Charles Chan35fd1a72016-06-13 18:54:31 -0700174 Set<IpAddress> ips = host.ipAddresses();
Charles Chan65238242017-06-22 18:03:14 -0700175 log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
Charles Chan93e71ba2016-04-29 14:38:22 -0700176
Charles Chan2fde6d42017-08-23 14:46:43 -0700177 locations.forEach(location -> {
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700178 if (isDoubleTaggedHost(host)) {
179 ips.forEach(ip ->
180 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
181 host.innerVlan(), hostVlanId, host.tpid(), ip, true)
182 );
183 } else {
184 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
185 ips.forEach(ip ->
Charles Chan2ff1bac2018-03-29 16:03:41 -0700186 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700187 );
188 }
Charles Chan65238242017-06-22 18:03:14 -0700189
190 // Also remove redirection flows on the pair device if exists.
191 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Saurav Das9a554292018-04-27 18:42:30 -0700192 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
Charles Chan2ff1bac2018-03-29 16:03:41 -0700193 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
Charles Chan65238242017-06-22 18:03:14 -0700194 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
195 // when the host is untagged
Charles Chanf76de302018-06-15 18:54:18 -0700196 VlanId vlanId = vlanForPairPort(hostVlanId, location);
197 if (vlanId == null) {
198 return;
199 }
Charles Chan65238242017-06-22 18:03:14 -0700200
201 processBridgingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId, true);
202 ips.forEach(ip ->
203 processRoutingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId,
204 ip, true));
205 }
Charles Chan4d639392018-06-21 19:07:12 -0700206
207 // Delete prefix from sr-device-subnet when the next hop host is removed
208 srManager.routeService.getRouteTables().forEach(tableId -> {
209 srManager.routeService.getRoutes(tableId).forEach(routeInfo -> {
210 if (routeInfo.allRoutes().stream().anyMatch(rr -> ips.contains(rr.nextHop()))) {
211 log.debug("HostRemoved. removeSubnet {}, {}", location, routeInfo.prefix());
212 srManager.deviceConfiguration.removeSubnet(location, routeInfo.prefix());
213 }
214 });
215 });
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400216
Charles Chanf9a52702017-06-16 15:19:24 -0700217 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700218 }
219
Charles Chanf9a52702017-06-16 15:19:24 -0700220 void processHostMovedEvent(HostEvent event) {
Charles Chan9bd0e5a2018-04-25 18:51:46 -0400221 Host host = event.subject();
pierff0a45a2019-05-17 20:47:06 +0200222 hostWorkers.execute(() -> processHostMovedEventInternal(event), host.id().hashCode());
223 }
224
225 private void processHostMovedEventInternal(HostEvent event) {
226 Host host = event.subject();
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700227 MacAddress hostMac = host.mac();
228 VlanId hostVlanId = host.vlan();
Charles Chan9bd0e5a2018-04-25 18:51:46 -0400229 Set<HostLocation> prevLocations = event.prevSubject().locations();
230 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
231 Set<HostLocation> newLocations = host.locations();
232 Set<IpAddress> newIps = host.ipAddresses();
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700233 EthType hostTpid = host.tpid();
234 boolean doubleTaggedHost = isDoubleTaggedHost(host);
Charles Chan9bd0e5a2018-04-25 18:51:46 -0400235
Charles Chan47933752017-11-30 15:37:50 -0800236 log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Charles Chanf9a52702017-06-16 15:19:24 -0700237 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
238 .collect(Collectors.toSet());
Charles Chan93e71ba2016-04-29 14:38:22 -0700239
Charles Chanf9a52702017-06-16 15:19:24 -0700240 // For each old location
Charles Chan2ff1bac2018-03-29 16:03:41 -0700241 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan65238242017-06-22 18:03:14 -0700242 // Remove routing rules for old IPs
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700243 Sets.difference(prevIps, newIps).forEach(ip -> {
244 if (doubleTaggedHost) {
245 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
246 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
247 } else {
Charles Chan65238242017-06-22 18:03:14 -0700248 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700249 ip, true);
250 }
251 });
Charles Chan65238242017-06-22 18:03:14 -0700252
253 // Redirect the flows to pair link if configured
254 // Note: Do not continue removing any rule
255 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
Saurav Das9a554292018-04-27 18:42:30 -0700256 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(prevLocation.deviceId());
Charles Chan5145de22018-10-11 12:35:36 -0700257 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
258 newLocations.stream().anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
259 newLocations.stream().noneMatch(location -> location.deviceId().equals(prevLocation.deviceId()))) {
Charles Chan65238242017-06-22 18:03:14 -0700260 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
261 // when the host is untagged
262 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
263
264 processBridgingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId, false);
265 newIps.forEach(ip ->
266 processRoutingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId,
267 ip, false));
268 return;
269 }
Charles Chanf9a52702017-06-16 15:19:24 -0700270
Charles Chan50bb6ef2018-04-18 18:41:05 -0700271 // Remove flows for unchanged IPs only when the host moves from a switch to another.
Charles Chanf9a52702017-06-16 15:19:24 -0700272 // Otherwise, do not remove and let the adding part update the old flow
273 if (!newDeviceIds.contains(prevLocation.deviceId())) {
Charles Chan65238242017-06-22 18:03:14 -0700274 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700275 Sets.intersection(prevIps, newIps).forEach(ip -> {
276 if (doubleTaggedHost) {
277 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
278 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
279 } else {
280 processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
281 hostMac, hostVlanId, ip, true);
282 }
283 });
Charles Chanf9a52702017-06-16 15:19:24 -0700284 }
285
286 // Remove bridging rules if new interface vlan is different from old interface vlan
287 // Otherwise, do not remove and let the adding part update the old flow
288 if (newLocations.stream().noneMatch(newLocation -> {
289 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
290 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
291 // Host is tagged and the new location has the host vlan in vlan-tagged
Charles Chan971d7ba2018-05-01 11:50:20 -0700292 return srManager.interfaceService.getTaggedVlanId(newLocation).contains(hostVlanId) ||
Charles Chanf9a52702017-06-16 15:19:24 -0700293 (oldAssignedVlan != null && newAssignedVlan != null &&
294 // Host is untagged and the new location has the same assigned vlan
295 oldAssignedVlan.equals(newAssignedVlan));
296 })) {
Charles Chan65238242017-06-22 18:03:14 -0700297 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chanf9a52702017-06-16 15:19:24 -0700298 }
299
300 // Remove routing rules for unchanged IPs if none of the subnet of new location contains
301 // the IP. Otherwise, do not remove and let the adding part update the old flow
302 Sets.intersection(prevIps, newIps).forEach(ip -> {
303 if (newLocations.stream().noneMatch(newLocation ->
304 srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700305 if (doubleTaggedHost) {
306 processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
307 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
308 } else {
309 processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
310 hostMac, hostVlanId, ip, true);
311 }
Charles Chanf9a52702017-06-16 15:19:24 -0700312 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700313 });
Charles Chanf9a52702017-06-16 15:19:24 -0700314 });
315
316 // For each new location, add all new IPs.
Charles Chan50bb6ef2018-04-18 18:41:05 -0700317 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan65238242017-06-22 18:03:14 -0700318 processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700319 newIps.forEach(ip -> {
320 if (doubleTaggedHost) {
321 processDoubleTaggedRoutingRule(newLocation.deviceId(), newLocation.port(),
322 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
323 } else {
Charles Chan65238242017-06-22 18:03:14 -0700324 processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700325 ip, false);
326 }
327 });
Charles Chan9bd0e5a2018-04-25 18:51:46 -0400328
329 // Probe on pair device when host move
330 // Majorly for the 2nd step of [1A/x, 1B/x] -> [1A/x, 1B/y] -> [1A/y, 1B/y]
331 // But will also cover [1A/x] -> [1A/y] -> [1A/y, 1B/y]
332 if (srManager.activeProbing) {
Charles Chanf82d1552018-07-23 15:27:34 -0700333
Charles Chan9bd0e5a2018-04-25 18:51:46 -0400334 srManager.getPairDeviceId(newLocation.deviceId()).ifPresent(pairDeviceId ->
335 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort ->
336 probe(host, newLocation, pairDeviceId, pairRemotePort)
337 )
338 );
339 }
Charles Chanf9a52702017-06-16 15:19:24 -0700340 });
341
342 // For each unchanged location, add new IPs and remove old IPs.
Charles Chan50bb6ef2018-04-18 18:41:05 -0700343 Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700344 Sets.difference(prevIps, newIps).forEach(ip -> {
345 if (doubleTaggedHost) {
346 processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
347 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
348 } else {
349 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
350 hostMac, hostVlanId, ip, true);
351 }
352 });
Charles Chanf9a52702017-06-16 15:19:24 -0700353
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700354 Sets.difference(newIps, prevIps).forEach(ip -> {
355 if (doubleTaggedHost) {
356 processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
357 hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
358 } else {
359 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
360 hostMac, hostVlanId, ip, false);
361 }
362 });
Charles Chanf82d1552018-07-23 15:27:34 -0700363
364 // Verify existing location and see if it is still valid
365 srManager.probingService.probeHost(host, unchangedLocation, ProbeMode.VERIFY);
Charles Chanf9a52702017-06-16 15:19:24 -0700366 });
Saurav Das45f48152018-01-18 12:07:33 -0800367
368 // ensure dual-homed host locations have viable uplinks
Saurav Das9a554292018-04-27 18:42:30 -0700369 if (newLocations.size() > prevLocations.size() || srManager.singleHomedDown) {
Saurav Das45f48152018-01-18 12:07:33 -0800370 newLocations.forEach(loc -> {
371 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
Saurav Das9a554292018-04-27 18:42:30 -0700372 srManager.linkHandler.checkUplinksForHost(loc);
Saurav Das45f48152018-01-18 12:07:33 -0800373 }
374 });
375 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700376 }
377
Charles Chanf9a52702017-06-16 15:19:24 -0700378 void processHostUpdatedEvent(HostEvent event) {
Charles Chan47933752017-11-30 15:37:50 -0800379 Host host = event.subject();
pierff0a45a2019-05-17 20:47:06 +0200380 hostWorkers.execute(() -> processHostUpdatedEventInternal(event), host.id().hashCode());
381 }
382
383 private void processHostUpdatedEventInternal(HostEvent event) {
384 Host host = event.subject();
Charles Chan47933752017-11-30 15:37:50 -0800385 MacAddress hostMac = host.mac();
386 VlanId hostVlanId = host.vlan();
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700387 EthType hostTpid = host.tpid();
Charles Chan47933752017-11-30 15:37:50 -0800388 Set<HostLocation> locations = host.locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700389 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chan47933752017-11-30 15:37:50 -0800390 Set<IpAddress> newIps = host.ipAddresses();
Charles Chan3d650a62017-11-20 08:46:24 -0800391 log.info("Host {}/{} is updated", hostMac, hostVlanId);
Charles Chan93e71ba2016-04-29 14:38:22 -0700392
Charles Chan2ff1bac2018-03-29 16:03:41 -0700393 locations.forEach(location -> {
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700394 Sets.difference(prevIps, newIps).forEach(ip -> {
395 if (isDoubleTaggedHost(host)) {
396 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
397 host.innerVlan(), hostVlanId, hostTpid, ip, true);
398 } else {
399 processRoutingRule(location.deviceId(), location.port(), hostMac,
400 hostVlanId, ip, true);
401 }
402 });
403 Sets.difference(newIps, prevIps).forEach(ip -> {
404 if (isDoubleTaggedHost(host)) {
405 processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
406 host.innerVlan(), hostVlanId, hostTpid, ip, false);
407 } else {
408 processRoutingRule(location.deviceId(), location.port(), hostMac,
409 hostVlanId, ip, false);
410 }
411 });
Charles Chanf9a52702017-06-16 15:19:24 -0700412 });
Charles Chan3d650a62017-11-20 08:46:24 -0800413
414 // Use the pair link temporarily before the second location of a dual-homed host shows up.
415 // This do not affect single-homed hosts since the flow will be blocked in
416 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
Charles Chan47933752017-11-30 15:37:50 -0800417 locations.forEach(location ->
Charles Chan3d650a62017-11-20 08:46:24 -0800418 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
Charles Chan2ff1bac2018-03-29 16:03:41 -0700419 if (locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
Charles Chan47933752017-11-30 15:37:50 -0800420 Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
421 Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
422
Saurav Das9a554292018-04-27 18:42:30 -0700423 srManager.getPairLocalPort(pairDeviceId).ifPresent(pairRemotePort -> {
Charles Chan3d650a62017-11-20 08:46:24 -0800424 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
425 // when the host is untagged
Charles Chanf76de302018-06-15 18:54:18 -0700426 VlanId vlanId = vlanForPairPort(hostVlanId, location);
427 if (vlanId == null) {
428 return;
429 }
Charles Chan3d650a62017-11-20 08:46:24 -0800430
Charles Chan47933752017-11-30 15:37:50 -0800431 ipsToRemove.forEach(ip ->
Charles Chan3d650a62017-11-20 08:46:24 -0800432 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, true)
433 );
Charles Chan47933752017-11-30 15:37:50 -0800434 ipsToAdd.forEach(ip ->
Charles Chan3d650a62017-11-20 08:46:24 -0800435 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
436 );
Charles Chan47933752017-11-30 15:37:50 -0800437
438 if (srManager.activeProbing) {
439 probe(host, location, pairDeviceId, pairRemotePort);
440 }
Charles Chan3d650a62017-11-20 08:46:24 -0800441 });
442 }
Charles Chan47933752017-11-30 15:37:50 -0800443 })
444 );
445 }
446
447 /**
448 * When a non-pair port comes up, probe each host on the pair device if
449 * (1) the host is tagged and the tagged vlan of current port contains host vlan; or
450 * (2) the host is untagged and the internal vlan is the same on the host port and current port.
451 *
452 * @param cp connect point
453 */
454 void processPortUp(ConnectPoint cp) {
Saurav Das9a554292018-04-27 18:42:30 -0700455 if (cp.port().equals(srManager.getPairLocalPort(cp.deviceId()).orElse(null))) {
Charles Chan47933752017-11-30 15:37:50 -0800456 return;
457 }
458 if (srManager.activeProbing) {
459 srManager.getPairDeviceId(cp.deviceId())
pierff0a45a2019-05-17 20:47:06 +0200460 .ifPresent(pairDeviceId -> srManager.hostService.getConnectedHosts(pairDeviceId).forEach(
461 host -> hostWorkers.execute(() -> probingIfNecessary(host, pairDeviceId, cp),
462 host.id().hashCode())));
463 }
464 }
465
466 private void probingIfNecessary(Host host, DeviceId pairDeviceId, ConnectPoint cp) {
467 if (isHostInVlanOfPort(host, pairDeviceId, cp)) {
468 srManager.probingService.probeHost(host, cp, ProbeMode.DISCOVER);
Charles Chan47933752017-11-30 15:37:50 -0800469 }
470 }
471
472 /**
473 * Checks if given host located on given device id matches VLAN config of current port.
474 *
475 * @param host host to check
476 * @param deviceId device id to check
477 * @param cp current connect point
478 * @return true if the host located at deviceId matches the VLAN config on cp
479 */
480 private boolean isHostInVlanOfPort(Host host, DeviceId deviceId, ConnectPoint cp) {
481 VlanId internalVlan = srManager.getInternalVlanId(cp);
Charles Chan971d7ba2018-05-01 11:50:20 -0700482 Set<VlanId> taggedVlan = srManager.interfaceService.getTaggedVlanId(cp);
Charles Chan47933752017-11-30 15:37:50 -0800483
484 return taggedVlan.contains(host.vlan()) ||
485 (internalVlan != null && host.locations().stream()
486 .filter(l -> l.deviceId().equals(deviceId))
487 .map(srManager::getInternalVlanId)
488 .anyMatch(internalVlan::equals));
489 }
490
491 /**
492 * Send a probe on all locations with the same VLAN on pair device, excluding pair port.
493 *
494 * @param host host to probe
495 * @param location newly discovered host location
496 * @param pairDeviceId pair device id
497 * @param pairRemotePort pair remote port
498 */
499 private void probe(Host host, ConnectPoint location, DeviceId pairDeviceId, PortNumber pairRemotePort) {
Mayank Tiwaria61b0a82018-10-29 18:27:35 -0400500 //Check if the host still exists in the host store
501 if (hostService.getHost(host.id()) == null) {
502 log.debug("Host entry for host {} no more present. Aborting hostprobe discover for this host", host.id());
503 return;
504 }
505
Charles Chan47933752017-11-30 15:37:50 -0800506 VlanId vlanToProbe = host.vlan().equals(VlanId.NONE) ?
507 srManager.getInternalVlanId(location) : host.vlan();
Mayank Tiwaria61b0a82018-10-29 18:27:35 -0400508 if (srManager.symmetricProbing) {
509 srManager.interfaceService.getInterfaces().stream()
510 .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
511 i.vlanUntagged().equals(vlanToProbe) ||
512 i.vlanNative().equals(vlanToProbe))
513 .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
514 .filter(i -> i.connectPoint().port().equals(location.port()))
515 .forEach(i -> {
516 log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
517 srManager.probingService.probeHost(host, i.connectPoint(), ProbeMode.DISCOVER);
518 });
519 } else {
520 srManager.interfaceService.getInterfaces().stream()
521 .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
522 i.vlanUntagged().equals(vlanToProbe) ||
523 i.vlanNative().equals(vlanToProbe))
524 .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
525 .filter(i -> !i.connectPoint().port().equals(pairRemotePort))
526 .forEach(i -> {
527 log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
528 srManager.probingService.probeHost(host, i.connectPoint(), ProbeMode.DISCOVER);
529 });
530 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700531 }
532
533 /**
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700534 * Populates or revokes a bridging rule on given deviceId that matches given mac,
535 * given vlan and output to given port.
Charles Chanb7b4c932017-06-15 19:25:25 -0700536 *
537 * @param deviceId device ID
538 * @param port port
539 * @param mac mac address
540 * @param vlanId VLAN ID
541 * @param revoke true to revoke the rule; false to populate
542 */
543 private void processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
544 VlanId vlanId, boolean revoke) {
Charles Chan2ff1bac2018-03-29 16:03:41 -0700545 log.info("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
Charles Chanb7b4c932017-06-15 19:25:25 -0700546 mac, vlanId, deviceId, port);
547
Charles Chan2ff1bac2018-03-29 16:03:41 -0700548 if (!revoke) {
549 srManager.defaultRoutingHandler.populateBridging(deviceId, port, mac, vlanId);
550 } else {
551 srManager.defaultRoutingHandler.revokeBridging(deviceId, port, mac, vlanId);
Charles Chanb7b4c932017-06-15 19:25:25 -0700552 }
Charles Chanb7b4c932017-06-15 19:25:25 -0700553 }
554
555 /**
556 * Populate or revoke a routing rule on given deviceId that matches given ip,
557 * set destination mac to given mac, set vlan to given vlan and output to given port.
558 *
559 * @param deviceId device ID
560 * @param port port
561 * @param mac mac address
562 * @param vlanId VLAN ID
563 * @param ip IP address
564 * @param revoke true to revoke the rule; false to populate
565 */
566 private void processRoutingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
567 VlanId vlanId, IpAddress ip, boolean revoke) {
568 ConnectPoint location = new ConnectPoint(deviceId, port);
Charles Chanf9a52702017-06-16 15:19:24 -0700569 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
570 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
571 return;
Charles Chanb7b4c932017-06-15 19:25:25 -0700572 }
Charles Chanb7b4c932017-06-15 19:25:25 -0700573
Charles Chanf9a52702017-06-16 15:19:24 -0700574 log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
575 if (revoke) {
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000576 srManager.defaultRoutingHandler.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
Charles Chanf9a52702017-06-16 15:19:24 -0700577 } else {
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000578 srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
Charles Chan6ea94fc2016-05-10 17:29:47 -0700579 }
Charles Chan6ea94fc2016-05-10 17:29:47 -0700580 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700581
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700582 /**
583 * Populate or revoke a routing rule and egress rules on given deviceId that matches given IP,
584 * to set destination mac to given mac, set inner vlan and outer vlan to given vlans,
585 * set outer TPID, and output to given port.
586 *
587 * @param deviceId device ID
588 * @param port port
589 * @param mac mac address
590 * @param innerVlan inner VLAN ID
591 * @param outerVlan outer VLAN ID
592 * @param outerTpid outer TPID
593 * @param ip IP address
594 * @param revoke true to revoke the rule; false to populate
595 */
596 private void processDoubleTaggedRoutingRule(DeviceId deviceId, PortNumber port,
597 MacAddress mac, VlanId innerVlan,
598 VlanId outerVlan, EthType outerTpid,
599 IpAddress ip, boolean revoke) {
Charles Chanb67dfdd2018-07-24 16:40:35 -0700600 if (!srManager.routeDoubleTaggedHosts) {
601 log.debug("Routing for double tagged host is disabled. Ignore {}/{}/{}", mac, outerVlan, innerVlan);
Charles Chan57465d32018-07-19 14:55:31 -0700602 return;
603 }
604
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700605 ConnectPoint location = new ConnectPoint(deviceId, port);
606 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
607 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
608 return;
609 }
610 log.info("{} routing rule for double-tagged host {} at {}",
611 revoke ? "Revoking" : "Populating", ip, location);
612 if (revoke) {
613 srManager.defaultRoutingHandler.revokeDoubleTaggedRoute(
614 deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
615 } else {
616 srManager.defaultRoutingHandler.populateDoubleTaggedRoute(
617 deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
618 }
619 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700620
Charles Chan57465d32018-07-19 14:55:31 -0700621 void populateAllDoubleTaggedHost() {
Charles Chanb67dfdd2018-07-24 16:40:35 -0700622 log.info("Enabling routing for all double tagged hosts");
Charles Chan57465d32018-07-19 14:55:31 -0700623 Sets.newHashSet(srManager.hostService.getHosts()).stream().filter(this::isDoubleTaggedHost)
624 .forEach(h -> h.locations().forEach(l ->
625 h.ipAddresses().forEach(i ->
626 processDoubleTaggedRoutingRule(l.deviceId(), l.port(), h.mac(), h.innerVlan(),
627 h.vlan(), h.tpid(), i, false)
628 )
629 )
630 );
631 }
632
633 void revokeAllDoubleTaggedHost() {
Charles Chanb67dfdd2018-07-24 16:40:35 -0700634 log.info("Disabling routing for all double tagged hosts");
Charles Chan57465d32018-07-19 14:55:31 -0700635 Sets.newHashSet(srManager.hostService.getHosts()).stream().filter(this::isDoubleTaggedHost)
636 .forEach(h -> h.locations().forEach(l ->
637 h.ipAddresses().forEach(i ->
638 processDoubleTaggedRoutingRule(l.deviceId(), l.port(), h.mac(), h.innerVlan(),
639 h.vlan(), h.tpid(), i, true)
640 )
641 )
642 );
643 }
644
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700645 /**
Charles Chanf76de302018-06-15 18:54:18 -0700646 * Returns VLAN ID to be used to program redirection flow on pair port.
647 *
648 * @param hostVlanId host VLAN ID
649 * @param location host location
650 * @return VLAN ID to be used; Or null if host VLAN does not match the interface config
651 */
652 VlanId vlanForPairPort(VlanId hostVlanId, ConnectPoint location) {
653 VlanId internalVlan = srManager.getInternalVlanId(location);
654 Set<VlanId> taggedVlan = srManager.interfaceService.getTaggedVlanId(location);
655
656 if (!hostVlanId.equals(VlanId.NONE) && taggedVlan.contains(hostVlanId)) {
657 return hostVlanId;
658 } else if (hostVlanId.equals(VlanId.NONE) && internalVlan != null) {
659 return internalVlan;
660 } else {
661 log.warn("VLAN mismatch. hostVlan={}, location={}, internalVlan={}, taggedVlan={}",
662 hostVlanId, location, internalVlan, taggedVlan);
663 return null;
664 }
665 }
666
667 /**
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700668 * Update forwarding objective for unicast bridging and unicast routing.
669 * Also check the validity of updated interface configuration on VLAN.
670 *
671 * @param deviceId device ID that host attaches to
672 * @param portNum port number that host attaches to
673 * @param vlanId Vlan ID configured on the switch port
674 * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
675 * @param install true to populate the objective, false to revoke
676 */
677 void processIntfVlanUpdatedEvent(DeviceId deviceId, PortNumber portNum, VlanId vlanId,
pierff0a45a2019-05-17 20:47:06 +0200678 boolean popVlan, boolean install) {
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700679 ConnectPoint connectPoint = new ConnectPoint(deviceId, portNum);
680 Set<Host> hosts = hostService.getConnectedHosts(connectPoint);
681
682 if (hosts == null || hosts.size() == 0) {
Charles Chan10b2fee2018-04-21 00:44:29 -0700683 log.debug("processIntfVlanUpdatedEvent: No hosts connected to {}", connectPoint);
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700684 return;
685 }
686
pierff0a45a2019-05-17 20:47:06 +0200687 hosts.forEach(host -> hostWorkers.execute(() -> processIntfVlanUpdatedEventInternal(
688 host, deviceId, portNum, vlanId, popVlan, install), host.id().hashCode()));
689 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700690
pierff0a45a2019-05-17 20:47:06 +0200691 private void processIntfVlanUpdatedEventInternal(Host host, DeviceId deviceId, PortNumber portNum,
692 VlanId vlanId, boolean popVlan, boolean install) {
693 MacAddress mac = host.mac();
694 VlanId hostVlanId = host.vlan();
695
696 // Check whether the host vlan is valid for new interface configuration
697 if ((!popVlan && hostVlanId.equals(vlanId)) ||
698 (popVlan && hostVlanId.equals(VlanId.NONE))) {
699 srManager.defaultRoutingHandler.updateBridging(deviceId, portNum, mac, vlanId, popVlan, install);
700 // Update Forwarding objective and corresponding simple Next objective
701 // for each host and IP address connected to given port
702 host.ipAddresses().forEach(ipAddress -> srManager.defaultRoutingHandler.updateFwdObj(
703 deviceId, portNum, ipAddress.toIpPrefix(), mac, vlanId, popVlan, install)
704 );
705 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700706 }
707
708 /**
709 * Populate or revoke routing rule for each host, according to the updated
710 * subnet configuration on the interface.
711 * @param cp connect point of the updated interface
712 * @param ipPrefixSet IP Prefixes added or removed
713 * @param install true if IP Prefixes added, false otherwise
714 */
715 void processIntfIpUpdatedEvent(ConnectPoint cp, Set<IpPrefix> ipPrefixSet, boolean install) {
716 Set<Host> hosts = hostService.getConnectedHosts(cp);
717
718 if (hosts == null || hosts.size() == 0) {
Charles Chan10b2fee2018-04-21 00:44:29 -0700719 log.debug("processIntfIpUpdatedEvent: No hosts connected to {}", cp);
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700720 return;
721 }
722
723 // Check whether the host IP address is in the interface's subnet
pierff0a45a2019-05-17 20:47:06 +0200724 hosts.forEach(host -> hostWorkers.execute(() -> processIntfIpUpdatedEventInternal(
725 host, cp, ipPrefixSet, install)));
726 }
727
728 private void processIntfIpUpdatedEventInternal(Host host, ConnectPoint cp, Set<IpPrefix> ipPrefixSet,
729 boolean install) {
730 host.ipAddresses().forEach(hostIpAddress -> {
731 ipPrefixSet.forEach(ipPrefix -> {
732 if (install && ipPrefix.contains(hostIpAddress)) {
733 srManager.defaultRoutingHandler.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
734 host.mac(), host.vlan(), cp.port(), true);
735 } else if (!install && ipPrefix.contains(hostIpAddress)) {
736 srManager.defaultRoutingHandler.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
737 host.mac(), host.vlan(), cp.port(), true);
738 }
739 });
740 });
Jonghwan Hyun42fe1052017-08-25 17:48:36 -0700741 }
Saurav Das45f48152018-01-18 12:07:33 -0800742
743 /**
744 * Returns the set of portnumbers on the given device that are part of the
745 * locations for dual-homed hosts.
746 *
747 * @param deviceId the given deviceId
748 * @return set of port numbers on given device that are dual-homed host
749 * locations. May be empty if no dual homed hosts are connected to
750 * the given device
751 */
752 Set<PortNumber> getDualHomedHostPorts(DeviceId deviceId) {
753 Set<PortNumber> dualHomedLocations = new HashSet<>();
754 srManager.hostService.getConnectedHosts(deviceId).stream()
755 .filter(host -> host.locations().size() == 2)
756 .forEach(host -> host.locations().stream()
757 .filter(loc -> loc.deviceId().equals(deviceId))
758 .forEach(loc -> dualHomedLocations.add(loc.port())));
759 return dualHomedLocations;
760 }
Jonghwan Hyun800d9d02018-04-09 09:40:50 -0700761
762 /**
763 * Checks if the given host is double-tagged or not.
764 *
765 * @param host host to check
766 * @return true if it is double-tagged, false otherwise
767 */
768 private boolean isDoubleTaggedHost(Host host) {
769 return !host.innerVlan().equals(VlanId.NONE);
770 }
771
Charles Chand2990362016-04-18 13:44:03 -0700772}