blob: 396b9989c79240e27c3e15077d794132f32147f3 [file] [log] [blame]
Pier7b657162018-03-27 11:29:42 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
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.mcast;
18
pier9e02ab72020-02-12 20:40:55 +010019import com.google.common.collect.ImmutableList;
Pierdb27b8d2018-04-17 16:29:56 +020020import com.google.common.collect.ImmutableMap;
Pier7b657162018-03-27 11:29:42 -070021import com.google.common.collect.ImmutableSet;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070022import com.google.common.collect.Lists;
Pierdb27b8d2018-04-17 16:29:56 +020023import com.google.common.collect.Maps;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070024import com.google.common.collect.Sets;
Pierdb27b8d2018-04-17 16:29:56 +020025import com.google.common.hash.HashFunction;
26import com.google.common.hash.Hashing;
Pier7b657162018-03-27 11:29:42 -070027import org.onlab.packet.Ethernet;
28import org.onlab.packet.IpAddress;
29import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
31import org.onosproject.cluster.NodeId;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.mcast.api.McastRoute;
34import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.HostId;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070037import org.onosproject.net.Link;
pier9e02ab72020-02-12 20:40:55 +010038import org.onosproject.net.Path;
Pier7b657162018-03-27 11:29:42 -070039import org.onosproject.net.PortNumber;
40import org.onosproject.net.config.basics.McastConfig;
41import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.TrafficSelector;
44import org.onosproject.net.flow.TrafficTreatment;
45import org.onosproject.net.flow.criteria.Criteria;
Pier7b657162018-03-27 11:29:42 -070046import org.onosproject.net.flow.instructions.Instructions;
47import org.onosproject.net.flowobjective.DefaultFilteringObjective;
48import org.onosproject.net.flowobjective.DefaultForwardingObjective;
49import org.onosproject.net.flowobjective.DefaultNextObjective;
50import org.onosproject.net.flowobjective.DefaultObjectiveContext;
51import org.onosproject.net.flowobjective.FilteringObjective;
52import org.onosproject.net.flowobjective.ForwardingObjective;
53import org.onosproject.net.flowobjective.NextObjective;
54import org.onosproject.net.flowobjective.ObjectiveContext;
pier9e02ab72020-02-12 20:40:55 +010055import org.onosproject.net.topology.LinkWeigher;
56import org.onosproject.net.topology.Topology;
57import org.onosproject.net.topology.TopologyService;
58import org.onosproject.segmentrouting.SRLinkWeigher;
Pier7b657162018-03-27 11:29:42 -070059import org.onosproject.segmentrouting.SegmentRoutingManager;
60import org.onosproject.segmentrouting.SegmentRoutingService;
61import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
62import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
63import org.slf4j.Logger;
64
65import java.util.Collection;
pier9e02ab72020-02-12 20:40:55 +010066import java.util.Collections;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070067import java.util.List;
Pier7b657162018-03-27 11:29:42 -070068import java.util.Map;
pier9e02ab72020-02-12 20:40:55 +010069import java.util.Optional;
Pier7b657162018-03-27 11:29:42 -070070import java.util.Set;
71import java.util.stream.Collectors;
72
Pier7b657162018-03-27 11:29:42 -070073/**
74 * Utility class for Multicast Handler.
75 */
76class McastUtils {
Pier7b657162018-03-27 11:29:42 -070077 // Internal reference to the log
78 private final Logger log;
pier9e02ab72020-02-12 20:40:55 +010079 // Internal reference to SR Manager and topology service
80 private final SegmentRoutingManager srManager;
81 private final TopologyService topologyService;
Pier7b657162018-03-27 11:29:42 -070082 // Internal reference to the app id
83 private ApplicationId coreAppId;
Pierdb27b8d2018-04-17 16:29:56 +020084 // Hashing function for the multicast hasher
85 private static final HashFunction HASH_FN = Hashing.md5();
86 // Read only cache of the Mcast leader
87 private Map<IpAddress, NodeId> mcastLeaderCache;
Pier7b657162018-03-27 11:29:42 -070088
89 /**
90 * Builds a new McastUtils object.
91 *
92 * @param srManager the SR manager
93 * @param coreAppId the core application id
94 * @param log log reference of the McastHandler
95 */
96 McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
97 this.srManager = srManager;
pier9e02ab72020-02-12 20:40:55 +010098 this.topologyService = srManager.topologyService;
Pier7b657162018-03-27 11:29:42 -070099 this.coreAppId = coreAppId;
100 this.log = log;
Pierdb27b8d2018-04-17 16:29:56 +0200101 this.mcastLeaderCache = Maps.newConcurrentMap();
Pier7b657162018-03-27 11:29:42 -0700102 }
103
104 /**
Pier72d0e582018-04-20 14:14:34 +0200105 * Clean up when deactivating the application.
106 */
pier9e02ab72020-02-12 20:40:55 +0100107 void terminate() {
Pier72d0e582018-04-20 14:14:34 +0200108 mcastLeaderCache.clear();
109 }
110
111 /**
Pier7b657162018-03-27 11:29:42 -0700112 * Get router mac using application config and the connect point.
113 *
114 * @param deviceId the device id
115 * @param port the port number
116 * @return the router mac if the port is configured, otherwise null
117 */
118 private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
119 // Do nothing if the port is configured as suppressed
120 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
121 SegmentRoutingAppConfig appConfig = srManager.cfgService
122 .getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
123 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
124 log.info("Ignore suppressed port {}", connectPoint);
125 return MacAddress.NONE;
126 }
127 // Get the router mac using the device configuration
128 MacAddress routerMac;
129 try {
130 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
131 } catch (DeviceConfigNotFoundException dcnfe) {
Esin Karaman93b28892019-07-24 11:26:39 +0000132 log.warn("Failed to get device MAC since the device {} is not configured", deviceId);
133 return null;
Pier7b657162018-03-27 11:29:42 -0700134 }
135 return routerMac;
136 }
137
138 /**
139 * Adds filtering objective for given device and port.
140 *
141 * @param deviceId device ID
142 * @param port ingress port number
143 * @param assignedVlan assigned VLAN ID
144 * @param mcastIp the group address
145 * @param mcastRole the role of the device
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000146 * @param matchOnMac match or not on macaddress
Pier7b657162018-03-27 11:29:42 -0700147 */
148 void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000149 IpAddress mcastIp, McastRole mcastRole, boolean matchOnMac) {
Vignesh Ethiraj75790122019-08-26 12:18:42 +0000150 if (!srManager.deviceConfiguration().isConfigured(deviceId)) {
151 log.debug("skip update of fitering objective for unconfigured device: {}", deviceId);
152 return;
153 }
Pier7b657162018-03-27 11:29:42 -0700154 MacAddress routerMac = getRouterMac(deviceId, port);
Esin Karaman93b28892019-07-24 11:26:39 +0000155
156 if (MacAddress.NONE.equals(routerMac)) {
Pier7b657162018-03-27 11:29:42 -0700157 return;
158 }
Pier7b657162018-03-27 11:29:42 -0700159 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000160 routerMac, mcastRole, matchOnMac);
Pier7b657162018-03-27 11:29:42 -0700161 ObjectiveContext context = new DefaultObjectiveContext(
162 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
163 deviceId, port.toLong(), assignedVlan),
164 (objective, error) ->
165 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
166 deviceId, port.toLong(), assignedVlan, error));
167 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
168 }
169
170 /**
171 * Removes filtering objective for given device and port.
172 *
173 * @param deviceId device ID
174 * @param port ingress port number
175 * @param assignedVlan assigned VLAN ID
176 * @param mcastIp multicast IP address
177 * @param mcastRole the multicast role of the device
178 */
179 void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
180 IpAddress mcastIp, McastRole mcastRole) {
Vignesh Ethiraj75790122019-08-26 12:18:42 +0000181 if (!srManager.deviceConfiguration().isConfigured(deviceId)) {
182 log.debug("skip update of fitering objective for unconfigured device: {}", deviceId);
183 return;
184 }
Pier7b657162018-03-27 11:29:42 -0700185 MacAddress routerMac = getRouterMac(deviceId, port);
Esin Karaman93b28892019-07-24 11:26:39 +0000186
187 if (MacAddress.NONE.equals(routerMac)) {
Pier7b657162018-03-27 11:29:42 -0700188 return;
189 }
Pier7b657162018-03-27 11:29:42 -0700190 FilteringObjective.Builder filtObjBuilder =
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000191 filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole, false);
Pier7b657162018-03-27 11:29:42 -0700192 ObjectiveContext context = new DefaultObjectiveContext(
193 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
194 deviceId, port.toLong(), assignedVlan),
195 (objective, error) ->
196 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
197 deviceId, port.toLong(), assignedVlan, error));
198 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
199 }
200
201 /**
Pier7b657162018-03-27 11:29:42 -0700202 * Gets ingress VLAN from McastConfig.
203 *
204 * @return ingress VLAN or VlanId.NONE if not configured
205 */
206 private VlanId ingressVlan() {
207 McastConfig mcastConfig =
208 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
209 return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
210 }
211
212 /**
213 * Gets egress VLAN from McastConfig.
214 *
215 * @return egress VLAN or VlanId.NONE if not configured
216 */
217 private VlanId egressVlan() {
218 McastConfig mcastConfig =
219 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
220 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
221 }
222
223 /**
224 * Gets assigned VLAN according to the value of egress VLAN.
225 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
226 *
227 * @param cp connect point; Can be null if not specified
228 * @return assigned VLAN ID
229 */
230 VlanId assignedVlan(ConnectPoint cp) {
231 // Use the egressVlan if it is tagged
232 if (!egressVlan().equals(VlanId.NONE)) {
233 return egressVlan();
234 }
235 // Reuse unicast VLAN if the port has subnet configured
236 if (cp != null) {
237 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Saurav Das9bf49582018-08-13 15:34:26 -0700238 return (untaggedVlan != null) ? untaggedVlan
239 : srManager.getDefaultInternalVlan();
Pier7b657162018-03-27 11:29:42 -0700240 }
241 // Use DEFAULT_VLAN if none of the above matches
Saurav Das9bf49582018-08-13 15:34:26 -0700242 return srManager.getDefaultInternalVlan();
Pier7b657162018-03-27 11:29:42 -0700243 }
244
pier9e02ab72020-02-12 20:40:55 +0100245
246
Pier7b657162018-03-27 11:29:42 -0700247 /**
Pier71c55772018-04-17 17:25:22 +0200248 * Gets sources connect points of given multicast group.
249 *
250 * @param mcastIp multicast IP
251 * @return sources connect points or empty set if not found
252 */
253 Set<ConnectPoint> getSources(IpAddress mcastIp) {
Piere99511d2018-04-19 16:47:06 +0200254 // TODO we should support different types of routes
Pier71c55772018-04-17 17:25:22 +0200255 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
256 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
257 .findFirst().orElse(null);
258 return mcastRoute == null ? ImmutableSet.of() :
259 srManager.multicastRouteService.sources(mcastRoute);
260 }
261
262 /**
Pier7b657162018-03-27 11:29:42 -0700263 * Gets sinks of given multicast group.
264 *
265 * @param mcastIp multicast IP
266 * @return map of sinks or empty map if not found
267 */
268 Map<HostId, Set<ConnectPoint>> getSinks(IpAddress mcastIp) {
Piere99511d2018-04-19 16:47:06 +0200269 // TODO we should support different types of routes
Pier7b657162018-03-27 11:29:42 -0700270 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
271 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
272 .findFirst().orElse(null);
273 return mcastRoute == null ?
Pier71c55772018-04-17 17:25:22 +0200274 ImmutableMap.of() :
Pier7b657162018-03-27 11:29:42 -0700275 srManager.multicastRouteService.routeData(mcastRoute).sinks();
276 }
277
278 /**
279 * Get sinks affected by this egress device.
280 *
281 * @param egressDevice the egress device
282 * @param mcastIp the mcast ip address
283 * @return the map of the sinks affected
284 */
285 Map<HostId, Set<ConnectPoint>> getAffectedSinks(DeviceId egressDevice,
Piere99511d2018-04-19 16:47:06 +0200286 IpAddress mcastIp) {
Pier7b657162018-03-27 11:29:42 -0700287 return getSinks(mcastIp).entrySet()
288 .stream()
289 .filter(hostIdSetEntry -> hostIdSetEntry.getValue().stream()
290 .map(ConnectPoint::deviceId)
291 .anyMatch(deviceId -> deviceId.equals(egressDevice))
292 ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
293 }
294
295 /**
296 * Creates a next objective builder for multicast.
297 *
298 * @param mcastIp multicast group
299 * @param assignedVlan assigned VLAN ID
300 * @param outPorts set of output port numbers
301 * @param nextId the next id
302 * @return next objective builder
303 */
304 NextObjective.Builder nextObjBuilder(IpAddress mcastIp, VlanId assignedVlan,
305 Set<PortNumber> outPorts, Integer nextId) {
306 // If nextId is null allocate a new one
307 if (nextId == null) {
308 nextId = srManager.flowObjectiveService.allocateNextId();
309 }
310 // Build the meta selector with the fwd objective info
311 TrafficSelector metadata =
312 DefaultTrafficSelector.builder()
313 .matchVlanId(assignedVlan)
314 .matchIPDst(mcastIp.toIpPrefix())
315 .build();
316 // Define the nextobjective type
317 NextObjective.Builder nextObjBuilder = DefaultNextObjective
318 .builder().withId(nextId)
319 .withType(NextObjective.Type.BROADCAST)
320 .fromApp(srManager.appId())
321 .withMeta(metadata);
322 // Add the output ports
323 outPorts.forEach(port -> {
324 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
325 if (egressVlan().equals(VlanId.NONE)) {
326 tBuilder.popVlan();
327 }
328 tBuilder.setOutput(port);
329 nextObjBuilder.addTreatment(tBuilder.build());
330 });
331 // Done return the complete builder
332 return nextObjBuilder;
333 }
334
335 /**
336 * Creates a forwarding objective builder for multicast.
337 *
338 * @param mcastIp multicast group
339 * @param assignedVlan assigned VLAN ID
340 * @param nextId next ID of the L3 multicast group
341 * @return forwarding objective builder
342 */
343 ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
344 VlanId assignedVlan, int nextId) {
345 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
346 // Let's the matching on the group address
347 // TODO SSM support in future
348 if (mcastIp.isIp6()) {
349 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
350 sbuilder.matchIPv6Dst(mcastIp.toIpPrefix());
351 } else {
352 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
353 sbuilder.matchIPDst(mcastIp.toIpPrefix());
354 }
355 // Then build the meta selector
356 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
357 metabuilder.matchVlanId(assignedVlan);
358 // Finally return the completed builder
359 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
360 fwdBuilder.withSelector(sbuilder.build())
361 .withMeta(metabuilder.build())
362 .nextStep(nextId)
363 .withFlag(ForwardingObjective.Flag.SPECIFIC)
364 .fromApp(srManager.appId())
365 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
366 return fwdBuilder;
367 }
368
369 /**
370 * Creates a filtering objective builder for multicast.
371 *
372 * @param ingressPort ingress port of the multicast stream
373 * @param assignedVlan assigned VLAN ID
374 * @param mcastIp the group address
375 * @param routerMac router MAC. This is carried in metadata and used from some switches that
376 * need to put unicast entry before multicast entry in TMAC table.
377 * @param mcastRole the Multicast role
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000378 * @param matchOnMac match or not on macaddress
Pier7b657162018-03-27 11:29:42 -0700379 * @return filtering objective builder
380 */
381 private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort, VlanId assignedVlan,
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000382 IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole,
383 boolean matchOnMac) {
Pier7b657162018-03-27 11:29:42 -0700384 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
385 // Let's add the in port matching and the priority
386 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
387 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
388 // According to the mcast role we match on the proper vlan
389 // If the role is null we are on the transit or on the egress
390 if (mcastRole == null) {
391 filtBuilder.addCondition(Criteria.matchVlanId(egressVlan()));
392 } else {
393 filtBuilder.addCondition(Criteria.matchVlanId(ingressVlan()));
394 }
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000395 // Add vlan info to the treatment builder
396 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder()
397 .pushVlan().setVlanId(assignedVlan);
398 // Additionally match on mac address and augment the treatment
399 if (matchOnMac) {
400 // According to the IP type we set the proper match on the mac address
401 if (mcastIp.isIp4()) {
402 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
403 MacAddress.IPV4_MULTICAST_MASK));
404 } else {
405 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
406 MacAddress.IPV6_MULTICAST_MASK));
407 }
408 // We set mac address to the treatment
409 if (routerMac != null && !routerMac.equals(MacAddress.NONE)) {
410 ttb.setEthDst(routerMac);
411 }
Pier7b657162018-03-27 11:29:42 -0700412 }
413 // We finally build the meta treatment
Harshada Chaundkar9204f312019-07-02 16:01:24 +0000414 TrafficTreatment tt = ttb.build();
415 filtBuilder.withMeta(tt);
Pier7b657162018-03-27 11:29:42 -0700416 // Done, we return a permit filtering objective
417 return filtBuilder.permit().fromApp(srManager.appId());
418 }
419
420 /**
421 * Gets output ports information from treatments.
422 *
423 * @param treatments collection of traffic treatments
424 * @return set of output port numbers
425 */
426 Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
427 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
428 treatments.forEach(treatment -> treatment.allInstructions().stream()
429 .filter(instr -> instr instanceof Instructions.OutputInstruction)
430 .forEach(instr -> builder.add(((Instructions.OutputInstruction) instr).port())));
431 return builder.build();
432 }
Pierdb27b8d2018-04-17 16:29:56 +0200433
434 /**
435 * Returns the hash of the group address.
436 *
437 * @param ipAddress the ip address
438 * @return the hash of the address
439 */
440 private Long hasher(IpAddress ipAddress) {
441 return HASH_FN.newHasher()
442 .putBytes(ipAddress.toOctets())
443 .hash()
444 .asLong();
445 }
446
447 /**
448 * Given a multicast group define a leader for it.
449 *
450 * @param mcastIp the group address
451 * @return true if the instance is the leader of the group
452 */
453 boolean isLeader(IpAddress mcastIp) {
454 // Get our id
455 final NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
456 // Get the leader for this group using the ip address as key
457 final NodeId leader = srManager.workPartitionService.getLeader(mcastIp, this::hasher);
458 // If there is not a leader, let's send an error
459 if (leader == null) {
460 log.error("Fail to elect a leader for {}.", mcastIp);
461 return false;
462 }
463 // Update cache and return operation result
464 mcastLeaderCache.put(mcastIp, leader);
465 return currentNodeId.equals(leader);
466 }
467
468 /**
469 * Given a multicast group withdraw its leader.
470 *
471 * @param mcastIp the group address
472 */
473 void withdrawLeader(IpAddress mcastIp) {
474 // For now just update the cache
475 mcastLeaderCache.remove(mcastIp);
476 }
477
478 Map<IpAddress, NodeId> getMcastLeaders(IpAddress mcastIp) {
479 // If mcast ip is present
480 if (mcastIp != null) {
481 return mcastLeaderCache.entrySet().stream()
482 .filter(entry -> entry.getKey().equals(mcastIp))
483 .collect(Collectors.toMap(Map.Entry::getKey,
484 Map.Entry::getValue));
485 }
486 // Otherwise take all the groups
487 return ImmutableMap.copyOf(mcastLeaderCache);
488 }
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700489
490 /**
pier9e02ab72020-02-12 20:40:55 +0100491 * Go through all the paths, looking for shared links to be used
492 * in the final path computation.
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700493 *
pier9e02ab72020-02-12 20:40:55 +0100494 * @param egresses egress devices
495 * @param availablePaths all the available paths towards the egress
496 * @return shared links between egress devices
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700497 */
pier9e02ab72020-02-12 20:40:55 +0100498 private Set<Link> exploreMcastTree(Set<DeviceId> egresses,
499 Map<DeviceId, List<Path>> availablePaths) {
500 int minLength = Integer.MAX_VALUE;
501 int length;
502 List<Path> currentPaths;
503 // Verify the source can still reach all the egresses
504 for (DeviceId egress : egresses) {
505 // From the source we cannot reach all the sinks
506 // just continue and let's figure out after
507 currentPaths = availablePaths.get(egress);
508 if (currentPaths.isEmpty()) {
509 continue;
510 }
511 // Get the length of the first one available, update the min length
512 length = currentPaths.get(0).links().size();
513 if (length < minLength) {
514 minLength = length;
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700515 }
516 }
pier9e02ab72020-02-12 20:40:55 +0100517 // If there are no paths
518 if (minLength == Integer.MAX_VALUE) {
519 return Collections.emptySet();
520 }
521 int index = 0;
522 Set<Link> sharedLinks = Sets.newHashSet();
523 Set<Link> currentSharedLinks;
524 Set<Link> currentLinks;
525 DeviceId egressToRemove = null;
526 // Let's find out the shared links
527 while (index < minLength) {
528 // Initialize the intersection with the paths related to the first egress
529 currentPaths = availablePaths.get(egresses.stream().findFirst().orElse(null));
530 currentSharedLinks = Sets.newHashSet();
531 // Iterate over the paths and take the "index" links
532 for (Path path : currentPaths) {
533 currentSharedLinks.add(path.links().get(index));
534 }
535 // Iterate over the remaining egress
536 for (DeviceId egress : egresses) {
537 // Iterate over the paths and take the "index" links
538 currentLinks = Sets.newHashSet();
539 for (Path path : availablePaths.get(egress)) {
540 currentLinks.add(path.links().get(index));
541 }
542 // Do intersection
543 currentSharedLinks = Sets.intersection(currentSharedLinks, currentLinks);
544 // If there are no shared paths exit and record the device to remove
545 // we have to retry with a subset of sinks
546 if (currentSharedLinks.isEmpty()) {
547 egressToRemove = egress;
548 index = minLength;
549 break;
550 }
551 }
552 sharedLinks.addAll(currentSharedLinks);
553 index++;
554 }
555 // If the shared links is empty and there are egress let's retry another time with less sinks,
556 // we can still build optimal subtrees
557 if (sharedLinks.isEmpty() && egresses.size() > 1 && egressToRemove != null) {
558 egresses.remove(egressToRemove);
559 sharedLinks = exploreMcastTree(egresses, availablePaths);
560 }
561 return sharedLinks;
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700562 }
pier9e02ab72020-02-12 20:40:55 +0100563
564 /**
565 * Build Mcast tree having as root the given source and as leaves the given egress points.
566 *
567 * @param mcastIp multicast group
568 * @param source source of the tree
569 * @param sinks leaves of the tree
570 * @return the computed Mcast tree
571 */
572 Map<ConnectPoint, List<Path>> computeSinkMcastTree(IpAddress mcastIp,
573 DeviceId source,
574 Set<ConnectPoint> sinks) {
575 // Get the egress devices, remove source from the egress if present
576 Set<DeviceId> egresses = sinks.stream().map(ConnectPoint::deviceId)
577 .filter(deviceId -> !deviceId.equals(source)).collect(Collectors.toSet());
578 Map<DeviceId, List<Path>> mcastTree = computeMcastTree(mcastIp, source, egresses);
579 final Map<ConnectPoint, List<Path>> finalTree = Maps.newHashMap();
580 // We need to put back the source if it was originally present
581 sinks.forEach(sink -> {
582 List<Path> sinkPaths = mcastTree.get(sink.deviceId());
583 finalTree.put(sink, sinkPaths != null ? sinkPaths : ImmutableList.of());
584 });
585 return finalTree;
586 }
587
588 /**
589 * Build Mcast tree having as root the given source and as leaves the given egress.
590 *
591 * @param mcastIp multicast group
592 * @param source source of the tree
593 * @param egresses leaves of the tree
594 * @return the computed Mcast tree
595 */
596 private Map<DeviceId, List<Path>> computeMcastTree(IpAddress mcastIp,
597 DeviceId source,
598 Set<DeviceId> egresses) {
599 log.debug("Computing tree for Multicast group {}, source {} and leafs {}",
600 mcastIp, source, egresses);
601 // Pre-compute all the paths
602 Map<DeviceId, List<Path>> availablePaths = Maps.newHashMap();
603 egresses.forEach(egress -> availablePaths.put(egress, getPaths(source, egress,
604 Collections.emptySet())));
605 // Explore the topology looking for shared links amongst the egresses
606 Set<Link> linksToEnforce = exploreMcastTree(Sets.newHashSet(egresses), availablePaths);
607 // Build the final paths enforcing the shared links between egress devices
608 availablePaths.clear();
609 egresses.forEach(egress -> availablePaths.put(egress, getPaths(source, egress,
610 linksToEnforce)));
611 return availablePaths;
612 }
613
614 /**
615 * Gets path from src to dst computed using the custom link weigher.
616 *
617 * @param src source device ID
618 * @param dst destination device ID
619 * @param linksToEnforce links to be enforced
620 * @return list of paths from src to dst
621 */
622 List<Path> getPaths(DeviceId src, DeviceId dst, Set<Link> linksToEnforce) {
623 final Topology currentTopology = topologyService.currentTopology();
624 final LinkWeigher linkWeigher = new SRLinkWeigher(srManager, src, linksToEnforce);
625 List<Path> allPaths = Lists.newArrayList(topologyService.getPaths(currentTopology, src, dst, linkWeigher));
626 log.trace("{} path(s) found from {} to {}", allPaths.size(), src, dst);
627 return allPaths;
628 }
629
630 /**
631 * Gets a stored path having dst as egress.
632 *
633 * @param dst destination device ID
634 * @param storedPaths paths list
635 * @return an optional path
636 */
637 Optional<? extends List<Link>> getStoredPath(DeviceId dst, Collection<? extends List<Link>> storedPaths) {
638 return storedPaths.stream()
639 .filter(path -> path.get(path.size() - 1).dst().deviceId().equals(dst))
640 .findFirst();
641 }
642
643 /**
644 * Returns a set of affected paths by the failed element.
645 *
646 * @param paths the paths to check
647 * @param failedElement the failed element
648 * @return the affected paths
649 */
650 Set<List<Link>> getAffectedPaths(Set<List<Link>> paths, Object failedElement) {
651 if (failedElement instanceof DeviceId) {
652 return getAffectedPathsByDevice(paths, failedElement);
653 }
654 return getAffectedPathsByLink(paths, failedElement);
655 }
656
657 private Set<List<Link>> getAffectedPathsByDevice(Set<List<Link>> paths, Object failedElement) {
658 DeviceId affectedDevice = (DeviceId) failedElement;
659 Set<List<Link>> affectedPaths = Sets.newHashSet();
660 paths.forEach(path -> {
661 if (path.stream().anyMatch(link -> link.src().deviceId().equals(affectedDevice))) {
662 affectedPaths.add(path);
663 }
664 });
665 return affectedPaths;
666 }
667
668 private Set<List<Link>> getAffectedPathsByLink(Set<List<Link>> paths, Object failedElement) {
669 Link affectedLink = (Link) failedElement;
670 Set<List<Link>> affectedPaths = Sets.newHashSet();
671 paths.forEach(path -> {
672 if (path.contains(affectedLink)) {
673 affectedPaths.add(path);
674 }
675 });
676 return affectedPaths;
677 }
678
679 /**
680 * Checks if the failure is affecting the transit device.
681 *
682 * @param devices the transit devices
683 * @param failedElement the failed element
684 * @return true if the failed element is affecting the transit devices
685 */
686 boolean isInfraFailure(Set<DeviceId> devices, Object failedElement) {
687 if (failedElement instanceof DeviceId) {
688 return isInfraFailureByDevice(devices, failedElement);
689 }
690 return true;
691 }
692
693 private boolean isInfraFailureByDevice(Set<DeviceId> devices, Object failedElement) {
694 DeviceId affectedDevice = (DeviceId) failedElement;
695 return devices.contains(affectedDevice);
696 }
697
698 /**
699 * Checks if a port is an infra port.
700 *
701 * @param connectPoint port to be checked
702 * @param storedPaths paths to be checked against
703 * @return true if the port is an infra port. False otherwise.
704 */
705 boolean isInfraPort(ConnectPoint connectPoint, Collection<? extends List<Link>> storedPaths) {
706 for (List<Link> path : storedPaths) {
707 if (path.stream().anyMatch(link -> link.src().equals(connectPoint) ||
708 link.dst().equals(connectPoint))) {
709 return true;
710 }
711 }
712 return false;
713 }
714
Pier7b657162018-03-27 11:29:42 -0700715}