blob: 9aca787255665d156b81ec1d4b91c1b834df9c43 [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;
21import com.google.common.collect.Sets;
22import org.onlab.packet.Ethernet;
23import org.onlab.packet.IpAddress;
24import org.onlab.packet.IpPrefix;
25import org.onlab.packet.MacAddress;
26import org.onlab.packet.VlanId;
27import org.onlab.util.KryoNamespace;
Pier Luigi580fd8a2018-01-16 10:47:50 +010028import org.onosproject.cluster.NodeId;
Charles Chanc91c8782016-03-30 17:54:24 -070029import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
Ray Milkeyae0068a2017-08-15 11:02:29 -070031import org.onosproject.net.config.basics.McastConfig;
Charles Chanc91c8782016-03-30 17:54:24 -070032import org.onosproject.net.ConnectPoint;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.Link;
35import org.onosproject.net.Path;
36import org.onosproject.net.PortNumber;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.flow.criteria.Criteria;
42import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan72779502016-04-23 17:36:10 -070046import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070047import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
Charles Chan72779502016-04-23 17:36:10 -070050import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070051import org.onosproject.net.mcast.McastEvent;
52import org.onosproject.net.mcast.McastRouteInfo;
53import org.onosproject.net.topology.TopologyService;
Charles Chan370a65b2016-05-10 17:29:47 -070054import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan72779502016-04-23 17:36:10 -070055import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chanc91c8782016-03-30 17:54:24 -070056import org.onosproject.store.serializers.KryoNamespaces;
57import org.onosproject.store.service.ConsistentMap;
58import org.onosproject.store.service.Serializer;
59import org.onosproject.store.service.StorageService;
Pier Luigi580fd8a2018-01-16 10:47:50 +010060import org.onosproject.store.service.Versioned;
Charles Chanc91c8782016-03-30 17:54:24 -070061import org.slf4j.Logger;
62import org.slf4j.LoggerFactory;
63
64import java.util.Collection;
65import java.util.Collections;
66import java.util.List;
Charles Chan72779502016-04-23 17:36:10 -070067import java.util.Map;
Charles Chanc91c8782016-03-30 17:54:24 -070068import java.util.Optional;
69import java.util.Set;
Charles Chan72779502016-04-23 17:36:10 -070070import java.util.stream.Collectors;
71
72import static com.google.common.base.Preconditions.checkState;
Charles Chan10b0fb72017-02-02 16:20:42 -080073import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -070074
75/**
Charles Chan1eaf4802016-04-18 13:44:03 -070076 * Handles multicast-related events.
Charles Chanc91c8782016-03-30 17:54:24 -070077 */
Charles Chan1eaf4802016-04-18 13:44:03 -070078public class McastHandler {
79 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chanc91c8782016-03-30 17:54:24 -070080 private final SegmentRoutingManager srManager;
81 private final ApplicationId coreAppId;
Charles Chan82f19972016-05-17 13:13:55 -070082 private final StorageService storageService;
83 private final TopologyService topologyService;
Charles Chan72779502016-04-23 17:36:10 -070084 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
85 private final KryoNamespace.Builder mcastKryo;
86 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
87
88 /**
89 * Role in the multicast tree.
90 */
91 public enum McastRole {
92 /**
93 * The device is the ingress device of this group.
94 */
95 INGRESS,
96 /**
97 * The device is the transit device of this group.
98 */
99 TRANSIT,
100 /**
101 * The device is the egress device of this group.
102 */
103 EGRESS
104 }
Charles Chanc91c8782016-03-30 17:54:24 -0700105
106 /**
107 * Constructs the McastEventHandler.
108 *
109 * @param srManager Segment Routing manager
110 */
Charles Chan1eaf4802016-04-18 13:44:03 -0700111 public McastHandler(SegmentRoutingManager srManager) {
Charles Chanc91c8782016-03-30 17:54:24 -0700112 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chanc91c8782016-03-30 17:54:24 -0700113 this.srManager = srManager;
114 this.storageService = srManager.storageService;
115 this.topologyService = srManager.topologyService;
Charles Chan72779502016-04-23 17:36:10 -0700116 mcastKryo = new KryoNamespace.Builder()
Charles Chanc91c8782016-03-30 17:54:24 -0700117 .register(KryoNamespaces.API)
Charles Chan72779502016-04-23 17:36:10 -0700118 .register(McastStoreKey.class)
119 .register(McastRole.class);
Charles Chanc91c8782016-03-30 17:54:24 -0700120 mcastNextObjStore = storageService
Charles Chan72779502016-04-23 17:36:10 -0700121 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chanc91c8782016-03-30 17:54:24 -0700122 .withName("onos-mcast-nextobj-store")
Charles Chan4922a172016-05-23 16:45:45 -0700123 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chanc91c8782016-03-30 17:54:24 -0700124 .build();
Charles Chan72779502016-04-23 17:36:10 -0700125 mcastRoleStore = storageService
126 .<McastStoreKey, McastRole>consistentMapBuilder()
127 .withName("onos-mcast-role-store")
Charles Chan4922a172016-05-23 16:45:45 -0700128 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan72779502016-04-23 17:36:10 -0700129 .build();
130 }
131
132 /**
133 * Read initial multicast from mcast store.
134 */
Charles Chan82f19972016-05-17 13:13:55 -0700135 protected void init() {
Charles Chan72779502016-04-23 17:36:10 -0700136 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
137 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
138 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
139 sinks.forEach(sink -> {
140 processSinkAddedInternal(source, sink, mcastRoute.group());
141 });
142 });
Charles Chanc91c8782016-03-30 17:54:24 -0700143 }
144
145 /**
146 * Processes the SOURCE_ADDED event.
147 *
148 * @param event McastEvent with SOURCE_ADDED type
149 */
150 protected void processSourceAdded(McastEvent event) {
151 log.info("processSourceAdded {}", event);
152 McastRouteInfo mcastRouteInfo = event.subject();
153 if (!mcastRouteInfo.isComplete()) {
154 log.info("Incompleted McastRouteInfo. Abort.");
155 return;
156 }
157 ConnectPoint source = mcastRouteInfo.source().orElse(null);
158 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
159 IpAddress mcastIp = mcastRouteInfo.route().group();
160
161 sinks.forEach(sink -> {
162 processSinkAddedInternal(source, sink, mcastIp);
163 });
164 }
165
166 /**
167 * Processes the SINK_ADDED event.
168 *
169 * @param event McastEvent with SINK_ADDED type
170 */
171 protected void processSinkAdded(McastEvent event) {
172 log.info("processSinkAdded {}", event);
173 McastRouteInfo mcastRouteInfo = event.subject();
174 if (!mcastRouteInfo.isComplete()) {
175 log.info("Incompleted McastRouteInfo. Abort.");
176 return;
177 }
178 ConnectPoint source = mcastRouteInfo.source().orElse(null);
179 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
180 IpAddress mcastIp = mcastRouteInfo.route().group();
181
182 processSinkAddedInternal(source, sink, mcastIp);
183 }
184
185 /**
186 * Processes the SINK_REMOVED event.
187 *
188 * @param event McastEvent with SINK_REMOVED type
189 */
190 protected void processSinkRemoved(McastEvent event) {
191 log.info("processSinkRemoved {}", event);
192 McastRouteInfo mcastRouteInfo = event.subject();
193 if (!mcastRouteInfo.isComplete()) {
194 log.info("Incompleted McastRouteInfo. Abort.");
195 return;
196 }
197 ConnectPoint source = mcastRouteInfo.source().orElse(null);
198 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
199 IpAddress mcastIp = mcastRouteInfo.route().group();
Charles Chanc91c8782016-03-30 17:54:24 -0700200
Charles Chan0932eca2016-06-28 16:50:13 -0700201 // Continue only when this instance is the master of source device
202 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
203 log.info("Skip {} due to lack of mastership of the source device {}",
204 mcastIp, source.deviceId());
205 return;
206 }
207
Charles Chanc91c8782016-03-30 17:54:24 -0700208 // When source and sink are on the same device
209 if (source.deviceId().equals(sink.deviceId())) {
210 // Source and sink are on even the same port. There must be something wrong.
211 if (source.port().equals(sink.port())) {
212 log.warn("Sink is on the same port of source. Abort");
213 return;
214 }
Charles Chana8f9dee2016-05-16 18:44:13 -0700215 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chanc91c8782016-03-30 17:54:24 -0700216 return;
217 }
218
219 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700220 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700221 if (isLast) {
222 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
223 }
Charles Chanc91c8782016-03-30 17:54:24 -0700224
225 // If this is the last sink on the device, also update upstream
226 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
227 if (mcastPath.isPresent()) {
228 List<Link> links = Lists.newArrayList(mcastPath.get().links());
229 Collections.reverse(links);
230 for (Link link : links) {
231 if (isLast) {
232 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
Charles Chana8f9dee2016-05-16 18:44:13 -0700233 mcastIp,
234 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan72779502016-04-23 17:36:10 -0700235 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chanc91c8782016-03-30 17:54:24 -0700236 }
237 }
238 }
239 }
240
241 /**
242 * Establishes a path from source to sink for given multicast group.
243 *
244 * @param source connect point of the multicast source
245 * @param sink connection point of the multicast sink
246 * @param mcastIp multicast group IP address
247 */
248 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
249 IpAddress mcastIp) {
Charles Chan0932eca2016-06-28 16:50:13 -0700250 // Continue only when this instance is the master of source device
251 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
252 log.info("Skip {} due to lack of mastership of the source device {}",
253 source.deviceId());
254 return;
255 }
256
Charles Chan72779502016-04-23 17:36:10 -0700257 // Process the ingress device
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000258 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source), mcastIp);
Charles Chan72779502016-04-23 17:36:10 -0700259
Charles Chanc91c8782016-03-30 17:54:24 -0700260 // When source and sink are on the same device
261 if (source.deviceId().equals(sink.deviceId())) {
262 // Source and sink are on even the same port. There must be something wrong.
263 if (source.port().equals(sink.port())) {
264 log.warn("Sink is on the same port of source. Abort");
265 return;
266 }
Charles Chana8f9dee2016-05-16 18:44:13 -0700267 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700268 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chanc91c8782016-03-30 17:54:24 -0700269 return;
270 }
271
Charles Chanc91c8782016-03-30 17:54:24 -0700272 // Find a path. If present, create/update groups and flows for each hop
273 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
274 if (mcastPath.isPresent()) {
Charles Chan72779502016-04-23 17:36:10 -0700275 List<Link> links = mcastPath.get().links();
276 checkState(links.size() == 2,
277 "Path in leaf-spine topology should always be two hops: ", links);
278
279 links.forEach(link -> {
Charles Chana8f9dee2016-05-16 18:44:13 -0700280 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
281 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000282 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null), mcastIp);
Charles Chanc91c8782016-03-30 17:54:24 -0700283 });
Charles Chan72779502016-04-23 17:36:10 -0700284
Charles Chanc91c8782016-03-30 17:54:24 -0700285 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700286 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700287
288 // Setup mcast roles
289 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
290 McastRole.INGRESS);
291 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
292 McastRole.TRANSIT);
293 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
294 McastRole.EGRESS);
295 } else {
296 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
297 source.deviceId(), sink.deviceId());
Charles Chanc91c8782016-03-30 17:54:24 -0700298 }
299 }
300
301 /**
Charles Chan72779502016-04-23 17:36:10 -0700302 * Processes the LINK_DOWN event.
303 *
304 * @param affectedLink Link that is going down
305 */
306 protected void processLinkDown(Link affectedLink) {
Charles Chan72779502016-04-23 17:36:10 -0700307 getAffectedGroups(affectedLink).forEach(mcastIp -> {
Pier Luigi580fd8a2018-01-16 10:47:50 +0100308 // TODO Optimize when the group editing is in place
309 log.debug("Processing link down {} for group {}",
310 affectedLink, mcastIp);
311
Charles Chan72779502016-04-23 17:36:10 -0700312 // Find out the ingress, transit and egress device of affected group
313 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
314 .stream().findAny().orElse(null);
315 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
316 .stream().findAny().orElse(null);
317 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
Charles Chana8f9dee2016-05-16 18:44:13 -0700318 ConnectPoint source = getSource(mcastIp);
319
320 // Do not proceed if any of these info is missing
321 if (ingressDevice == null || transitDevice == null
322 || egressDevices == null || source == null) {
323 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
324 ingressDevice, transitDevice, egressDevices, source);
Charles Chan72779502016-04-23 17:36:10 -0700325 return;
326 }
327
Charles Chan0932eca2016-06-28 16:50:13 -0700328 // Continue only when this instance is the master of source device
329 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
330 log.info("Skip {} due to lack of mastership of the source device {}",
331 source.deviceId());
332 return;
333 }
334
Charles Chan72779502016-04-23 17:36:10 -0700335 // Remove entire transit
Charles Chana8f9dee2016-05-16 18:44:13 -0700336 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700337
338 // Remove transit-facing port on ingress device
339 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
340 if (ingressTransitPort != null) {
Charles Chana8f9dee2016-05-16 18:44:13 -0700341 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700342 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
343 }
344
345 // Construct a new path for each egress device
346 egressDevices.forEach(egressDevice -> {
347 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
348 if (mcastPath.isPresent()) {
Pier Luigi580fd8a2018-01-16 10:47:50 +0100349 installPath(mcastIp, source, mcastPath.get());
Charles Chan72779502016-04-23 17:36:10 -0700350 } else {
351 log.warn("Fail to recover egress device {} from link failure {}",
352 egressDevice, affectedLink);
Charles Chana8f9dee2016-05-16 18:44:13 -0700353 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700354 }
355 });
356 });
357 }
358
359 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100360 * Process the DEVICE_DOWN event.
361 *
362 * @param deviceDown device going down
363 */
364 protected void processDeviceDown(DeviceId deviceDown) {
365 // Get the mcast groups affected by the device going down
366 getAffectedGroups(deviceDown).forEach(mcastIp -> {
367 // TODO Optimize when the group editing is in place
368 log.debug("Processing device down {} for group {}",
369 deviceDown, mcastIp);
370
371 // Find out the ingress, transit and egress device of affected group
372 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
373 .stream().findAny().orElse(null);
374 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
375 .stream().findAny().orElse(null);
376 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
377 ConnectPoint source = getSource(mcastIp);
378
379 // Do not proceed if ingress device or source of this group are missing
380 // If sinks are in other leafs, we have ingress, transit, egress, and source
381 // If sinks are in the same leaf, we have just ingress and source
382 if (ingressDevice == null || source == null) {
383 log.warn("Missing ingress {} or source {} for group {}",
384 ingressDevice, source, mcastIp);
385 return;
386 }
387
388 // Continue only when we have the mastership on the operation
389 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
390 // When the source is available we just check the mastership
391 if (srManager.deviceService.isAvailable(source.deviceId())) {
392 log.info("Skip {} due to lack of mastership of the source device {}",
393 mcastIp, source.deviceId());
394 return;
395 }
396 // Fallback with Leadership service
397 // source id is used a topic
398 NodeId leader = srManager.leadershipService.runForLeadership(
399 source.deviceId().toString()).leaderNodeId();
400 // Verify if this node is the leader
401 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
402 log.info("Skip {} due to lack of leadership on the topic {}",
403 mcastIp, source.deviceId());
404 return;
405 }
406 }
407
408 // If it exists, we have to remove it in any case
409 if (transitDevice != null) {
410 // Remove entire transit
411 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
412 }
413 // If the ingress is down
414 if (ingressDevice.equals(deviceDown)) {
415 // Remove entire ingress
416 removeGroupFromDevice(ingressDevice, mcastIp, assignedVlan(source));
417 // If other sinks different from the ingress exist
418 if (!egressDevices.isEmpty()) {
419 // Remove all the remaining egress
420 egressDevices.forEach(
421 egressDevice -> removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null))
422 );
423 }
424 } else {
425 // Egress or transit could be down at this point
426 // Get the ingress-transit port if it exists
427 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
428 if (ingressTransitPort != null) {
429 // Remove transit-facing port on ingress device
430 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
431 }
432 // One of the egress device is down
433 if (egressDevices.contains(deviceDown)) {
434 // Remove entire device down
435 removeGroupFromDevice(deviceDown, mcastIp, assignedVlan(null));
436 // Remove the device down from egress
437 egressDevices.remove(deviceDown);
438 // If there are no more egress and ingress does not have sinks
439 if (egressDevices.isEmpty() && !hasSinks(ingressDevice, mcastIp)) {
440 // Remove entire ingress
441 mcastRoleStore.remove(new McastStoreKey(mcastIp, ingressDevice));
442 // We have done
443 return;
444 }
445 }
446 // Construct a new path for each egress device
447 egressDevices.forEach(egressDevice -> {
448 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
449 // If there is a new path
450 if (mcastPath.isPresent()) {
451 // Let's install the new mcast path for this egress
452 installPath(mcastIp, source, mcastPath.get());
453 } else {
454 // We were not able to find an alternative path for this egress
455 log.warn("Fail to recover egress device {} from device down {}",
456 egressDevice, deviceDown);
457 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
458 }
459 });
460 }
461 });
462 }
463
464 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700465 * Adds filtering objective for given device and port.
466 *
467 * @param deviceId device ID
468 * @param port ingress port number
469 * @param assignedVlan assigned VLAN ID
470 */
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000471 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700472 // Do nothing if the port is configured as suppressed
Charles Chan370a65b2016-05-10 17:29:47 -0700473 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
474 SegmentRoutingAppConfig appConfig = srManager.cfgService
475 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
476 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
477 log.info("Ignore suppressed port {}", connectPoint);
Charles Chanc91c8782016-03-30 17:54:24 -0700478 return;
479 }
480
481 FilteringObjective.Builder filtObjBuilder =
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000482 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
Charles Chan72779502016-04-23 17:36:10 -0700483 ObjectiveContext context = new DefaultObjectiveContext(
484 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan10b0fb72017-02-02 16:20:42 -0800485 deviceId, port.toLong(), assignedVlan),
Charles Chan72779502016-04-23 17:36:10 -0700486 (objective, error) ->
487 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan10b0fb72017-02-02 16:20:42 -0800488 deviceId, port.toLong(), assignedVlan, error));
Charles Chan72779502016-04-23 17:36:10 -0700489 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chanc91c8782016-03-30 17:54:24 -0700490 }
491
492 /**
493 * Adds a port to given multicast group on given device. This involves the
494 * update of L3 multicast group and multicast routing table entry.
495 *
496 * @param deviceId device ID
497 * @param port port to be added
498 * @param mcastIp multicast group
499 * @param assignedVlan assigned VLAN ID
500 */
501 private void addPortToDevice(DeviceId deviceId, PortNumber port,
502 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700503 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700504 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan72779502016-04-23 17:36:10 -0700505 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700506 // First time someone request this mcast group via this device
507 portBuilder.add(port);
508 } else {
509 // This device already serves some subscribers of this mcast group
Charles Chan72779502016-04-23 17:36:10 -0700510 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700511 // Stop if the port is already in the nextobj
512 Set<PortNumber> existingPorts = getPorts(nextObj.next());
513 if (existingPorts.contains(port)) {
514 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
515 return;
516 }
Yuta HIGUCHI6f31b3c2018-01-24 23:39:06 -0800517 portBuilder.addAll(existingPorts).add(port);
Charles Chanc91c8782016-03-30 17:54:24 -0700518 }
519 // Create, store and apply the new nextObj and fwdObj
Charles Chan72779502016-04-23 17:36:10 -0700520 ObjectiveContext context = new DefaultObjectiveContext(
521 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
522 mcastIp, deviceId, port.toLong(), assignedVlan),
523 (objective, error) ->
524 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
525 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700526 NextObjective newNextObj =
527 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
528 ForwardingObjective fwdObj =
Charles Chan72779502016-04-23 17:36:10 -0700529 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
530 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700531 srManager.flowObjectiveService.next(deviceId, newNextObj);
532 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700533 }
534
535 /**
536 * Removes a port from given multicast group on given device.
537 * This involves the update of L3 multicast group and multicast routing
538 * table entry.
539 *
540 * @param deviceId device ID
541 * @param port port to be added
542 * @param mcastIp multicast group
543 * @param assignedVlan assigned VLAN ID
544 * @return true if this is the last sink on this device
545 */
546 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
547 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700548 McastStoreKey mcastStoreKey =
549 new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700550 // This device is not serving this multicast group
Charles Chan72779502016-04-23 17:36:10 -0700551 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700552 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
553 return false;
554 }
Charles Chan72779502016-04-23 17:36:10 -0700555 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700556
557 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan72779502016-04-23 17:36:10 -0700558 // This port does not serve this multicast group
Charles Chanc91c8782016-03-30 17:54:24 -0700559 if (!existingPorts.contains(port)) {
560 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
561 return false;
562 }
563 // Copy and modify the ImmutableSet
564 existingPorts = Sets.newHashSet(existingPorts);
565 existingPorts.remove(port);
566
567 NextObjective newNextObj;
568 ForwardingObjective fwdObj;
569 if (existingPorts.isEmpty()) {
570 // If this is the last sink, remove flows and groups
571 // NOTE: Rely on GroupStore garbage collection rather than explicitly
572 // remove L3MG since there might be other flows/groups refer to
573 // the same L2IG
Charles Chan72779502016-04-23 17:36:10 -0700574 ObjectiveContext context = new DefaultObjectiveContext(
575 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
576 mcastIp, deviceId, port.toLong(), assignedVlan),
577 (objective, error) ->
578 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
579 mcastIp, deviceId, port.toLong(), assignedVlan, error));
580 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
581 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700582 srManager.flowObjectiveService.forward(deviceId, fwdObj);
583 } else {
584 // If this is not the last sink, update flows and groups
Charles Chan72779502016-04-23 17:36:10 -0700585 ObjectiveContext context = new DefaultObjectiveContext(
586 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
587 mcastIp, deviceId, port.toLong(), assignedVlan),
588 (objective, error) ->
589 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
590 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700591 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chan82f19972016-05-17 13:13:55 -0700592 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan72779502016-04-23 17:36:10 -0700593 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700594 srManager.flowObjectiveService.next(deviceId, newNextObj);
595 srManager.flowObjectiveService.forward(deviceId, fwdObj);
596 }
Charles Chanc91c8782016-03-30 17:54:24 -0700597 return existingPorts.isEmpty();
598 }
599
Charles Chan72779502016-04-23 17:36:10 -0700600 /**
601 * Removes entire group on given device.
602 *
603 * @param deviceId device ID
604 * @param mcastIp multicast group to be removed
605 * @param assignedVlan assigned VLAN ID
606 */
607 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
608 VlanId assignedVlan) {
609 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
610 // This device is not serving this multicast group
611 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
612 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
613 return;
614 }
615 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
616 // NOTE: Rely on GroupStore garbage collection rather than explicitly
617 // remove L3MG since there might be other flows/groups refer to
618 // the same L2IG
619 ObjectiveContext context = new DefaultObjectiveContext(
620 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
621 mcastIp, deviceId, assignedVlan),
622 (objective, error) ->
623 log.warn("Failed to remove {} on {}, vlan {}: {}",
624 mcastIp, deviceId, assignedVlan, error));
625 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
626 srManager.flowObjectiveService.forward(deviceId, fwdObj);
627 mcastNextObjStore.remove(mcastStoreKey);
628 mcastRoleStore.remove(mcastStoreKey);
629 }
630
Pier Luigi580fd8a2018-01-16 10:47:50 +0100631 private void installPath(IpAddress mcastIp, ConnectPoint source, Path mcastPath) {
632 // Get Links
633 List<Link> links = mcastPath.links();
634 // For each link, modify the next on the source device adding the src port
635 // and a new filter objective on the destination port
636 links.forEach(link -> {
637 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
638 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
639 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null),
640 mcastIp);
641 });
642 // Setup new transit mcast role
643 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
644 McastRole.TRANSIT);
Charles Chan72779502016-04-23 17:36:10 -0700645 }
646
Charles Chanc91c8782016-03-30 17:54:24 -0700647 /**
648 * Creates a next objective builder for multicast.
649 *
650 * @param mcastIp multicast group
651 * @param assignedVlan assigned VLAN ID
652 * @param outPorts set of output port numbers
653 * @return next objective builder
654 */
655 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
656 VlanId assignedVlan, Set<PortNumber> outPorts) {
657 int nextId = srManager.flowObjectiveService.allocateNextId();
658
659 TrafficSelector metadata =
660 DefaultTrafficSelector.builder()
661 .matchVlanId(assignedVlan)
662 .matchIPDst(mcastIp.toIpPrefix())
663 .build();
664
665 NextObjective.Builder nextObjBuilder = DefaultNextObjective
666 .builder().withId(nextId)
667 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
668 .withMeta(metadata);
669
670 outPorts.forEach(port -> {
671 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
672 if (egressVlan().equals(VlanId.NONE)) {
673 tBuilder.popVlan();
674 }
675 tBuilder.setOutput(port);
676 nextObjBuilder.addTreatment(tBuilder.build());
677 });
678
679 return nextObjBuilder;
680 }
681
682 /**
683 * Creates a forwarding objective builder for multicast.
684 *
685 * @param mcastIp multicast group
686 * @param assignedVlan assigned VLAN ID
687 * @param nextId next ID of the L3 multicast group
688 * @return forwarding objective builder
689 */
690 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
691 VlanId assignedVlan, int nextId) {
692 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000693 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
694
695 if (mcastIp.isIp4()) {
696 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
697 sbuilder.matchIPDst(mcastPrefix);
698 } else {
699 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
700 sbuilder.matchIPv6Dst(mcastPrefix);
701 }
702
703
Charles Chanc91c8782016-03-30 17:54:24 -0700704 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
705 metabuilder.matchVlanId(assignedVlan);
706
707 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
708 fwdBuilder.withSelector(sbuilder.build())
709 .withMeta(metabuilder.build())
710 .nextStep(nextId)
711 .withFlag(ForwardingObjective.Flag.SPECIFIC)
712 .fromApp(srManager.appId)
713 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
714 return fwdBuilder;
715 }
716
717 /**
718 * Creates a filtering objective builder for multicast.
719 *
720 * @param deviceId Device ID
721 * @param ingressPort ingress port of the multicast stream
722 * @param assignedVlan assigned VLAN ID
723 * @return filtering objective builder
724 */
725 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000726 VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700727 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan0932eca2016-06-28 16:50:13 -0700728
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000729 if (mcastIp.isIp4()) {
730 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
731 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
732 MacAddress.IPV4_MULTICAST_MASK))
733 .addCondition(Criteria.matchVlanId(egressVlan()))
734 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
735 } else {
736 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
737 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
738 MacAddress.IPV6_MULTICAST_MASK))
739 .addCondition(Criteria.matchVlanId(egressVlan()))
740 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
741 }
Charles Chan0932eca2016-06-28 16:50:13 -0700742 TrafficTreatment tt = DefaultTrafficTreatment.builder()
743 .pushVlan().setVlanId(assignedVlan).build();
744 filtBuilder.withMeta(tt);
745
Charles Chanc91c8782016-03-30 17:54:24 -0700746 return filtBuilder.permit().fromApp(srManager.appId);
747 }
748
749 /**
750 * Gets output ports information from treatments.
751 *
752 * @param treatments collection of traffic treatments
753 * @return set of output port numbers
754 */
755 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
756 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
757 treatments.forEach(treatment -> {
758 treatment.allInstructions().stream()
759 .filter(instr -> instr instanceof OutputInstruction)
760 .forEach(instr -> {
761 builder.add(((OutputInstruction) instr).port());
762 });
763 });
764 return builder.build();
765 }
766
767 /**
768 * Gets a path from src to dst.
769 * If a path was allocated before, returns the allocated path.
770 * Otherwise, randomly pick one from available paths.
771 *
772 * @param src source device ID
773 * @param dst destination device ID
774 * @param mcastIp multicast group
775 * @return an optional path from src to dst
776 */
777 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
778 List<Path> allPaths = Lists.newArrayList(
779 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700780 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700781 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700782 return Optional.empty();
783 }
784
785 // If one of the available path is used before, use the same path
Charles Chan72779502016-04-23 17:36:10 -0700786 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
787 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
788 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700789 Set<PortNumber> existingPorts = getPorts(nextObj.next());
790 for (Path path : allPaths) {
791 PortNumber srcPort = path.links().get(0).src().port();
792 if (existingPorts.contains(srcPort)) {
793 return Optional.of(path);
794 }
795 }
796 }
797 // Otherwise, randomly pick a path
798 Collections.shuffle(allPaths);
799 return allPaths.stream().findFirst();
800 }
801
802 /**
Charles Chan72779502016-04-23 17:36:10 -0700803 * Gets device(s) of given role in given multicast group.
804 *
805 * @param mcastIp multicast IP
806 * @param role multicast role
807 * @return set of device ID or empty set if not found
808 */
809 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
810 return mcastRoleStore.entrySet().stream()
811 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
812 entry.getValue().value() == role)
813 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
814 .collect(Collectors.toSet());
815 }
816
817 /**
Charles Chana8f9dee2016-05-16 18:44:13 -0700818 * Gets source connect point of given multicast group.
819 *
820 * @param mcastIp multicast IP
821 * @return source connect point or null if not found
822 */
823 private ConnectPoint getSource(IpAddress mcastIp) {
824 return srManager.multicastRouteService.getRoutes().stream()
825 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
826 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
827 .findAny().orElse(null);
828 }
829
830 /**
Charles Chan72779502016-04-23 17:36:10 -0700831 * Gets groups which is affected by the link down event.
832 *
833 * @param link link going down
834 * @return a set of multicast IpAddress
835 */
836 private Set<IpAddress> getAffectedGroups(Link link) {
837 DeviceId deviceId = link.src().deviceId();
838 PortNumber port = link.src().port();
839 return mcastNextObjStore.entrySet().stream()
840 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
841 getPorts(entry.getValue().value().next()).contains(port))
842 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
843 .collect(Collectors.toSet());
844 }
845
846 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100847 * Gets groups which are affected by the device down event.
848 *
849 * @param deviceId device going down
850 * @return a set of multicast IpAddress
851 */
852 private Set<IpAddress> getAffectedGroups(DeviceId deviceId) {
853 return mcastNextObjStore.entrySet().stream()
854 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
855 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
856 .collect(Collectors.toSet());
857 }
858
859 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700860 * Gets egress VLAN from McastConfig.
861 *
862 * @return egress VLAN or VlanId.NONE if not configured
863 */
864 private VlanId egressVlan() {
865 McastConfig mcastConfig =
866 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
867 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
868 }
869
870 /**
871 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chana8f9dee2016-05-16 18:44:13 -0700872 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chanc91c8782016-03-30 17:54:24 -0700873 *
Charles Chana8f9dee2016-05-16 18:44:13 -0700874 * @param cp connect point; Can be null if not specified
875 * @return assigned VLAN ID
Charles Chanc91c8782016-03-30 17:54:24 -0700876 */
Charles Chana8f9dee2016-05-16 18:44:13 -0700877 private VlanId assignedVlan(ConnectPoint cp) {
878 // Use the egressVlan if it is tagged
879 if (!egressVlan().equals(VlanId.NONE)) {
880 return egressVlan();
881 }
882 // Reuse unicast VLAN if the port has subnet configured
883 if (cp != null) {
Charles Chanf9759582017-10-20 19:09:16 -0700884 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan10b0fb72017-02-02 16:20:42 -0800885 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chana8f9dee2016-05-16 18:44:13 -0700886 }
Charles Chan10b0fb72017-02-02 16:20:42 -0800887 // Use DEFAULT_VLAN if none of the above matches
888 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -0700889 }
Charles Chan72779502016-04-23 17:36:10 -0700890
891 /**
892 * Gets the spine-facing port on ingress device of given multicast group.
893 *
894 * @param mcastIp multicast IP
895 * @return spine-facing port on ingress device
896 */
897 private PortNumber ingressTransitPort(IpAddress mcastIp) {
898 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
899 .stream().findAny().orElse(null);
900 if (ingressDevice != null) {
901 NextObjective nextObj = mcastNextObjStore
902 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
903 Set<PortNumber> ports = getPorts(nextObj.next());
904
905 for (PortNumber port : ports) {
906 // Spine-facing port should have no subnet and no xconnect
907 if (srManager.deviceConfiguration != null &&
Pier Ventreb6a7f342016-11-26 21:05:22 -0800908 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chan82f19972016-05-17 13:13:55 -0700909 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan72779502016-04-23 17:36:10 -0700910 return port;
911 }
912 }
913 }
914 return null;
915 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700916
917 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100918 * Verify if the given device has sinks
919 * for the multicast group.
920 *
921 * @param deviceId device Id
922 * @param mcastIp multicast IP
923 * @return true if the device has sink for the group.
924 * False otherwise.
925 */
926 private boolean hasSinks(DeviceId deviceId, IpAddress mcastIp) {
927 if (deviceId != null) {
928 // Get the nextobjective
929 Versioned<NextObjective> versionedNextObj = mcastNextObjStore.get(
930 new McastStoreKey(mcastIp, deviceId)
931 );
932 // If it exists
933 if (versionedNextObj != null) {
934 NextObjective nextObj = versionedNextObj.value();
935 // Retrieves all the output ports
936 Set<PortNumber> ports = getPorts(nextObj.next());
937 // Tries to find at least one port that is not spine-facing
938 for (PortNumber port : ports) {
939 // Spine-facing port should have no subnet and no xconnect
940 if (srManager.deviceConfiguration != null &&
941 (!srManager.deviceConfiguration.getPortSubnets(deviceId, port).isEmpty() ||
942 srManager.xConnectHandler.hasXConnect(new ConnectPoint(deviceId, port)))) {
943 return true;
944 }
945 }
946 }
947 }
948 return false;
949 }
950
951 /**
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700952 * Removes filtering objective for given device and port.
953 *
954 * @param deviceId device ID
955 * @param port ingress port number
956 * @param assignedVlan assigned VLAN ID
957 * @param mcastIp multicast IP address
958 */
959 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
960 // Do nothing if the port is configured as suppressed
961 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
962 SegmentRoutingAppConfig appConfig = srManager.cfgService
963 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
964 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
965 log.info("Ignore suppressed port {}", connectPoint);
966 return;
967 }
968
969 FilteringObjective.Builder filtObjBuilder =
970 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
971 ObjectiveContext context = new DefaultObjectiveContext(
972 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
973 deviceId, port.toLong(), assignedVlan),
974 (objective, error) ->
975 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
976 deviceId, port.toLong(), assignedVlan, error));
977 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
978 }
979
980 /**
981 * Adds or removes filtering objective for given device and port.
982 *
983 * @param deviceId device ID
984 * @param portNum ingress port number
985 * @param vlanId assigned VLAN ID
986 * @param install true to add, false to remove
987 */
988 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
989 VlanId vlanId, boolean install) {
990 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
991 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
992 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
993 if (install) {
994 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
995 } else {
996 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
997 }
998 }
999 });
1000 }
Charles Chanc91c8782016-03-30 17:54:24 -07001001}