blob: e77ba97cc9f1ff3b0bf571a3808d8e7517e7c00c [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
526 portBuilder.addAll(existingPorts).add(port).build();
527 // 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;
584 ForwardingObjective fwdObj;
585 if (existingPorts.isEmpty()) {
586 // If this is the last sink, remove flows and groups
587 // NOTE: Rely on GroupStore garbage collection rather than explicitly
588 // remove L3MG since there might be other flows/groups refer to
589 // the same L2IG
Charles Chan72779502016-04-23 17:36:10 -0700590 ObjectiveContext context = new DefaultObjectiveContext(
591 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
592 mcastIp, deviceId, port.toLong(), assignedVlan),
593 (objective, error) ->
594 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
595 mcastIp, deviceId, port.toLong(), assignedVlan, error));
596 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
597 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700598 srManager.flowObjectiveService.forward(deviceId, fwdObj);
599 } else {
600 // If this is not the last sink, update flows and groups
Charles Chan72779502016-04-23 17:36:10 -0700601 ObjectiveContext context = new DefaultObjectiveContext(
602 (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 Luigi4f0dd212018-01-19 10:24:53 +0100607 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts, null).add();
Charles Chan82f19972016-05-17 13:13:55 -0700608 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan72779502016-04-23 17:36:10 -0700609 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700610 srManager.flowObjectiveService.next(deviceId, newNextObj);
611 srManager.flowObjectiveService.forward(deviceId, fwdObj);
612 }
Charles Chanc91c8782016-03-30 17:54:24 -0700613 return existingPorts.isEmpty();
614 }
615
Charles Chan72779502016-04-23 17:36:10 -0700616 /**
617 * Removes entire group on given device.
618 *
619 * @param deviceId device ID
620 * @param mcastIp multicast group to be removed
621 * @param assignedVlan assigned VLAN ID
622 */
623 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
624 VlanId assignedVlan) {
625 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
626 // This device is not serving this multicast group
627 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
628 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
629 return;
630 }
631 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
632 // NOTE: Rely on GroupStore garbage collection rather than explicitly
633 // remove L3MG since there might be other flows/groups refer to
634 // the same L2IG
635 ObjectiveContext context = new DefaultObjectiveContext(
636 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
637 mcastIp, deviceId, assignedVlan),
638 (objective, error) ->
639 log.warn("Failed to remove {} on {}, vlan {}: {}",
640 mcastIp, deviceId, assignedVlan, error));
641 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
642 srManager.flowObjectiveService.forward(deviceId, fwdObj);
643 mcastNextObjStore.remove(mcastStoreKey);
644 mcastRoleStore.remove(mcastStoreKey);
645 }
646
Pier Luigi580fd8a2018-01-16 10:47:50 +0100647 private void installPath(IpAddress mcastIp, ConnectPoint source, Path mcastPath) {
648 // Get Links
649 List<Link> links = mcastPath.links();
650 // For each link, modify the next on the source device adding the src port
651 // and a new filter objective on the destination port
652 links.forEach(link -> {
653 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
654 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
655 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null),
656 mcastIp);
657 });
658 // Setup new transit mcast role
659 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
660 McastRole.TRANSIT);
Charles Chan72779502016-04-23 17:36:10 -0700661 }
662
Charles Chanc91c8782016-03-30 17:54:24 -0700663 /**
664 * Creates a next objective builder for multicast.
665 *
666 * @param mcastIp multicast group
667 * @param assignedVlan assigned VLAN ID
668 * @param outPorts set of output port numbers
669 * @return next objective builder
670 */
671 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
Pier Luigi4f0dd212018-01-19 10:24:53 +0100672 VlanId assignedVlan, Set<PortNumber> outPorts, Integer nextId) {
673 // If nextId is null allocate a new one
674 if (nextId == null) {
675 nextId = srManager.flowObjectiveService.allocateNextId();
676 }
Charles Chanc91c8782016-03-30 17:54:24 -0700677
678 TrafficSelector metadata =
679 DefaultTrafficSelector.builder()
680 .matchVlanId(assignedVlan)
681 .matchIPDst(mcastIp.toIpPrefix())
682 .build();
683
684 NextObjective.Builder nextObjBuilder = DefaultNextObjective
685 .builder().withId(nextId)
686 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
687 .withMeta(metadata);
688
689 outPorts.forEach(port -> {
690 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
691 if (egressVlan().equals(VlanId.NONE)) {
692 tBuilder.popVlan();
693 }
694 tBuilder.setOutput(port);
695 nextObjBuilder.addTreatment(tBuilder.build());
696 });
697
698 return nextObjBuilder;
699 }
700
701 /**
702 * Creates a forwarding objective builder for multicast.
703 *
704 * @param mcastIp multicast group
705 * @param assignedVlan assigned VLAN ID
706 * @param nextId next ID of the L3 multicast group
707 * @return forwarding objective builder
708 */
709 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
710 VlanId assignedVlan, int nextId) {
711 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000712 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
713
714 if (mcastIp.isIp4()) {
715 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
716 sbuilder.matchIPDst(mcastPrefix);
717 } else {
718 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
719 sbuilder.matchIPv6Dst(mcastPrefix);
720 }
721
722
Charles Chanc91c8782016-03-30 17:54:24 -0700723 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
724 metabuilder.matchVlanId(assignedVlan);
725
726 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
727 fwdBuilder.withSelector(sbuilder.build())
728 .withMeta(metabuilder.build())
729 .nextStep(nextId)
730 .withFlag(ForwardingObjective.Flag.SPECIFIC)
731 .fromApp(srManager.appId)
732 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
733 return fwdBuilder;
734 }
735
736 /**
737 * Creates a filtering objective builder for multicast.
738 *
739 * @param deviceId Device ID
740 * @param ingressPort ingress port of the multicast stream
741 * @param assignedVlan assigned VLAN ID
742 * @return filtering objective builder
743 */
744 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000745 VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700746 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan0932eca2016-06-28 16:50:13 -0700747
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000748 if (mcastIp.isIp4()) {
749 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
750 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
751 MacAddress.IPV4_MULTICAST_MASK))
752 .addCondition(Criteria.matchVlanId(egressVlan()))
753 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
754 } else {
755 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
756 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
757 MacAddress.IPV6_MULTICAST_MASK))
758 .addCondition(Criteria.matchVlanId(egressVlan()))
759 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
760 }
Charles Chan0932eca2016-06-28 16:50:13 -0700761 TrafficTreatment tt = DefaultTrafficTreatment.builder()
762 .pushVlan().setVlanId(assignedVlan).build();
763 filtBuilder.withMeta(tt);
764
Charles Chanc91c8782016-03-30 17:54:24 -0700765 return filtBuilder.permit().fromApp(srManager.appId);
766 }
767
768 /**
769 * Gets output ports information from treatments.
770 *
771 * @param treatments collection of traffic treatments
772 * @return set of output port numbers
773 */
774 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
775 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
776 treatments.forEach(treatment -> {
777 treatment.allInstructions().stream()
778 .filter(instr -> instr instanceof OutputInstruction)
779 .forEach(instr -> {
780 builder.add(((OutputInstruction) instr).port());
781 });
782 });
783 return builder.build();
784 }
785
786 /**
787 * Gets a path from src to dst.
788 * If a path was allocated before, returns the allocated path.
789 * Otherwise, randomly pick one from available paths.
790 *
791 * @param src source device ID
792 * @param dst destination device ID
793 * @param mcastIp multicast group
794 * @return an optional path from src to dst
795 */
796 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
797 List<Path> allPaths = Lists.newArrayList(
798 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700799 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700800 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700801 return Optional.empty();
802 }
803
Pier Luigi91573e12018-01-23 16:06:38 +0100804 // Create a map index of suitablity-to-list of paths. For example
805 // a path in the list associated to the index 1 shares only the
806 // first hop and it is less suitable of a path belonging to the index
807 // 2 that shares leaf-spine.
808 Map<Integer, List<Path>> eligiblePaths = Maps.newHashMap();
809 // Some init steps
810 int nhop;
811 McastStoreKey mcastStoreKey;
812 Link hop;
813 PortNumber srcPort;
814 Set<PortNumber> existingPorts;
815 NextObjective nextObj;
816 // Iterate over paths looking for eligible paths
817 for (Path path : allPaths) {
818 // Unlikely, it will happen...
819 if (!src.equals(path.links().get(0).src().deviceId())) {
820 continue;
821 }
822 nhop = 0;
823 // Iterate over the links
824 while (nhop < path.links().size()) {
825 // Get the link and verify if a next related
826 // to the src device exist in the store
827 hop = path.links().get(nhop);
828 mcastStoreKey = new McastStoreKey(mcastIp, hop.src().deviceId());
829 // It does not exist in the store, exit
830 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
831 break;
Charles Chanc91c8782016-03-30 17:54:24 -0700832 }
Pier Luigi91573e12018-01-23 16:06:38 +0100833 // Get the output ports on the next
834 nextObj = mcastNextObjStore.get(mcastStoreKey).value();
835 existingPorts = getPorts(nextObj.next());
836 // And the src port on the link
837 srcPort = hop.src().port();
838 // the src port is not used as output, exit
839 if (!existingPorts.contains(srcPort)) {
840 break;
841 }
842 nhop++;
843 }
844 // n_hop defines the index
845 if (nhop > 0) {
846 eligiblePaths.compute(nhop, (index, paths) -> {
847 paths = paths == null ? Lists.newArrayList() : paths;
848 paths.add(path);
849 return paths;
850 });
Charles Chanc91c8782016-03-30 17:54:24 -0700851 }
852 }
Pier Luigi91573e12018-01-23 16:06:38 +0100853
854 // No suitable paths
855 if (eligiblePaths.isEmpty()) {
856 log.debug("No eligiblePath(s) found from {} to {}", src, dst);
857 // Otherwise, randomly pick a path
858 Collections.shuffle(allPaths);
859 return allPaths.stream().findFirst();
860 }
861
862 // Let's take the best ones
863 Integer bestIndex = eligiblePaths.keySet()
864 .stream()
865 .sorted(Comparator.reverseOrder())
866 .findFirst().orElse(null);
867 List<Path> bestPaths = eligiblePaths.get(bestIndex);
868 log.debug("{} eligiblePath(s) found from {} to {}",
869 bestPaths.size(), src, dst);
870 // randomly pick a path on the highest index
871 Collections.shuffle(bestPaths);
872 return bestPaths.stream().findFirst();
Charles Chanc91c8782016-03-30 17:54:24 -0700873 }
874
875 /**
Charles Chan72779502016-04-23 17:36:10 -0700876 * Gets device(s) of given role in given multicast group.
877 *
878 * @param mcastIp multicast IP
879 * @param role multicast role
880 * @return set of device ID or empty set if not found
881 */
882 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
883 return mcastRoleStore.entrySet().stream()
884 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
885 entry.getValue().value() == role)
886 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
887 .collect(Collectors.toSet());
888 }
889
890 /**
Charles Chana8f9dee2016-05-16 18:44:13 -0700891 * Gets source connect point of given multicast group.
892 *
893 * @param mcastIp multicast IP
894 * @return source connect point or null if not found
895 */
896 private ConnectPoint getSource(IpAddress mcastIp) {
897 return srManager.multicastRouteService.getRoutes().stream()
898 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
899 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
900 .findAny().orElse(null);
901 }
902
903 /**
Charles Chan72779502016-04-23 17:36:10 -0700904 * Gets groups which is affected by the link down event.
905 *
906 * @param link link going down
907 * @return a set of multicast IpAddress
908 */
909 private Set<IpAddress> getAffectedGroups(Link link) {
910 DeviceId deviceId = link.src().deviceId();
911 PortNumber port = link.src().port();
912 return mcastNextObjStore.entrySet().stream()
913 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
914 getPorts(entry.getValue().value().next()).contains(port))
915 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
916 .collect(Collectors.toSet());
917 }
918
919 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100920 * Gets groups which are affected by the device down event.
921 *
922 * @param deviceId device going down
923 * @return a set of multicast IpAddress
924 */
925 private Set<IpAddress> getAffectedGroups(DeviceId deviceId) {
926 return mcastNextObjStore.entrySet().stream()
927 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
928 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
929 .collect(Collectors.toSet());
930 }
931
932 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700933 * Gets egress VLAN from McastConfig.
934 *
935 * @return egress VLAN or VlanId.NONE if not configured
936 */
937 private VlanId egressVlan() {
938 McastConfig mcastConfig =
939 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
940 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
941 }
942
943 /**
944 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chana8f9dee2016-05-16 18:44:13 -0700945 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chanc91c8782016-03-30 17:54:24 -0700946 *
Charles Chana8f9dee2016-05-16 18:44:13 -0700947 * @param cp connect point; Can be null if not specified
948 * @return assigned VLAN ID
Charles Chanc91c8782016-03-30 17:54:24 -0700949 */
Charles Chana8f9dee2016-05-16 18:44:13 -0700950 private VlanId assignedVlan(ConnectPoint cp) {
951 // Use the egressVlan if it is tagged
952 if (!egressVlan().equals(VlanId.NONE)) {
953 return egressVlan();
954 }
955 // Reuse unicast VLAN if the port has subnet configured
956 if (cp != null) {
Charles Chanf9759582017-10-20 19:09:16 -0700957 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan10b0fb72017-02-02 16:20:42 -0800958 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chana8f9dee2016-05-16 18:44:13 -0700959 }
Charles Chan10b0fb72017-02-02 16:20:42 -0800960 // Use DEFAULT_VLAN if none of the above matches
961 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -0700962 }
Charles Chan72779502016-04-23 17:36:10 -0700963
964 /**
965 * Gets the spine-facing port on ingress device of given multicast group.
966 *
967 * @param mcastIp multicast IP
968 * @return spine-facing port on ingress device
969 */
970 private PortNumber ingressTransitPort(IpAddress mcastIp) {
971 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
972 .stream().findAny().orElse(null);
973 if (ingressDevice != null) {
974 NextObjective nextObj = mcastNextObjStore
975 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
976 Set<PortNumber> ports = getPorts(nextObj.next());
977
978 for (PortNumber port : ports) {
979 // Spine-facing port should have no subnet and no xconnect
980 if (srManager.deviceConfiguration != null &&
Pier Ventreb6a7f342016-11-26 21:05:22 -0800981 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chan82f19972016-05-17 13:13:55 -0700982 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan72779502016-04-23 17:36:10 -0700983 return port;
984 }
985 }
986 }
987 return null;
988 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700989
990 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100991 * Verify if the given device has sinks
992 * for the multicast group.
993 *
994 * @param deviceId device Id
995 * @param mcastIp multicast IP
996 * @return true if the device has sink for the group.
997 * False otherwise.
998 */
999 private boolean hasSinks(DeviceId deviceId, IpAddress mcastIp) {
1000 if (deviceId != null) {
1001 // Get the nextobjective
1002 Versioned<NextObjective> versionedNextObj = mcastNextObjStore.get(
1003 new McastStoreKey(mcastIp, deviceId)
1004 );
1005 // If it exists
1006 if (versionedNextObj != null) {
1007 NextObjective nextObj = versionedNextObj.value();
1008 // Retrieves all the output ports
1009 Set<PortNumber> ports = getPorts(nextObj.next());
1010 // Tries to find at least one port that is not spine-facing
1011 for (PortNumber port : ports) {
1012 // Spine-facing port should have no subnet and no xconnect
1013 if (srManager.deviceConfiguration != null &&
1014 (!srManager.deviceConfiguration.getPortSubnets(deviceId, port).isEmpty() ||
1015 srManager.xConnectHandler.hasXConnect(new ConnectPoint(deviceId, port)))) {
1016 return true;
1017 }
1018 }
1019 }
1020 }
1021 return false;
1022 }
1023
1024 /**
Jonghwan Hyune5ef7622017-08-25 17:48:36 -07001025 * Removes filtering objective for given device and port.
1026 *
1027 * @param deviceId device ID
1028 * @param port ingress port number
1029 * @param assignedVlan assigned VLAN ID
1030 * @param mcastIp multicast IP address
1031 */
1032 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
1033 // Do nothing if the port is configured as suppressed
1034 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
1035 SegmentRoutingAppConfig appConfig = srManager.cfgService
1036 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
1037 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
1038 log.info("Ignore suppressed port {}", connectPoint);
1039 return;
1040 }
1041
1042 FilteringObjective.Builder filtObjBuilder =
1043 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
1044 ObjectiveContext context = new DefaultObjectiveContext(
1045 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
1046 deviceId, port.toLong(), assignedVlan),
1047 (objective, error) ->
1048 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
1049 deviceId, port.toLong(), assignedVlan, error));
1050 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
1051 }
1052
1053 /**
1054 * Adds or removes filtering objective for given device and port.
1055 *
1056 * @param deviceId device ID
1057 * @param portNum ingress port number
1058 * @param vlanId assigned VLAN ID
1059 * @param install true to add, false to remove
1060 */
1061 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
1062 VlanId vlanId, boolean install) {
1063 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
1064 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
1065 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
1066 if (install) {
1067 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1068 } else {
1069 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1070 }
1071 }
1072 });
1073 }
Charles Chanc91c8782016-03-30 17:54:24 -07001074}