blob: 94b3a4cf04bc3f7254e8a1c928a25722422f49e4 [file] [log] [blame]
Charles Chanc91c8782016-03-30 17:54:24 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chanc91c8782016-03-30 17:54:24 -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
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
Pier Luigi91573e12018-01-23 16:06:38 +010021import com.google.common.collect.Maps;
Charles Chanc91c8782016-03-30 17:54:24 -070022import com.google.common.collect.Sets;
23import org.onlab.packet.Ethernet;
24import org.onlab.packet.IpAddress;
25import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onlab.util.KryoNamespace;
Pier Luigi580fd8a2018-01-16 10:47:50 +010029import org.onosproject.cluster.NodeId;
Charles Chanc91c8782016-03-30 17:54:24 -070030import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
Ray Milkeyae0068a2017-08-15 11:02:29 -070032import org.onosproject.net.config.basics.McastConfig;
Charles Chanc91c8782016-03-30 17:54:24 -070033import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.Link;
36import org.onosproject.net.Path;
37import org.onosproject.net.PortNumber;
38import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.flow.DefaultTrafficTreatment;
40import org.onosproject.net.flow.TrafficSelector;
41import org.onosproject.net.flow.TrafficTreatment;
42import org.onosproject.net.flow.criteria.Criteria;
43import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
44import org.onosproject.net.flowobjective.DefaultFilteringObjective;
45import org.onosproject.net.flowobjective.DefaultForwardingObjective;
46import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan72779502016-04-23 17:36:10 -070047import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070048import org.onosproject.net.flowobjective.FilteringObjective;
49import org.onosproject.net.flowobjective.ForwardingObjective;
50import org.onosproject.net.flowobjective.NextObjective;
Charles Chan72779502016-04-23 17:36:10 -070051import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070052import org.onosproject.net.mcast.McastEvent;
53import org.onosproject.net.mcast.McastRouteInfo;
54import org.onosproject.net.topology.TopologyService;
Charles Chan370a65b2016-05-10 17:29:47 -070055import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan72779502016-04-23 17:36:10 -070056import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chanc91c8782016-03-30 17:54:24 -070057import org.onosproject.store.serializers.KryoNamespaces;
58import org.onosproject.store.service.ConsistentMap;
59import org.onosproject.store.service.Serializer;
60import org.onosproject.store.service.StorageService;
Pier Luigi580fd8a2018-01-16 10:47:50 +010061import org.onosproject.store.service.Versioned;
Charles Chanc91c8782016-03-30 17:54:24 -070062import org.slf4j.Logger;
63import org.slf4j.LoggerFactory;
64
65import java.util.Collection;
66import java.util.Collections;
Pier Luigi91573e12018-01-23 16:06:38 +010067import java.util.Comparator;
Charles Chanc91c8782016-03-30 17:54:24 -070068import java.util.List;
Charles Chan72779502016-04-23 17:36:10 -070069import java.util.Map;
Charles Chanc91c8782016-03-30 17:54:24 -070070import java.util.Optional;
71import java.util.Set;
Charles Chan72779502016-04-23 17:36:10 -070072import java.util.stream.Collectors;
73
74import static com.google.common.base.Preconditions.checkState;
Charles Chan10b0fb72017-02-02 16:20:42 -080075import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -070076
77/**
Charles Chan1eaf4802016-04-18 13:44:03 -070078 * Handles multicast-related events.
Charles Chanc91c8782016-03-30 17:54:24 -070079 */
Charles Chan1eaf4802016-04-18 13:44:03 -070080public class McastHandler {
81 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chanc91c8782016-03-30 17:54:24 -070082 private final SegmentRoutingManager srManager;
83 private final ApplicationId coreAppId;
Charles Chan82f19972016-05-17 13:13:55 -070084 private final StorageService storageService;
85 private final TopologyService topologyService;
Charles Chan72779502016-04-23 17:36:10 -070086 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
87 private final KryoNamespace.Builder mcastKryo;
88 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
89
90 /**
91 * Role in the multicast tree.
92 */
93 public enum McastRole {
94 /**
95 * The device is the ingress device of this group.
96 */
97 INGRESS,
98 /**
99 * The device is the transit device of this group.
100 */
101 TRANSIT,
102 /**
103 * The device is the egress device of this group.
104 */
105 EGRESS
106 }
Charles Chanc91c8782016-03-30 17:54:24 -0700107
108 /**
109 * Constructs the McastEventHandler.
110 *
111 * @param srManager Segment Routing manager
112 */
Charles Chan1eaf4802016-04-18 13:44:03 -0700113 public McastHandler(SegmentRoutingManager srManager) {
Charles Chanc91c8782016-03-30 17:54:24 -0700114 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chanc91c8782016-03-30 17:54:24 -0700115 this.srManager = srManager;
116 this.storageService = srManager.storageService;
117 this.topologyService = srManager.topologyService;
Charles Chan72779502016-04-23 17:36:10 -0700118 mcastKryo = new KryoNamespace.Builder()
Charles Chanc91c8782016-03-30 17:54:24 -0700119 .register(KryoNamespaces.API)
Charles Chan72779502016-04-23 17:36:10 -0700120 .register(McastStoreKey.class)
121 .register(McastRole.class);
Charles Chanc91c8782016-03-30 17:54:24 -0700122 mcastNextObjStore = storageService
Charles Chan72779502016-04-23 17:36:10 -0700123 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chanc91c8782016-03-30 17:54:24 -0700124 .withName("onos-mcast-nextobj-store")
Charles Chan4922a172016-05-23 16:45:45 -0700125 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chanc91c8782016-03-30 17:54:24 -0700126 .build();
Charles Chan72779502016-04-23 17:36:10 -0700127 mcastRoleStore = storageService
128 .<McastStoreKey, McastRole>consistentMapBuilder()
129 .withName("onos-mcast-role-store")
Charles Chan4922a172016-05-23 16:45:45 -0700130 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan72779502016-04-23 17:36:10 -0700131 .build();
132 }
133
134 /**
135 * Read initial multicast from mcast store.
136 */
Charles Chan82f19972016-05-17 13:13:55 -0700137 protected void init() {
Charles Chan72779502016-04-23 17:36:10 -0700138 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
139 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
140 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
141 sinks.forEach(sink -> {
142 processSinkAddedInternal(source, sink, mcastRoute.group());
143 });
144 });
Charles Chanc91c8782016-03-30 17:54:24 -0700145 }
146
147 /**
148 * Processes the SOURCE_ADDED event.
149 *
150 * @param event McastEvent with SOURCE_ADDED type
151 */
152 protected void processSourceAdded(McastEvent event) {
153 log.info("processSourceAdded {}", event);
154 McastRouteInfo mcastRouteInfo = event.subject();
155 if (!mcastRouteInfo.isComplete()) {
156 log.info("Incompleted McastRouteInfo. Abort.");
157 return;
158 }
159 ConnectPoint source = mcastRouteInfo.source().orElse(null);
160 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
161 IpAddress mcastIp = mcastRouteInfo.route().group();
162
163 sinks.forEach(sink -> {
164 processSinkAddedInternal(source, sink, mcastIp);
165 });
166 }
167
168 /**
169 * Processes the SINK_ADDED event.
170 *
171 * @param event McastEvent with SINK_ADDED type
172 */
173 protected void processSinkAdded(McastEvent event) {
174 log.info("processSinkAdded {}", event);
175 McastRouteInfo mcastRouteInfo = event.subject();
176 if (!mcastRouteInfo.isComplete()) {
177 log.info("Incompleted McastRouteInfo. Abort.");
178 return;
179 }
180 ConnectPoint source = mcastRouteInfo.source().orElse(null);
181 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
182 IpAddress mcastIp = mcastRouteInfo.route().group();
183
184 processSinkAddedInternal(source, sink, mcastIp);
185 }
186
187 /**
188 * Processes the SINK_REMOVED event.
189 *
190 * @param event McastEvent with SINK_REMOVED type
191 */
192 protected void processSinkRemoved(McastEvent event) {
193 log.info("processSinkRemoved {}", event);
194 McastRouteInfo mcastRouteInfo = event.subject();
195 if (!mcastRouteInfo.isComplete()) {
196 log.info("Incompleted McastRouteInfo. Abort.");
197 return;
198 }
199 ConnectPoint source = mcastRouteInfo.source().orElse(null);
200 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
201 IpAddress mcastIp = mcastRouteInfo.route().group();
Charles Chanc91c8782016-03-30 17:54:24 -0700202
Charles Chan0932eca2016-06-28 16:50:13 -0700203 // Continue only when this instance is the master of source device
204 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
205 log.info("Skip {} due to lack of mastership of the source device {}",
206 mcastIp, source.deviceId());
207 return;
208 }
209
Charles Chanc91c8782016-03-30 17:54:24 -0700210 // When source and sink are on the same device
211 if (source.deviceId().equals(sink.deviceId())) {
212 // Source and sink are on even the same port. There must be something wrong.
213 if (source.port().equals(sink.port())) {
214 log.warn("Sink is on the same port of source. Abort");
215 return;
216 }
Charles Chana8f9dee2016-05-16 18:44:13 -0700217 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chanc91c8782016-03-30 17:54:24 -0700218 return;
219 }
220
221 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700222 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700223 if (isLast) {
224 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
225 }
Charles Chanc91c8782016-03-30 17:54:24 -0700226
227 // If this is the last sink on the device, also update upstream
228 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
229 if (mcastPath.isPresent()) {
230 List<Link> links = Lists.newArrayList(mcastPath.get().links());
231 Collections.reverse(links);
232 for (Link link : links) {
233 if (isLast) {
234 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
Charles Chana8f9dee2016-05-16 18:44:13 -0700235 mcastIp,
236 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan72779502016-04-23 17:36:10 -0700237 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chanc91c8782016-03-30 17:54:24 -0700238 }
239 }
240 }
241 }
242
243 /**
244 * Establishes a path from source to sink for given multicast group.
245 *
246 * @param source connect point of the multicast source
247 * @param sink connection point of the multicast sink
248 * @param mcastIp multicast group IP address
249 */
250 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
251 IpAddress mcastIp) {
Charles Chan0932eca2016-06-28 16:50:13 -0700252 // Continue only when this instance is the master of source device
253 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
254 log.info("Skip {} due to lack of mastership of the source device {}",
255 source.deviceId());
256 return;
257 }
258
Charles Chan72779502016-04-23 17:36:10 -0700259 // Process the ingress device
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000260 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source), mcastIp);
Charles Chan72779502016-04-23 17:36:10 -0700261
Charles Chanc91c8782016-03-30 17:54:24 -0700262 // When source and sink are on the same device
263 if (source.deviceId().equals(sink.deviceId())) {
264 // Source and sink are on even the same port. There must be something wrong.
265 if (source.port().equals(sink.port())) {
266 log.warn("Sink is on the same port of source. Abort");
267 return;
268 }
Charles Chana8f9dee2016-05-16 18:44:13 -0700269 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700270 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chanc91c8782016-03-30 17:54:24 -0700271 return;
272 }
273
Charles Chanc91c8782016-03-30 17:54:24 -0700274 // Find a path. If present, create/update groups and flows for each hop
275 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
276 if (mcastPath.isPresent()) {
Charles Chan72779502016-04-23 17:36:10 -0700277 List<Link> links = mcastPath.get().links();
278 checkState(links.size() == 2,
279 "Path in leaf-spine topology should always be two hops: ", links);
280
281 links.forEach(link -> {
Charles Chana8f9dee2016-05-16 18:44:13 -0700282 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
283 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000284 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null), mcastIp);
Charles Chanc91c8782016-03-30 17:54:24 -0700285 });
Charles Chan72779502016-04-23 17:36:10 -0700286
Charles Chanc91c8782016-03-30 17:54:24 -0700287 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700288 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700289
290 // Setup mcast roles
291 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
292 McastRole.INGRESS);
293 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
294 McastRole.TRANSIT);
295 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
296 McastRole.EGRESS);
297 } else {
298 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
299 source.deviceId(), sink.deviceId());
Charles Chanc91c8782016-03-30 17:54:24 -0700300 }
301 }
302
303 /**
Charles Chan72779502016-04-23 17:36:10 -0700304 * Processes the LINK_DOWN event.
305 *
306 * @param affectedLink Link that is going down
307 */
308 protected void processLinkDown(Link affectedLink) {
Charles Chan72779502016-04-23 17:36:10 -0700309 getAffectedGroups(affectedLink).forEach(mcastIp -> {
Pier Luigi580fd8a2018-01-16 10:47:50 +0100310 // TODO Optimize when the group editing is in place
311 log.debug("Processing link down {} for group {}",
312 affectedLink, mcastIp);
313
Charles Chan72779502016-04-23 17:36:10 -0700314 // Find out the ingress, transit and egress device of affected group
315 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
316 .stream().findAny().orElse(null);
317 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
318 .stream().findAny().orElse(null);
319 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
Charles Chana8f9dee2016-05-16 18:44:13 -0700320 ConnectPoint source = getSource(mcastIp);
321
322 // Do not proceed if any of these info is missing
323 if (ingressDevice == null || transitDevice == null
324 || egressDevices == null || source == null) {
325 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
326 ingressDevice, transitDevice, egressDevices, source);
Charles Chan72779502016-04-23 17:36:10 -0700327 return;
328 }
329
Charles Chan0932eca2016-06-28 16:50:13 -0700330 // Continue only when this instance is the master of source device
331 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
332 log.info("Skip {} due to lack of mastership of the source device {}",
333 source.deviceId());
334 return;
335 }
336
Charles Chan72779502016-04-23 17:36:10 -0700337 // Remove entire transit
Charles Chana8f9dee2016-05-16 18:44:13 -0700338 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700339
340 // Remove transit-facing port on ingress device
341 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
342 if (ingressTransitPort != null) {
Charles Chana8f9dee2016-05-16 18:44:13 -0700343 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700344 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
345 }
346
347 // Construct a new path for each egress device
348 egressDevices.forEach(egressDevice -> {
349 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
350 if (mcastPath.isPresent()) {
Pier Luigi580fd8a2018-01-16 10:47:50 +0100351 installPath(mcastIp, source, mcastPath.get());
Charles Chan72779502016-04-23 17:36:10 -0700352 } else {
353 log.warn("Fail to recover egress device {} from link failure {}",
354 egressDevice, affectedLink);
Charles Chana8f9dee2016-05-16 18:44:13 -0700355 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700356 }
357 });
358 });
359 }
360
361 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100362 * Process the DEVICE_DOWN event.
363 *
364 * @param deviceDown device going down
365 */
366 protected void processDeviceDown(DeviceId deviceDown) {
367 // Get the mcast groups affected by the device going down
368 getAffectedGroups(deviceDown).forEach(mcastIp -> {
369 // TODO Optimize when the group editing is in place
370 log.debug("Processing device down {} for group {}",
371 deviceDown, mcastIp);
372
373 // Find out the ingress, transit and egress device of affected group
374 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
375 .stream().findAny().orElse(null);
376 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
377 .stream().findAny().orElse(null);
378 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
379 ConnectPoint source = getSource(mcastIp);
380
381 // Do not proceed if ingress device or source of this group are missing
382 // If sinks are in other leafs, we have ingress, transit, egress, and source
383 // If sinks are in the same leaf, we have just ingress and source
384 if (ingressDevice == null || source == null) {
385 log.warn("Missing ingress {} or source {} for group {}",
386 ingressDevice, source, mcastIp);
387 return;
388 }
389
390 // Continue only when we have the mastership on the operation
391 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
392 // When the source is available we just check the mastership
393 if (srManager.deviceService.isAvailable(source.deviceId())) {
394 log.info("Skip {} due to lack of mastership of the source device {}",
395 mcastIp, source.deviceId());
396 return;
397 }
398 // Fallback with Leadership service
399 // source id is used a topic
400 NodeId leader = srManager.leadershipService.runForLeadership(
401 source.deviceId().toString()).leaderNodeId();
402 // Verify if this node is the leader
403 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
404 log.info("Skip {} due to lack of leadership on the topic {}",
405 mcastIp, source.deviceId());
406 return;
407 }
408 }
409
410 // If it exists, we have to remove it in any case
411 if (transitDevice != null) {
412 // Remove entire transit
413 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
414 }
415 // If the ingress is down
416 if (ingressDevice.equals(deviceDown)) {
417 // Remove entire ingress
418 removeGroupFromDevice(ingressDevice, mcastIp, assignedVlan(source));
419 // If other sinks different from the ingress exist
420 if (!egressDevices.isEmpty()) {
421 // Remove all the remaining egress
422 egressDevices.forEach(
423 egressDevice -> removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null))
424 );
425 }
426 } else {
427 // Egress or transit could be down at this point
428 // Get the ingress-transit port if it exists
429 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
430 if (ingressTransitPort != null) {
431 // Remove transit-facing port on ingress device
432 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
433 }
434 // One of the egress device is down
435 if (egressDevices.contains(deviceDown)) {
436 // Remove entire device down
437 removeGroupFromDevice(deviceDown, mcastIp, assignedVlan(null));
438 // Remove the device down from egress
439 egressDevices.remove(deviceDown);
440 // If there are no more egress and ingress does not have sinks
441 if (egressDevices.isEmpty() && !hasSinks(ingressDevice, mcastIp)) {
442 // Remove entire ingress
443 mcastRoleStore.remove(new McastStoreKey(mcastIp, ingressDevice));
444 // We have done
445 return;
446 }
447 }
448 // Construct a new path for each egress device
449 egressDevices.forEach(egressDevice -> {
450 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
451 // If there is a new path
452 if (mcastPath.isPresent()) {
453 // Let's install the new mcast path for this egress
454 installPath(mcastIp, source, mcastPath.get());
455 } else {
456 // We were not able to find an alternative path for this egress
457 log.warn("Fail to recover egress device {} from device down {}",
458 egressDevice, deviceDown);
459 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
460 }
461 });
462 }
463 });
464 }
465
466 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700467 * Adds filtering objective for given device and port.
468 *
469 * @param deviceId device ID
470 * @param port ingress port number
471 * @param assignedVlan assigned VLAN ID
472 */
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000473 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700474 // Do nothing if the port is configured as suppressed
Charles Chan370a65b2016-05-10 17:29:47 -0700475 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
476 SegmentRoutingAppConfig appConfig = srManager.cfgService
477 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
478 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
479 log.info("Ignore suppressed port {}", connectPoint);
Charles Chanc91c8782016-03-30 17:54:24 -0700480 return;
481 }
482
483 FilteringObjective.Builder filtObjBuilder =
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000484 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
Charles Chan72779502016-04-23 17:36:10 -0700485 ObjectiveContext context = new DefaultObjectiveContext(
486 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan10b0fb72017-02-02 16:20:42 -0800487 deviceId, port.toLong(), assignedVlan),
Charles Chan72779502016-04-23 17:36:10 -0700488 (objective, error) ->
489 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan10b0fb72017-02-02 16:20:42 -0800490 deviceId, port.toLong(), assignedVlan, error));
Charles Chan72779502016-04-23 17:36:10 -0700491 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chanc91c8782016-03-30 17:54:24 -0700492 }
493
494 /**
495 * Adds a port to given multicast group on given device. This involves the
496 * update of L3 multicast group and multicast routing table entry.
497 *
498 * @param deviceId device ID
499 * @param port port to be added
500 * @param mcastIp multicast group
501 * @param assignedVlan assigned VLAN ID
502 */
503 private void addPortToDevice(DeviceId deviceId, PortNumber port,
504 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700505 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700506 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Pier Luigi4f0dd212018-01-19 10:24:53 +0100507 NextObjective newNextObj;
Charles Chan72779502016-04-23 17:36:10 -0700508 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700509 // First time someone request this mcast group via this device
510 portBuilder.add(port);
Pier Luigi4f0dd212018-01-19 10:24:53 +0100511 // New nextObj
512 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
513 portBuilder.build(), null).add();
514 // Store the new port
515 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700516 } else {
517 // This device already serves some subscribers of this mcast group
Charles Chan72779502016-04-23 17:36:10 -0700518 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700519 // Stop if the port is already in the nextobj
520 Set<PortNumber> existingPorts = getPorts(nextObj.next());
521 if (existingPorts.contains(port)) {
522 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
523 return;
524 }
Pier Luigi4f0dd212018-01-19 10:24:53 +0100525 // Let's add the port and reuse the previous one
Yuta HIGUCHIbef07b52018-02-09 18:05:23 -0800526 portBuilder.addAll(existingPorts).add(port);
Pier Luigi4f0dd212018-01-19 10:24:53 +0100527 // Reuse previous nextObj
528 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
529 portBuilder.build(), nextObj.id()).addToExisting();
530 // Store the final next objective and send only the difference to the driver
531 mcastNextObjStore.put(mcastStoreKey, newNextObj);
532 // Add just the new port
533 portBuilder = ImmutableSet.builder();
534 portBuilder.add(port);
535 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
536 portBuilder.build(), nextObj.id()).addToExisting();
Charles Chanc91c8782016-03-30 17:54:24 -0700537 }
538 // Create, store and apply the new nextObj and fwdObj
Charles Chan72779502016-04-23 17:36:10 -0700539 ObjectiveContext context = new DefaultObjectiveContext(
540 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
541 mcastIp, deviceId, port.toLong(), assignedVlan),
542 (objective, error) ->
543 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
544 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700545 ForwardingObjective fwdObj =
Charles Chan72779502016-04-23 17:36:10 -0700546 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chanc91c8782016-03-30 17:54:24 -0700547 srManager.flowObjectiveService.next(deviceId, newNextObj);
548 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700549 }
550
551 /**
552 * Removes a port from given multicast group on given device.
553 * This involves the update of L3 multicast group and multicast routing
554 * table entry.
555 *
556 * @param deviceId device ID
557 * @param port port to be added
558 * @param mcastIp multicast group
559 * @param assignedVlan assigned VLAN ID
560 * @return true if this is the last sink on this device
561 */
562 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
563 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700564 McastStoreKey mcastStoreKey =
565 new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700566 // This device is not serving this multicast group
Charles Chan72779502016-04-23 17:36:10 -0700567 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700568 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
569 return false;
570 }
Charles Chan72779502016-04-23 17:36:10 -0700571 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700572
573 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan72779502016-04-23 17:36:10 -0700574 // This port does not serve this multicast group
Charles Chanc91c8782016-03-30 17:54:24 -0700575 if (!existingPorts.contains(port)) {
576 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
577 return false;
578 }
579 // Copy and modify the ImmutableSet
580 existingPorts = Sets.newHashSet(existingPorts);
581 existingPorts.remove(port);
582
583 NextObjective newNextObj;
Pier Luigi8cd46de2018-01-19 10:24:53 +0100584 ObjectiveContext context;
Charles Chanc91c8782016-03-30 17:54:24 -0700585 ForwardingObjective fwdObj;
586 if (existingPorts.isEmpty()) {
Pier Luigi8cd46de2018-01-19 10:24:53 +0100587 // If this is the last sink, remove flows and last bucket
Charles Chanc91c8782016-03-30 17:54:24 -0700588 // NOTE: Rely on GroupStore garbage collection rather than explicitly
589 // remove L3MG since there might be other flows/groups refer to
590 // the same L2IG
Pier Luigi8cd46de2018-01-19 10:24:53 +0100591 context = new DefaultObjectiveContext(
Charles Chan72779502016-04-23 17:36:10 -0700592 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
593 mcastIp, deviceId, port.toLong(), assignedVlan),
594 (objective, error) ->
595 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
596 mcastIp, deviceId, port.toLong(), assignedVlan, error));
597 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
598 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700599 } else {
600 // If this is not the last sink, update flows and groups
Pier Luigi8cd46de2018-01-19 10:24:53 +0100601 context = new DefaultObjectiveContext(
Charles Chan72779502016-04-23 17:36:10 -0700602 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
603 mcastIp, deviceId, port.toLong(), assignedVlan),
604 (objective, error) ->
605 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
606 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Pier Luigi8cd46de2018-01-19 10:24:53 +0100607 // Here we store the next objective with the remaining port
608 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
609 existingPorts, nextObj.id()).removeFromExisting();
Charles Chan82f19972016-05-17 13:13:55 -0700610 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan72779502016-04-23 17:36:10 -0700611 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700612 }
Pier Luigi8cd46de2018-01-19 10:24:53 +0100613 // Let's modify the next objective removing the bucket
614 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
615 ImmutableSet.of(port), nextObj.id()).removeFromExisting();
616 srManager.flowObjectiveService.next(deviceId, newNextObj);
617 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700618 return existingPorts.isEmpty();
619 }
620
Charles Chan72779502016-04-23 17:36:10 -0700621 /**
622 * Removes entire group on given device.
623 *
624 * @param deviceId device ID
625 * @param mcastIp multicast group to be removed
626 * @param assignedVlan assigned VLAN ID
627 */
628 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
629 VlanId assignedVlan) {
630 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
631 // This device is not serving this multicast group
632 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
633 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
634 return;
635 }
636 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
637 // NOTE: Rely on GroupStore garbage collection rather than explicitly
638 // remove L3MG since there might be other flows/groups refer to
639 // the same L2IG
640 ObjectiveContext context = new DefaultObjectiveContext(
641 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
642 mcastIp, deviceId, assignedVlan),
643 (objective, error) ->
644 log.warn("Failed to remove {} on {}, vlan {}: {}",
645 mcastIp, deviceId, assignedVlan, error));
646 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
647 srManager.flowObjectiveService.forward(deviceId, fwdObj);
648 mcastNextObjStore.remove(mcastStoreKey);
649 mcastRoleStore.remove(mcastStoreKey);
650 }
651
Pier Luigi580fd8a2018-01-16 10:47:50 +0100652 private void installPath(IpAddress mcastIp, ConnectPoint source, Path mcastPath) {
653 // Get Links
654 List<Link> links = mcastPath.links();
655 // For each link, modify the next on the source device adding the src port
656 // and a new filter objective on the destination port
657 links.forEach(link -> {
658 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
659 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
660 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null),
661 mcastIp);
662 });
663 // Setup new transit mcast role
664 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
665 McastRole.TRANSIT);
Charles Chan72779502016-04-23 17:36:10 -0700666 }
667
Charles Chanc91c8782016-03-30 17:54:24 -0700668 /**
669 * Creates a next objective builder for multicast.
670 *
671 * @param mcastIp multicast group
672 * @param assignedVlan assigned VLAN ID
673 * @param outPorts set of output port numbers
674 * @return next objective builder
675 */
676 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
Pier Luigi4f0dd212018-01-19 10:24:53 +0100677 VlanId assignedVlan, Set<PortNumber> outPorts, Integer nextId) {
678 // If nextId is null allocate a new one
679 if (nextId == null) {
680 nextId = srManager.flowObjectiveService.allocateNextId();
681 }
Charles Chanc91c8782016-03-30 17:54:24 -0700682
683 TrafficSelector metadata =
684 DefaultTrafficSelector.builder()
685 .matchVlanId(assignedVlan)
686 .matchIPDst(mcastIp.toIpPrefix())
687 .build();
688
689 NextObjective.Builder nextObjBuilder = DefaultNextObjective
690 .builder().withId(nextId)
691 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
692 .withMeta(metadata);
693
694 outPorts.forEach(port -> {
695 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
696 if (egressVlan().equals(VlanId.NONE)) {
697 tBuilder.popVlan();
698 }
699 tBuilder.setOutput(port);
700 nextObjBuilder.addTreatment(tBuilder.build());
701 });
702
703 return nextObjBuilder;
704 }
705
706 /**
707 * Creates a forwarding objective builder for multicast.
708 *
709 * @param mcastIp multicast group
710 * @param assignedVlan assigned VLAN ID
711 * @param nextId next ID of the L3 multicast group
712 * @return forwarding objective builder
713 */
714 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
715 VlanId assignedVlan, int nextId) {
716 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000717 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
718
719 if (mcastIp.isIp4()) {
720 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
721 sbuilder.matchIPDst(mcastPrefix);
722 } else {
723 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
724 sbuilder.matchIPv6Dst(mcastPrefix);
725 }
726
727
Charles Chanc91c8782016-03-30 17:54:24 -0700728 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
729 metabuilder.matchVlanId(assignedVlan);
730
731 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
732 fwdBuilder.withSelector(sbuilder.build())
733 .withMeta(metabuilder.build())
734 .nextStep(nextId)
735 .withFlag(ForwardingObjective.Flag.SPECIFIC)
736 .fromApp(srManager.appId)
737 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
738 return fwdBuilder;
739 }
740
741 /**
742 * Creates a filtering objective builder for multicast.
743 *
744 * @param deviceId Device ID
745 * @param ingressPort ingress port of the multicast stream
746 * @param assignedVlan assigned VLAN ID
747 * @return filtering objective builder
748 */
749 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000750 VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700751 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan0932eca2016-06-28 16:50:13 -0700752
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000753 if (mcastIp.isIp4()) {
754 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
755 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
756 MacAddress.IPV4_MULTICAST_MASK))
757 .addCondition(Criteria.matchVlanId(egressVlan()))
758 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
759 } else {
760 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
761 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
762 MacAddress.IPV6_MULTICAST_MASK))
763 .addCondition(Criteria.matchVlanId(egressVlan()))
764 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
765 }
Charles Chan0932eca2016-06-28 16:50:13 -0700766 TrafficTreatment tt = DefaultTrafficTreatment.builder()
767 .pushVlan().setVlanId(assignedVlan).build();
768 filtBuilder.withMeta(tt);
769
Charles Chanc91c8782016-03-30 17:54:24 -0700770 return filtBuilder.permit().fromApp(srManager.appId);
771 }
772
773 /**
774 * Gets output ports information from treatments.
775 *
776 * @param treatments collection of traffic treatments
777 * @return set of output port numbers
778 */
779 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
780 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
781 treatments.forEach(treatment -> {
782 treatment.allInstructions().stream()
783 .filter(instr -> instr instanceof OutputInstruction)
784 .forEach(instr -> {
785 builder.add(((OutputInstruction) instr).port());
786 });
787 });
788 return builder.build();
789 }
790
791 /**
792 * Gets a path from src to dst.
793 * If a path was allocated before, returns the allocated path.
794 * Otherwise, randomly pick one from available paths.
795 *
796 * @param src source device ID
797 * @param dst destination device ID
798 * @param mcastIp multicast group
799 * @return an optional path from src to dst
800 */
801 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
802 List<Path> allPaths = Lists.newArrayList(
803 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700804 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700805 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700806 return Optional.empty();
807 }
808
Pier Luigi91573e12018-01-23 16:06:38 +0100809 // Create a map index of suitablity-to-list of paths. For example
810 // a path in the list associated to the index 1 shares only the
811 // first hop and it is less suitable of a path belonging to the index
812 // 2 that shares leaf-spine.
813 Map<Integer, List<Path>> eligiblePaths = Maps.newHashMap();
814 // Some init steps
815 int nhop;
816 McastStoreKey mcastStoreKey;
817 Link hop;
818 PortNumber srcPort;
819 Set<PortNumber> existingPorts;
820 NextObjective nextObj;
821 // Iterate over paths looking for eligible paths
822 for (Path path : allPaths) {
823 // Unlikely, it will happen...
824 if (!src.equals(path.links().get(0).src().deviceId())) {
825 continue;
826 }
827 nhop = 0;
828 // Iterate over the links
829 while (nhop < path.links().size()) {
830 // Get the link and verify if a next related
831 // to the src device exist in the store
832 hop = path.links().get(nhop);
833 mcastStoreKey = new McastStoreKey(mcastIp, hop.src().deviceId());
834 // It does not exist in the store, exit
835 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
836 break;
Charles Chanc91c8782016-03-30 17:54:24 -0700837 }
Pier Luigi91573e12018-01-23 16:06:38 +0100838 // Get the output ports on the next
839 nextObj = mcastNextObjStore.get(mcastStoreKey).value();
840 existingPorts = getPorts(nextObj.next());
841 // And the src port on the link
842 srcPort = hop.src().port();
843 // the src port is not used as output, exit
844 if (!existingPorts.contains(srcPort)) {
845 break;
846 }
847 nhop++;
848 }
849 // n_hop defines the index
850 if (nhop > 0) {
851 eligiblePaths.compute(nhop, (index, paths) -> {
852 paths = paths == null ? Lists.newArrayList() : paths;
853 paths.add(path);
854 return paths;
855 });
Charles Chanc91c8782016-03-30 17:54:24 -0700856 }
857 }
Pier Luigi91573e12018-01-23 16:06:38 +0100858
859 // No suitable paths
860 if (eligiblePaths.isEmpty()) {
861 log.debug("No eligiblePath(s) found from {} to {}", src, dst);
862 // Otherwise, randomly pick a path
863 Collections.shuffle(allPaths);
864 return allPaths.stream().findFirst();
865 }
866
867 // Let's take the best ones
868 Integer bestIndex = eligiblePaths.keySet()
869 .stream()
870 .sorted(Comparator.reverseOrder())
871 .findFirst().orElse(null);
872 List<Path> bestPaths = eligiblePaths.get(bestIndex);
873 log.debug("{} eligiblePath(s) found from {} to {}",
874 bestPaths.size(), src, dst);
875 // randomly pick a path on the highest index
876 Collections.shuffle(bestPaths);
877 return bestPaths.stream().findFirst();
Charles Chanc91c8782016-03-30 17:54:24 -0700878 }
879
880 /**
Charles Chan72779502016-04-23 17:36:10 -0700881 * Gets device(s) of given role in given multicast group.
882 *
883 * @param mcastIp multicast IP
884 * @param role multicast role
885 * @return set of device ID or empty set if not found
886 */
887 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
888 return mcastRoleStore.entrySet().stream()
889 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
890 entry.getValue().value() == role)
891 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
892 .collect(Collectors.toSet());
893 }
894
895 /**
Charles Chana8f9dee2016-05-16 18:44:13 -0700896 * Gets source connect point of given multicast group.
897 *
898 * @param mcastIp multicast IP
899 * @return source connect point or null if not found
900 */
901 private ConnectPoint getSource(IpAddress mcastIp) {
902 return srManager.multicastRouteService.getRoutes().stream()
903 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
904 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
905 .findAny().orElse(null);
906 }
907
908 /**
Charles Chan72779502016-04-23 17:36:10 -0700909 * Gets groups which is affected by the link down event.
910 *
911 * @param link link going down
912 * @return a set of multicast IpAddress
913 */
914 private Set<IpAddress> getAffectedGroups(Link link) {
915 DeviceId deviceId = link.src().deviceId();
916 PortNumber port = link.src().port();
917 return mcastNextObjStore.entrySet().stream()
918 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
919 getPorts(entry.getValue().value().next()).contains(port))
920 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
921 .collect(Collectors.toSet());
922 }
923
924 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100925 * Gets groups which are affected by the device down event.
926 *
927 * @param deviceId device going down
928 * @return a set of multicast IpAddress
929 */
930 private Set<IpAddress> getAffectedGroups(DeviceId deviceId) {
931 return mcastNextObjStore.entrySet().stream()
932 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
933 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
934 .collect(Collectors.toSet());
935 }
936
937 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700938 * Gets egress VLAN from McastConfig.
939 *
940 * @return egress VLAN or VlanId.NONE if not configured
941 */
942 private VlanId egressVlan() {
943 McastConfig mcastConfig =
944 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
945 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
946 }
947
948 /**
949 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chana8f9dee2016-05-16 18:44:13 -0700950 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chanc91c8782016-03-30 17:54:24 -0700951 *
Charles Chana8f9dee2016-05-16 18:44:13 -0700952 * @param cp connect point; Can be null if not specified
953 * @return assigned VLAN ID
Charles Chanc91c8782016-03-30 17:54:24 -0700954 */
Charles Chana8f9dee2016-05-16 18:44:13 -0700955 private VlanId assignedVlan(ConnectPoint cp) {
956 // Use the egressVlan if it is tagged
957 if (!egressVlan().equals(VlanId.NONE)) {
958 return egressVlan();
959 }
960 // Reuse unicast VLAN if the port has subnet configured
961 if (cp != null) {
Charles Chanf9759582017-10-20 19:09:16 -0700962 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan10b0fb72017-02-02 16:20:42 -0800963 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chana8f9dee2016-05-16 18:44:13 -0700964 }
Charles Chan10b0fb72017-02-02 16:20:42 -0800965 // Use DEFAULT_VLAN if none of the above matches
966 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -0700967 }
Charles Chan72779502016-04-23 17:36:10 -0700968
969 /**
970 * Gets the spine-facing port on ingress device of given multicast group.
971 *
972 * @param mcastIp multicast IP
973 * @return spine-facing port on ingress device
974 */
975 private PortNumber ingressTransitPort(IpAddress mcastIp) {
976 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
977 .stream().findAny().orElse(null);
978 if (ingressDevice != null) {
979 NextObjective nextObj = mcastNextObjStore
980 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
981 Set<PortNumber> ports = getPorts(nextObj.next());
982
983 for (PortNumber port : ports) {
984 // Spine-facing port should have no subnet and no xconnect
985 if (srManager.deviceConfiguration != null &&
Pier Ventreb6a7f342016-11-26 21:05:22 -0800986 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chan82f19972016-05-17 13:13:55 -0700987 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan72779502016-04-23 17:36:10 -0700988 return port;
989 }
990 }
991 }
992 return null;
993 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700994
995 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100996 * Verify if the given device has sinks
997 * for the multicast group.
998 *
999 * @param deviceId device Id
1000 * @param mcastIp multicast IP
1001 * @return true if the device has sink for the group.
1002 * False otherwise.
1003 */
1004 private boolean hasSinks(DeviceId deviceId, IpAddress mcastIp) {
1005 if (deviceId != null) {
1006 // Get the nextobjective
1007 Versioned<NextObjective> versionedNextObj = mcastNextObjStore.get(
1008 new McastStoreKey(mcastIp, deviceId)
1009 );
1010 // If it exists
1011 if (versionedNextObj != null) {
1012 NextObjective nextObj = versionedNextObj.value();
1013 // Retrieves all the output ports
1014 Set<PortNumber> ports = getPorts(nextObj.next());
1015 // Tries to find at least one port that is not spine-facing
1016 for (PortNumber port : ports) {
1017 // Spine-facing port should have no subnet and no xconnect
1018 if (srManager.deviceConfiguration != null &&
1019 (!srManager.deviceConfiguration.getPortSubnets(deviceId, port).isEmpty() ||
1020 srManager.xConnectHandler.hasXConnect(new ConnectPoint(deviceId, port)))) {
1021 return true;
1022 }
1023 }
1024 }
1025 }
1026 return false;
1027 }
1028
1029 /**
Jonghwan Hyune5ef7622017-08-25 17:48:36 -07001030 * Removes filtering objective for given device and port.
1031 *
1032 * @param deviceId device ID
1033 * @param port ingress port number
1034 * @param assignedVlan assigned VLAN ID
1035 * @param mcastIp multicast IP address
1036 */
1037 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
1038 // Do nothing if the port is configured as suppressed
1039 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
1040 SegmentRoutingAppConfig appConfig = srManager.cfgService
1041 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
1042 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
1043 log.info("Ignore suppressed port {}", connectPoint);
1044 return;
1045 }
1046
1047 FilteringObjective.Builder filtObjBuilder =
1048 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
1049 ObjectiveContext context = new DefaultObjectiveContext(
1050 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
1051 deviceId, port.toLong(), assignedVlan),
1052 (objective, error) ->
1053 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
1054 deviceId, port.toLong(), assignedVlan, error));
1055 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
1056 }
1057
1058 /**
1059 * Adds or removes filtering objective for given device and port.
1060 *
1061 * @param deviceId device ID
1062 * @param portNum ingress port number
1063 * @param vlanId assigned VLAN ID
1064 * @param install true to add, false to remove
1065 */
1066 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
1067 VlanId vlanId, boolean install) {
1068 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
1069 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
1070 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
1071 if (install) {
1072 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1073 } else {
1074 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1075 }
1076 }
1077 });
1078 }
Charles Chanc91c8782016-03-30 17:54:24 -07001079}