blob: 083e9e401dcf04943287e7d5f20283c350f223e1 [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();
Charles Chan72779502016-04-23 17:36:10 -0700507 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700508 // First time someone request this mcast group via this device
509 portBuilder.add(port);
510 } else {
511 // This device already serves some subscribers of this mcast group
Charles Chan72779502016-04-23 17:36:10 -0700512 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700513 // Stop if the port is already in the nextobj
514 Set<PortNumber> existingPorts = getPorts(nextObj.next());
515 if (existingPorts.contains(port)) {
516 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
517 return;
518 }
Yuta HIGUCHI6f31b3c2018-01-24 23:39:06 -0800519 portBuilder.addAll(existingPorts).add(port);
Charles Chanc91c8782016-03-30 17:54:24 -0700520 }
521 // Create, store and apply the new nextObj and fwdObj
Charles Chan72779502016-04-23 17:36:10 -0700522 ObjectiveContext context = new DefaultObjectiveContext(
523 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
524 mcastIp, deviceId, port.toLong(), assignedVlan),
525 (objective, error) ->
526 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
527 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700528 NextObjective newNextObj =
529 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
530 ForwardingObjective fwdObj =
Charles Chan72779502016-04-23 17:36:10 -0700531 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
532 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700533 srManager.flowObjectiveService.next(deviceId, newNextObj);
534 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700535 }
536
537 /**
538 * Removes a port from given multicast group on given device.
539 * This involves the update of L3 multicast group and multicast routing
540 * table entry.
541 *
542 * @param deviceId device ID
543 * @param port port to be added
544 * @param mcastIp multicast group
545 * @param assignedVlan assigned VLAN ID
546 * @return true if this is the last sink on this device
547 */
548 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
549 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700550 McastStoreKey mcastStoreKey =
551 new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700552 // This device is not serving this multicast group
Charles Chan72779502016-04-23 17:36:10 -0700553 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700554 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
555 return false;
556 }
Charles Chan72779502016-04-23 17:36:10 -0700557 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700558
559 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan72779502016-04-23 17:36:10 -0700560 // This port does not serve this multicast group
Charles Chanc91c8782016-03-30 17:54:24 -0700561 if (!existingPorts.contains(port)) {
562 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
563 return false;
564 }
565 // Copy and modify the ImmutableSet
566 existingPorts = Sets.newHashSet(existingPorts);
567 existingPorts.remove(port);
568
569 NextObjective newNextObj;
570 ForwardingObjective fwdObj;
571 if (existingPorts.isEmpty()) {
572 // If this is the last sink, remove flows and groups
573 // NOTE: Rely on GroupStore garbage collection rather than explicitly
574 // remove L3MG since there might be other flows/groups refer to
575 // the same L2IG
Charles Chan72779502016-04-23 17:36:10 -0700576 ObjectiveContext context = new DefaultObjectiveContext(
577 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
578 mcastIp, deviceId, port.toLong(), assignedVlan),
579 (objective, error) ->
580 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
581 mcastIp, deviceId, port.toLong(), assignedVlan, error));
582 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
583 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700584 srManager.flowObjectiveService.forward(deviceId, fwdObj);
585 } else {
586 // If this is not the last sink, update flows and groups
Charles Chan72779502016-04-23 17:36:10 -0700587 ObjectiveContext context = new DefaultObjectiveContext(
588 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
589 mcastIp, deviceId, port.toLong(), assignedVlan),
590 (objective, error) ->
591 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
592 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700593 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chan82f19972016-05-17 13:13:55 -0700594 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan72779502016-04-23 17:36:10 -0700595 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700596 srManager.flowObjectiveService.next(deviceId, newNextObj);
597 srManager.flowObjectiveService.forward(deviceId, fwdObj);
598 }
Charles Chanc91c8782016-03-30 17:54:24 -0700599 return existingPorts.isEmpty();
600 }
601
Charles Chan72779502016-04-23 17:36:10 -0700602 /**
603 * Removes entire group on given device.
604 *
605 * @param deviceId device ID
606 * @param mcastIp multicast group to be removed
607 * @param assignedVlan assigned VLAN ID
608 */
609 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
610 VlanId assignedVlan) {
611 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
612 // This device is not serving this multicast group
613 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
614 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
615 return;
616 }
617 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
618 // NOTE: Rely on GroupStore garbage collection rather than explicitly
619 // remove L3MG since there might be other flows/groups refer to
620 // the same L2IG
621 ObjectiveContext context = new DefaultObjectiveContext(
622 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
623 mcastIp, deviceId, assignedVlan),
624 (objective, error) ->
625 log.warn("Failed to remove {} on {}, vlan {}: {}",
626 mcastIp, deviceId, assignedVlan, error));
627 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
628 srManager.flowObjectiveService.forward(deviceId, fwdObj);
629 mcastNextObjStore.remove(mcastStoreKey);
630 mcastRoleStore.remove(mcastStoreKey);
631 }
632
Pier Luigi580fd8a2018-01-16 10:47:50 +0100633 private void installPath(IpAddress mcastIp, ConnectPoint source, Path mcastPath) {
634 // Get Links
635 List<Link> links = mcastPath.links();
636 // For each link, modify the next on the source device adding the src port
637 // and a new filter objective on the destination port
638 links.forEach(link -> {
639 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
640 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
641 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null),
642 mcastIp);
643 });
644 // Setup new transit mcast role
645 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
646 McastRole.TRANSIT);
Charles Chan72779502016-04-23 17:36:10 -0700647 }
648
Charles Chanc91c8782016-03-30 17:54:24 -0700649 /**
650 * Creates a next objective builder for multicast.
651 *
652 * @param mcastIp multicast group
653 * @param assignedVlan assigned VLAN ID
654 * @param outPorts set of output port numbers
655 * @return next objective builder
656 */
657 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
658 VlanId assignedVlan, Set<PortNumber> outPorts) {
659 int nextId = srManager.flowObjectiveService.allocateNextId();
660
661 TrafficSelector metadata =
662 DefaultTrafficSelector.builder()
663 .matchVlanId(assignedVlan)
664 .matchIPDst(mcastIp.toIpPrefix())
665 .build();
666
667 NextObjective.Builder nextObjBuilder = DefaultNextObjective
668 .builder().withId(nextId)
669 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
670 .withMeta(metadata);
671
672 outPorts.forEach(port -> {
673 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
674 if (egressVlan().equals(VlanId.NONE)) {
675 tBuilder.popVlan();
676 }
677 tBuilder.setOutput(port);
678 nextObjBuilder.addTreatment(tBuilder.build());
679 });
680
681 return nextObjBuilder;
682 }
683
684 /**
685 * Creates a forwarding objective builder for multicast.
686 *
687 * @param mcastIp multicast group
688 * @param assignedVlan assigned VLAN ID
689 * @param nextId next ID of the L3 multicast group
690 * @return forwarding objective builder
691 */
692 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
693 VlanId assignedVlan, int nextId) {
694 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000695 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
696
697 if (mcastIp.isIp4()) {
698 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
699 sbuilder.matchIPDst(mcastPrefix);
700 } else {
701 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
702 sbuilder.matchIPv6Dst(mcastPrefix);
703 }
704
705
Charles Chanc91c8782016-03-30 17:54:24 -0700706 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
707 metabuilder.matchVlanId(assignedVlan);
708
709 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
710 fwdBuilder.withSelector(sbuilder.build())
711 .withMeta(metabuilder.build())
712 .nextStep(nextId)
713 .withFlag(ForwardingObjective.Flag.SPECIFIC)
714 .fromApp(srManager.appId)
715 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
716 return fwdBuilder;
717 }
718
719 /**
720 * Creates a filtering objective builder for multicast.
721 *
722 * @param deviceId Device ID
723 * @param ingressPort ingress port of the multicast stream
724 * @param assignedVlan assigned VLAN ID
725 * @return filtering objective builder
726 */
727 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000728 VlanId assignedVlan, IpAddress mcastIp) {
Charles Chanc91c8782016-03-30 17:54:24 -0700729 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan0932eca2016-06-28 16:50:13 -0700730
Julia Fergusonf1d9c342017-08-10 18:15:24 +0000731 if (mcastIp.isIp4()) {
732 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
733 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
734 MacAddress.IPV4_MULTICAST_MASK))
735 .addCondition(Criteria.matchVlanId(egressVlan()))
736 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
737 } else {
738 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
739 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
740 MacAddress.IPV6_MULTICAST_MASK))
741 .addCondition(Criteria.matchVlanId(egressVlan()))
742 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
743 }
Charles Chan0932eca2016-06-28 16:50:13 -0700744 TrafficTreatment tt = DefaultTrafficTreatment.builder()
745 .pushVlan().setVlanId(assignedVlan).build();
746 filtBuilder.withMeta(tt);
747
Charles Chanc91c8782016-03-30 17:54:24 -0700748 return filtBuilder.permit().fromApp(srManager.appId);
749 }
750
751 /**
752 * Gets output ports information from treatments.
753 *
754 * @param treatments collection of traffic treatments
755 * @return set of output port numbers
756 */
757 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
758 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
759 treatments.forEach(treatment -> {
760 treatment.allInstructions().stream()
761 .filter(instr -> instr instanceof OutputInstruction)
762 .forEach(instr -> {
763 builder.add(((OutputInstruction) instr).port());
764 });
765 });
766 return builder.build();
767 }
768
769 /**
770 * Gets a path from src to dst.
771 * If a path was allocated before, returns the allocated path.
772 * Otherwise, randomly pick one from available paths.
773 *
774 * @param src source device ID
775 * @param dst destination device ID
776 * @param mcastIp multicast group
777 * @return an optional path from src to dst
778 */
779 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
780 List<Path> allPaths = Lists.newArrayList(
781 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700782 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700783 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700784 return Optional.empty();
785 }
786
Pier Luigi91573e12018-01-23 16:06:38 +0100787 // Create a map index of suitablity-to-list of paths. For example
788 // a path in the list associated to the index 1 shares only the
789 // first hop and it is less suitable of a path belonging to the index
790 // 2 that shares leaf-spine.
791 Map<Integer, List<Path>> eligiblePaths = Maps.newHashMap();
792 // Some init steps
793 int nhop;
794 McastStoreKey mcastStoreKey;
795 Link hop;
796 PortNumber srcPort;
797 Set<PortNumber> existingPorts;
798 NextObjective nextObj;
799 // Iterate over paths looking for eligible paths
800 for (Path path : allPaths) {
801 // Unlikely, it will happen...
802 if (!src.equals(path.links().get(0).src().deviceId())) {
803 continue;
804 }
805 nhop = 0;
806 // Iterate over the links
807 while (nhop < path.links().size()) {
808 // Get the link and verify if a next related
809 // to the src device exist in the store
810 hop = path.links().get(nhop);
811 mcastStoreKey = new McastStoreKey(mcastIp, hop.src().deviceId());
812 // It does not exist in the store, exit
813 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
814 break;
Charles Chanc91c8782016-03-30 17:54:24 -0700815 }
Pier Luigi91573e12018-01-23 16:06:38 +0100816 // Get the output ports on the next
817 nextObj = mcastNextObjStore.get(mcastStoreKey).value();
818 existingPorts = getPorts(nextObj.next());
819 // And the src port on the link
820 srcPort = hop.src().port();
821 // the src port is not used as output, exit
822 if (!existingPorts.contains(srcPort)) {
823 break;
824 }
825 nhop++;
826 }
827 // n_hop defines the index
828 if (nhop > 0) {
829 eligiblePaths.compute(nhop, (index, paths) -> {
830 paths = paths == null ? Lists.newArrayList() : paths;
831 paths.add(path);
832 return paths;
833 });
Charles Chanc91c8782016-03-30 17:54:24 -0700834 }
835 }
Pier Luigi91573e12018-01-23 16:06:38 +0100836
837 // No suitable paths
838 if (eligiblePaths.isEmpty()) {
839 log.debug("No eligiblePath(s) found from {} to {}", src, dst);
840 // Otherwise, randomly pick a path
841 Collections.shuffle(allPaths);
842 return allPaths.stream().findFirst();
843 }
844
845 // Let's take the best ones
846 Integer bestIndex = eligiblePaths.keySet()
847 .stream()
848 .sorted(Comparator.reverseOrder())
849 .findFirst().orElse(null);
850 List<Path> bestPaths = eligiblePaths.get(bestIndex);
851 log.debug("{} eligiblePath(s) found from {} to {}",
852 bestPaths.size(), src, dst);
853 // randomly pick a path on the highest index
854 Collections.shuffle(bestPaths);
855 return bestPaths.stream().findFirst();
Charles Chanc91c8782016-03-30 17:54:24 -0700856 }
857
858 /**
Charles Chan72779502016-04-23 17:36:10 -0700859 * Gets device(s) of given role in given multicast group.
860 *
861 * @param mcastIp multicast IP
862 * @param role multicast role
863 * @return set of device ID or empty set if not found
864 */
865 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
866 return mcastRoleStore.entrySet().stream()
867 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
868 entry.getValue().value() == role)
869 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
870 .collect(Collectors.toSet());
871 }
872
873 /**
Charles Chana8f9dee2016-05-16 18:44:13 -0700874 * Gets source connect point of given multicast group.
875 *
876 * @param mcastIp multicast IP
877 * @return source connect point or null if not found
878 */
879 private ConnectPoint getSource(IpAddress mcastIp) {
880 return srManager.multicastRouteService.getRoutes().stream()
881 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
882 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
883 .findAny().orElse(null);
884 }
885
886 /**
Charles Chan72779502016-04-23 17:36:10 -0700887 * Gets groups which is affected by the link down event.
888 *
889 * @param link link going down
890 * @return a set of multicast IpAddress
891 */
892 private Set<IpAddress> getAffectedGroups(Link link) {
893 DeviceId deviceId = link.src().deviceId();
894 PortNumber port = link.src().port();
895 return mcastNextObjStore.entrySet().stream()
896 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
897 getPorts(entry.getValue().value().next()).contains(port))
898 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
899 .collect(Collectors.toSet());
900 }
901
902 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100903 * Gets groups which are affected by the device down event.
904 *
905 * @param deviceId device going down
906 * @return a set of multicast IpAddress
907 */
908 private Set<IpAddress> getAffectedGroups(DeviceId deviceId) {
909 return mcastNextObjStore.entrySet().stream()
910 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
911 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
912 .collect(Collectors.toSet());
913 }
914
915 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700916 * Gets egress VLAN from McastConfig.
917 *
918 * @return egress VLAN or VlanId.NONE if not configured
919 */
920 private VlanId egressVlan() {
921 McastConfig mcastConfig =
922 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
923 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
924 }
925
926 /**
927 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chana8f9dee2016-05-16 18:44:13 -0700928 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chanc91c8782016-03-30 17:54:24 -0700929 *
Charles Chana8f9dee2016-05-16 18:44:13 -0700930 * @param cp connect point; Can be null if not specified
931 * @return assigned VLAN ID
Charles Chanc91c8782016-03-30 17:54:24 -0700932 */
Charles Chana8f9dee2016-05-16 18:44:13 -0700933 private VlanId assignedVlan(ConnectPoint cp) {
934 // Use the egressVlan if it is tagged
935 if (!egressVlan().equals(VlanId.NONE)) {
936 return egressVlan();
937 }
938 // Reuse unicast VLAN if the port has subnet configured
939 if (cp != null) {
Charles Chanf9759582017-10-20 19:09:16 -0700940 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan10b0fb72017-02-02 16:20:42 -0800941 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chana8f9dee2016-05-16 18:44:13 -0700942 }
Charles Chan10b0fb72017-02-02 16:20:42 -0800943 // Use DEFAULT_VLAN if none of the above matches
944 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chanc91c8782016-03-30 17:54:24 -0700945 }
Charles Chan72779502016-04-23 17:36:10 -0700946
947 /**
948 * Gets the spine-facing port on ingress device of given multicast group.
949 *
950 * @param mcastIp multicast IP
951 * @return spine-facing port on ingress device
952 */
953 private PortNumber ingressTransitPort(IpAddress mcastIp) {
954 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
955 .stream().findAny().orElse(null);
956 if (ingressDevice != null) {
957 NextObjective nextObj = mcastNextObjStore
958 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
959 Set<PortNumber> ports = getPorts(nextObj.next());
960
961 for (PortNumber port : ports) {
962 // Spine-facing port should have no subnet and no xconnect
963 if (srManager.deviceConfiguration != null &&
Pier Ventreb6a7f342016-11-26 21:05:22 -0800964 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chan82f19972016-05-17 13:13:55 -0700965 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan72779502016-04-23 17:36:10 -0700966 return port;
967 }
968 }
969 }
970 return null;
971 }
Jonghwan Hyune5ef7622017-08-25 17:48:36 -0700972
973 /**
Pier Luigi580fd8a2018-01-16 10:47:50 +0100974 * Verify if the given device has sinks
975 * for the multicast group.
976 *
977 * @param deviceId device Id
978 * @param mcastIp multicast IP
979 * @return true if the device has sink for the group.
980 * False otherwise.
981 */
982 private boolean hasSinks(DeviceId deviceId, IpAddress mcastIp) {
983 if (deviceId != null) {
984 // Get the nextobjective
985 Versioned<NextObjective> versionedNextObj = mcastNextObjStore.get(
986 new McastStoreKey(mcastIp, deviceId)
987 );
988 // If it exists
989 if (versionedNextObj != null) {
990 NextObjective nextObj = versionedNextObj.value();
991 // Retrieves all the output ports
992 Set<PortNumber> ports = getPorts(nextObj.next());
993 // Tries to find at least one port that is not spine-facing
994 for (PortNumber port : ports) {
995 // Spine-facing port should have no subnet and no xconnect
996 if (srManager.deviceConfiguration != null &&
997 (!srManager.deviceConfiguration.getPortSubnets(deviceId, port).isEmpty() ||
998 srManager.xConnectHandler.hasXConnect(new ConnectPoint(deviceId, port)))) {
999 return true;
1000 }
1001 }
1002 }
1003 }
1004 return false;
1005 }
1006
1007 /**
Jonghwan Hyune5ef7622017-08-25 17:48:36 -07001008 * Removes filtering objective for given device and port.
1009 *
1010 * @param deviceId device ID
1011 * @param port ingress port number
1012 * @param assignedVlan assigned VLAN ID
1013 * @param mcastIp multicast IP address
1014 */
1015 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
1016 // Do nothing if the port is configured as suppressed
1017 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
1018 SegmentRoutingAppConfig appConfig = srManager.cfgService
1019 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
1020 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
1021 log.info("Ignore suppressed port {}", connectPoint);
1022 return;
1023 }
1024
1025 FilteringObjective.Builder filtObjBuilder =
1026 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
1027 ObjectiveContext context = new DefaultObjectiveContext(
1028 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
1029 deviceId, port.toLong(), assignedVlan),
1030 (objective, error) ->
1031 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
1032 deviceId, port.toLong(), assignedVlan, error));
1033 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
1034 }
1035
1036 /**
1037 * Adds or removes filtering objective for given device and port.
1038 *
1039 * @param deviceId device ID
1040 * @param portNum ingress port number
1041 * @param vlanId assigned VLAN ID
1042 * @param install true to add, false to remove
1043 */
1044 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
1045 VlanId vlanId, boolean install) {
1046 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
1047 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
1048 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
1049 if (install) {
1050 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1051 } else {
1052 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1053 }
1054 }
1055 });
1056 }
Charles Chanc91c8782016-03-30 17:54:24 -07001057}