blob: a4c9d196db65d31d61bb41910d12de50dff46e47 [file] [log] [blame]
Charles Chand55e84d2016-03-30 17:54:24 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chand55e84d2016-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 Luigi5131dad2018-01-23 16:06:38 +010021import com.google.common.collect.Maps;
Charles Chand55e84d2016-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 Luigi24592652018-01-16 10:47:50 +010029import org.onosproject.cluster.NodeId;
Charles Chand55e84d2016-03-30 17:54:24 -070030import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
Ray Milkey6c1f0f02017-08-15 11:02:29 -070032import org.onosproject.net.config.basics.McastConfig;
Charles Chand55e84d2016-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 Chan2199c302016-04-23 17:36:10 -070047import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070048import org.onosproject.net.flowobjective.FilteringObjective;
49import org.onosproject.net.flowobjective.ForwardingObjective;
50import org.onosproject.net.flowobjective.NextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070051import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070052import org.onosproject.net.mcast.McastEvent;
Pier Luigi3dfd8352018-01-25 16:16:02 +010053import org.onosproject.net.mcast.McastRoute;
Charles Chand55e84d2016-03-30 17:54:24 -070054import org.onosproject.net.mcast.McastRouteInfo;
Pier Luigi80cb7482018-02-15 16:33:08 +010055import org.onosproject.net.topology.Topology;
Charles Chand55e84d2016-03-30 17:54:24 -070056import org.onosproject.net.topology.TopologyService;
Charles Chan6ea94fc2016-05-10 17:29:47 -070057import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan2199c302016-04-23 17:36:10 -070058import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chand55e84d2016-03-30 17:54:24 -070059import org.onosproject.store.serializers.KryoNamespaces;
60import org.onosproject.store.service.ConsistentMap;
61import org.onosproject.store.service.Serializer;
62import org.onosproject.store.service.StorageService;
Pier Luigi24592652018-01-16 10:47:50 +010063import org.onosproject.store.service.Versioned;
Charles Chand55e84d2016-03-30 17:54:24 -070064import org.slf4j.Logger;
65import org.slf4j.LoggerFactory;
66
Pier Luigi3dfd8352018-01-25 16:16:02 +010067import java.time.Instant;
Charles Chand55e84d2016-03-30 17:54:24 -070068import java.util.Collection;
69import java.util.Collections;
Pier Luigi5131dad2018-01-23 16:06:38 +010070import java.util.Comparator;
Charles Chand55e84d2016-03-30 17:54:24 -070071import java.util.List;
Charles Chan2199c302016-04-23 17:36:10 -070072import java.util.Map;
Charles Chand55e84d2016-03-30 17:54:24 -070073import java.util.Optional;
74import java.util.Set;
Pier Luigi3dfd8352018-01-25 16:16:02 +010075import java.util.concurrent.ScheduledExecutorService;
76import java.util.concurrent.TimeUnit;
77import java.util.concurrent.locks.Lock;
78import java.util.concurrent.locks.ReentrantLock;
Charles Chan2199c302016-04-23 17:36:10 -070079import java.util.stream.Collectors;
80
81import static com.google.common.base.Preconditions.checkState;
Pier Luigi3dfd8352018-01-25 16:16:02 +010082import static java.util.concurrent.Executors.newScheduledThreadPool;
83import static org.onlab.util.Tools.groupedThreads;
Charles Chan59cc16d2017-02-02 16:20:42 -080084import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -070085
86/**
Charles Chand2990362016-04-18 13:44:03 -070087 * Handles multicast-related events.
Charles Chand55e84d2016-03-30 17:54:24 -070088 */
Charles Chand2990362016-04-18 13:44:03 -070089public class McastHandler {
90 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chand55e84d2016-03-30 17:54:24 -070091 private final SegmentRoutingManager srManager;
92 private final ApplicationId coreAppId;
Charles Chanfc5c7802016-05-17 13:13:55 -070093 private final StorageService storageService;
94 private final TopologyService topologyService;
Charles Chan2199c302016-04-23 17:36:10 -070095 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
96 private final KryoNamespace.Builder mcastKryo;
97 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
98
Pier Luigi3dfd8352018-01-25 16:16:02 +010099 // Mcast lock to serialize local operations
100 private final Lock mcastLock = new ReentrantLock();
101
102 /**
103 * Acquires the lock used when making mcast changes.
104 */
105 private void mcastLock() {
106 mcastLock.lock();
107 }
108
109 /**
110 * Releases the lock used when making mcast changes.
111 */
112 private void mcastUnlock() {
113 mcastLock.unlock();
114 }
115
116 // Stability threshold for Mcast. Seconds
117 private static final long MCAST_STABLITY_THRESHOLD = 5;
118 // Last change done
119 private Instant lastMcastChange = Instant.now();
120
121 /**
122 * Determines if mcast in the network has been stable in the last
123 * MCAST_STABLITY_THRESHOLD seconds, by comparing the current time
124 * to the last mcast change timestamp.
125 *
126 * @return true if stable
127 */
128 private boolean isMcastStable() {
129 long last = (long) (lastMcastChange.toEpochMilli() / 1000.0);
130 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
131 log.debug("Mcast stable since {}s", now - last);
132 return (now - last) > MCAST_STABLITY_THRESHOLD;
133 }
134
135 // Verify interval for Mcast
136 private static final long MCAST_VERIFY_INTERVAL = 30;
137
138 // Executor for mcast bucket corrector
139 private ScheduledExecutorService executorService
140 = newScheduledThreadPool(1, groupedThreads("mcastBktCorrector", "mcastbktC-%d", log));
141
Charles Chan2199c302016-04-23 17:36:10 -0700142 /**
143 * Role in the multicast tree.
144 */
145 public enum McastRole {
146 /**
147 * The device is the ingress device of this group.
148 */
149 INGRESS,
150 /**
151 * The device is the transit device of this group.
152 */
153 TRANSIT,
154 /**
155 * The device is the egress device of this group.
156 */
157 EGRESS
158 }
Charles Chand55e84d2016-03-30 17:54:24 -0700159
160 /**
161 * Constructs the McastEventHandler.
162 *
163 * @param srManager Segment Routing manager
164 */
Charles Chand2990362016-04-18 13:44:03 -0700165 public McastHandler(SegmentRoutingManager srManager) {
Charles Chand55e84d2016-03-30 17:54:24 -0700166 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chand55e84d2016-03-30 17:54:24 -0700167 this.srManager = srManager;
168 this.storageService = srManager.storageService;
169 this.topologyService = srManager.topologyService;
Charles Chan2199c302016-04-23 17:36:10 -0700170 mcastKryo = new KryoNamespace.Builder()
Charles Chand55e84d2016-03-30 17:54:24 -0700171 .register(KryoNamespaces.API)
Charles Chan2199c302016-04-23 17:36:10 -0700172 .register(McastStoreKey.class)
173 .register(McastRole.class);
Charles Chand55e84d2016-03-30 17:54:24 -0700174 mcastNextObjStore = storageService
Charles Chan2199c302016-04-23 17:36:10 -0700175 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chand55e84d2016-03-30 17:54:24 -0700176 .withName("onos-mcast-nextobj-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700177 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chand55e84d2016-03-30 17:54:24 -0700178 .build();
Charles Chan2199c302016-04-23 17:36:10 -0700179 mcastRoleStore = storageService
180 .<McastStoreKey, McastRole>consistentMapBuilder()
181 .withName("onos-mcast-role-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700182 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan2199c302016-04-23 17:36:10 -0700183 .build();
Pier Luigi3dfd8352018-01-25 16:16:02 +0100184 // Init the executor service and the buckets corrector
185 executorService.scheduleWithFixedDelay(new McastBucketCorrector(), 10,
186 MCAST_VERIFY_INTERVAL,
187 TimeUnit.SECONDS);
Charles Chan2199c302016-04-23 17:36:10 -0700188 }
189
190 /**
191 * Read initial multicast from mcast store.
192 */
Charles Chanfc5c7802016-05-17 13:13:55 -0700193 protected void init() {
Charles Chan2199c302016-04-23 17:36:10 -0700194 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
195 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
196 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
197 sinks.forEach(sink -> {
198 processSinkAddedInternal(source, sink, mcastRoute.group());
199 });
200 });
Charles Chand55e84d2016-03-30 17:54:24 -0700201 }
202
203 /**
Pier Luigi3dfd8352018-01-25 16:16:02 +0100204 * Clean up when deactivating the application.
205 */
206 protected void terminate() {
207 executorService.shutdown();
208 }
209
210 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700211 * Processes the SOURCE_ADDED event.
212 *
213 * @param event McastEvent with SOURCE_ADDED type
214 */
215 protected void processSourceAdded(McastEvent event) {
216 log.info("processSourceAdded {}", event);
217 McastRouteInfo mcastRouteInfo = event.subject();
218 if (!mcastRouteInfo.isComplete()) {
219 log.info("Incompleted McastRouteInfo. Abort.");
220 return;
221 }
222 ConnectPoint source = mcastRouteInfo.source().orElse(null);
223 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
224 IpAddress mcastIp = mcastRouteInfo.route().group();
225
Pier Luigi3dfd8352018-01-25 16:16:02 +0100226 sinks.forEach(sink -> processSinkAddedInternal(source, sink, mcastIp));
Charles Chand55e84d2016-03-30 17:54:24 -0700227 }
228
229 /**
230 * Processes the SINK_ADDED event.
231 *
232 * @param event McastEvent with SINK_ADDED type
233 */
234 protected void processSinkAdded(McastEvent event) {
235 log.info("processSinkAdded {}", event);
236 McastRouteInfo mcastRouteInfo = event.subject();
237 if (!mcastRouteInfo.isComplete()) {
238 log.info("Incompleted McastRouteInfo. Abort.");
239 return;
240 }
241 ConnectPoint source = mcastRouteInfo.source().orElse(null);
242 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
243 IpAddress mcastIp = mcastRouteInfo.route().group();
244
245 processSinkAddedInternal(source, sink, mcastIp);
246 }
247
248 /**
249 * Processes the SINK_REMOVED event.
250 *
251 * @param event McastEvent with SINK_REMOVED type
252 */
253 protected void processSinkRemoved(McastEvent event) {
254 log.info("processSinkRemoved {}", event);
255 McastRouteInfo mcastRouteInfo = event.subject();
256 if (!mcastRouteInfo.isComplete()) {
257 log.info("Incompleted McastRouteInfo. Abort.");
258 return;
259 }
260 ConnectPoint source = mcastRouteInfo.source().orElse(null);
261 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
262 IpAddress mcastIp = mcastRouteInfo.route().group();
Charles Chand55e84d2016-03-30 17:54:24 -0700263
Pier Luigi3dfd8352018-01-25 16:16:02 +0100264 processSinkRemovedInternal(source, sink, mcastIp);
265 }
Charles Chan1588e7b2016-06-28 16:50:13 -0700266
Pier Luigi3dfd8352018-01-25 16:16:02 +0100267 /**
Pier Luigi1c34a442018-02-02 16:19:11 +0100268 * Processes the ROUTE_REMOVED event.
269 *
270 * @param event McastEvent with ROUTE_REMOVED type
271 */
272 protected void processRouteRemoved(McastEvent event) {
273 log.info("processRouteRemoved {}", event);
274 McastRouteInfo mcastRouteInfo = event.subject();
275 if (!mcastRouteInfo.source().isPresent()) {
276 log.info("Incompleted McastRouteInfo. Abort.");
277 return;
278 }
279 // Get group ip and ingress connect point
280 IpAddress mcastIp = mcastRouteInfo.route().group();
281 ConnectPoint source = mcastRouteInfo.source().get();
282
283 processRouteRemovedInternal(source, mcastIp);
284 }
285
286 /**
287 * Removes the entire mcast tree related to this group.
288 *
289 * @param mcastIp multicast group IP address
290 */
291 private void processRouteRemovedInternal(ConnectPoint source, IpAddress mcastIp) {
292 lastMcastChange = Instant.now();
293 mcastLock();
294 try {
295 log.debug("Processing route down for group {}", mcastIp);
296
297 // Find out the ingress, transit and egress device of the affected group
298 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
299 .stream().findAny().orElse(null);
300 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
301 .stream().findAny().orElse(null);
302 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
303
304 // Verify leadership on the operation
305 if (!isLeader(source)) {
306 log.debug("Skip {} due to lack of leadership", mcastIp);
307 return;
308 }
309
310 // If there are egress devices, sinks could be only on the ingress
311 if (!egressDevices.isEmpty()) {
312 egressDevices.forEach(
313 deviceId -> removeGroupFromDevice(deviceId, mcastIp, assignedVlan(null))
314 );
315 }
316 // Transit could be null
317 if (transitDevice != null) {
318 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
319 }
320 // Ingress device should be not null
321 if (ingressDevice != null) {
322 removeGroupFromDevice(ingressDevice, mcastIp, assignedVlan(source));
323 }
324
325 } finally {
326 mcastUnlock();
327 }
328 }
329
330 /**
Pier Luigi3dfd8352018-01-25 16:16:02 +0100331 * Removes a path from source to sink for given multicast group.
332 *
333 * @param source connect point of the multicast source
334 * @param sink connection point of the multicast sink
335 * @param mcastIp multicast group IP address
336 */
337 private void processSinkRemovedInternal(ConnectPoint source, ConnectPoint sink,
338 IpAddress mcastIp) {
339 lastMcastChange = Instant.now();
340 mcastLock();
341 try {
Pier Luigi1c34a442018-02-02 16:19:11 +0100342 // Verify leadership on the operation
343 if (!isLeader(source)) {
344 log.debug("Skip {} due to lack of leadership", mcastIp);
Charles Chand55e84d2016-03-30 17:54:24 -0700345 return;
346 }
Charles Chand55e84d2016-03-30 17:54:24 -0700347
Pier Luigi3dfd8352018-01-25 16:16:02 +0100348 // When source and sink are on the same device
349 if (source.deviceId().equals(sink.deviceId())) {
350 // Source and sink are on even the same port. There must be something wrong.
351 if (source.port().equals(sink.port())) {
352 log.warn("Skip {} since sink {} is on the same port of source {}. Abort",
353 mcastIp, sink, source);
354 return;
355 }
356 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
357 return;
358 }
Charles Chand55e84d2016-03-30 17:54:24 -0700359
Pier Luigi3dfd8352018-01-25 16:16:02 +0100360 // Process the egress device
361 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
362 if (isLast) {
363 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
364 }
365
366 // If this is the last sink on the device, also update upstream
367 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
368 if (mcastPath.isPresent()) {
369 List<Link> links = Lists.newArrayList(mcastPath.get().links());
370 Collections.reverse(links);
371 for (Link link : links) {
372 if (isLast) {
373 isLast = removePortFromDevice(
374 link.src().deviceId(),
375 link.src().port(),
376 mcastIp,
377 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null)
378 );
379 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
380 }
Charles Chand55e84d2016-03-30 17:54:24 -0700381 }
382 }
Pier Luigi3dfd8352018-01-25 16:16:02 +0100383 } finally {
384 mcastUnlock();
Charles Chand55e84d2016-03-30 17:54:24 -0700385 }
386 }
387
388 /**
389 * Establishes a path from source to sink for given multicast group.
390 *
391 * @param source connect point of the multicast source
392 * @param sink connection point of the multicast sink
393 * @param mcastIp multicast group IP address
394 */
395 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
396 IpAddress mcastIp) {
Pier Luigi3dfd8352018-01-25 16:16:02 +0100397 lastMcastChange = Instant.now();
398 mcastLock();
399 try {
400 // Continue only when this instance is the master of source device
401 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
402 log.debug("Skip {} due to lack of mastership of the source device {}",
403 mcastIp, source.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700404 return;
405 }
Charles Chand55e84d2016-03-30 17:54:24 -0700406
Pier Luigi3dfd8352018-01-25 16:16:02 +0100407 // Process the ingress device
408 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source), mcastIp);
Charles Chan2199c302016-04-23 17:36:10 -0700409
Pier Luigi3dfd8352018-01-25 16:16:02 +0100410 // When source and sink are on the same device
411 if (source.deviceId().equals(sink.deviceId())) {
412 // Source and sink are on even the same port. There must be something wrong.
413 if (source.port().equals(sink.port())) {
414 log.warn("Skip {} since sink {} is on the same port of source {}. Abort",
415 mcastIp, sink, source);
416 return;
417 }
418 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
419 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
420 return;
421 }
Charles Chan2199c302016-04-23 17:36:10 -0700422
Pier Luigi3dfd8352018-01-25 16:16:02 +0100423 // Find a path. If present, create/update groups and flows for each hop
424 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
425 if (mcastPath.isPresent()) {
426 List<Link> links = mcastPath.get().links();
427 checkState(links.size() == 2,
428 "Path in leaf-spine topology should always be two hops: ", links);
Charles Chan2199c302016-04-23 17:36:10 -0700429
Pier Luigi3dfd8352018-01-25 16:16:02 +0100430 links.forEach(link -> {
431 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
432 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
433 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null), mcastIp);
434 });
435
436 // Process the egress device
437 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
438
439 // Setup mcast roles
440 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
441 McastRole.INGRESS);
442 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
443 McastRole.TRANSIT);
444 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
445 McastRole.EGRESS);
446 } else {
447 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
448 source.deviceId(), sink.deviceId());
449 }
450 } finally {
451 mcastUnlock();
Charles Chand55e84d2016-03-30 17:54:24 -0700452 }
453 }
454
455 /**
Charles Chan2199c302016-04-23 17:36:10 -0700456 * Processes the LINK_DOWN event.
457 *
458 * @param affectedLink Link that is going down
459 */
460 protected void processLinkDown(Link affectedLink) {
Pier Luigi3dfd8352018-01-25 16:16:02 +0100461 lastMcastChange = Instant.now();
462 mcastLock();
463 try {
464 // Get groups affected by the link down event
465 getAffectedGroups(affectedLink).forEach(mcastIp -> {
466 // TODO Optimize when the group editing is in place
467 log.debug("Processing link down {} for group {}",
468 affectedLink, mcastIp);
Pier Luigi24592652018-01-16 10:47:50 +0100469
Pier Luigi3dfd8352018-01-25 16:16:02 +0100470 // Find out the ingress, transit and egress device of affected group
471 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
472 .stream().findAny().orElse(null);
473 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
474 .stream().findAny().orElse(null);
475 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
476 ConnectPoint source = getSource(mcastIp);
Charles Chan8d449862016-05-16 18:44:13 -0700477
Pier Luigi3dfd8352018-01-25 16:16:02 +0100478 // Do not proceed if any of these info is missing
479 if (ingressDevice == null || transitDevice == null
480 || egressDevices == null || source == null) {
481 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
482 ingressDevice, transitDevice, egressDevices, source);
483 return;
Charles Chan2199c302016-04-23 17:36:10 -0700484 }
Pier Luigi3dfd8352018-01-25 16:16:02 +0100485
486 // Continue only when this instance is the master of source device
487 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
488 log.debug("Skip {} due to lack of mastership of the source device {}",
489 source.deviceId());
490 return;
491 }
492
493 // Remove entire transit
494 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
495
496 // Remove transit-facing port on ingress device
497 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
498 if (ingressTransitPort != null) {
499 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
500 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
501 }
502
503 // Construct a new path for each egress device
504 egressDevices.forEach(egressDevice -> {
505 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
506 if (mcastPath.isPresent()) {
507 installPath(mcastIp, source, mcastPath.get());
508 } else {
509 log.warn("Fail to recover egress device {} from link failure {}",
510 egressDevice, affectedLink);
511 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
512 }
513 });
Charles Chan2199c302016-04-23 17:36:10 -0700514 });
Pier Luigi3dfd8352018-01-25 16:16:02 +0100515 } finally {
516 mcastUnlock();
517 }
Charles Chan2199c302016-04-23 17:36:10 -0700518 }
519
520 /**
Pier Luigi24592652018-01-16 10:47:50 +0100521 * Process the DEVICE_DOWN event.
522 *
523 * @param deviceDown device going down
524 */
525 protected void processDeviceDown(DeviceId deviceDown) {
Pier Luigi3dfd8352018-01-25 16:16:02 +0100526 lastMcastChange = Instant.now();
527 mcastLock();
528 try {
529 // Get the mcast groups affected by the device going down
530 getAffectedGroups(deviceDown).forEach(mcastIp -> {
531 // TODO Optimize when the group editing is in place
532 log.debug("Processing device down {} for group {}",
533 deviceDown, mcastIp);
Pier Luigi24592652018-01-16 10:47:50 +0100534
Pier Luigi3dfd8352018-01-25 16:16:02 +0100535 // Find out the ingress, transit and egress device of affected group
536 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
537 .stream().findAny().orElse(null);
538 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
539 .stream().findAny().orElse(null);
540 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
541 ConnectPoint source = getSource(mcastIp);
Pier Luigi24592652018-01-16 10:47:50 +0100542
Pier Luigi3dfd8352018-01-25 16:16:02 +0100543 // Do not proceed if ingress device or source of this group are missing
544 // If sinks are in other leafs, we have ingress, transit, egress, and source
545 // If sinks are in the same leaf, we have just ingress and source
546 if (ingressDevice == null || source == null) {
547 log.warn("Missing ingress {} or source {} for group {}",
548 ingressDevice, source, mcastIp);
Pier Luigi24592652018-01-16 10:47:50 +0100549 return;
550 }
Pier Luigi24592652018-01-16 10:47:50 +0100551
Pier Luigi1c34a442018-02-02 16:19:11 +0100552 // Verify leadership on the operation
553 if (!isLeader(source)) {
554 log.debug("Skip {} due to lack of leadership", mcastIp);
555 return;
Pier Luigi24592652018-01-16 10:47:50 +0100556 }
Pier Luigi3dfd8352018-01-25 16:16:02 +0100557
558 // If it exists, we have to remove it in any case
559 if (transitDevice != null) {
560 // Remove entire transit
561 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
562 }
563 // If the ingress is down
564 if (ingressDevice.equals(deviceDown)) {
565 // Remove entire ingress
566 removeGroupFromDevice(ingressDevice, mcastIp, assignedVlan(source));
567 // If other sinks different from the ingress exist
568 if (!egressDevices.isEmpty()) {
569 // Remove all the remaining egress
570 egressDevices.forEach(
571 egressDevice -> removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null))
572 );
Pier Luigi24592652018-01-16 10:47:50 +0100573 }
Pier Luigi3dfd8352018-01-25 16:16:02 +0100574 } else {
575 // Egress or transit could be down at this point
576 // Get the ingress-transit port if it exists
577 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
578 if (ingressTransitPort != null) {
579 // Remove transit-facing port on ingress device
580 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
581 }
582 // One of the egress device is down
583 if (egressDevices.contains(deviceDown)) {
584 // Remove entire device down
585 removeGroupFromDevice(deviceDown, mcastIp, assignedVlan(null));
586 // Remove the device down from egress
587 egressDevices.remove(deviceDown);
588 // If there are no more egress and ingress does not have sinks
589 if (egressDevices.isEmpty() && !hasSinks(ingressDevice, mcastIp)) {
590 // Remove entire ingress
591 mcastRoleStore.remove(new McastStoreKey(mcastIp, ingressDevice));
592 // We have done
593 return;
594 }
595 }
596 // Construct a new path for each egress device
597 egressDevices.forEach(egressDevice -> {
598 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
599 // If there is a new path
600 if (mcastPath.isPresent()) {
601 // Let's install the new mcast path for this egress
602 installPath(mcastIp, source, mcastPath.get());
603 } else {
604 // We were not able to find an alternative path for this egress
605 log.warn("Fail to recover egress device {} from device down {}",
606 egressDevice, deviceDown);
607 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
608 }
609 });
610 }
611 });
612 } finally {
613 mcastUnlock();
614 }
Pier Luigi24592652018-01-16 10:47:50 +0100615 }
616
617 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700618 * Adds filtering objective for given device and port.
619 *
620 * @param deviceId device ID
621 * @param port ingress port number
622 * @param assignedVlan assigned VLAN ID
623 */
Julia Ferguson65428c32017-08-10 18:15:24 +0000624 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
Charles Chand55e84d2016-03-30 17:54:24 -0700625 // Do nothing if the port is configured as suppressed
Charles Chan6ea94fc2016-05-10 17:29:47 -0700626 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
627 SegmentRoutingAppConfig appConfig = srManager.cfgService
628 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
629 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
630 log.info("Ignore suppressed port {}", connectPoint);
Charles Chand55e84d2016-03-30 17:54:24 -0700631 return;
632 }
633
634 FilteringObjective.Builder filtObjBuilder =
Julia Ferguson65428c32017-08-10 18:15:24 +0000635 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
Charles Chan2199c302016-04-23 17:36:10 -0700636 ObjectiveContext context = new DefaultObjectiveContext(
637 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800638 deviceId, port.toLong(), assignedVlan),
Charles Chan2199c302016-04-23 17:36:10 -0700639 (objective, error) ->
640 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800641 deviceId, port.toLong(), assignedVlan, error));
Charles Chan2199c302016-04-23 17:36:10 -0700642 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700643 }
644
645 /**
646 * Adds a port to given multicast group on given device. This involves the
647 * update of L3 multicast group and multicast routing table entry.
648 *
649 * @param deviceId device ID
650 * @param port port to be added
651 * @param mcastIp multicast group
652 * @param assignedVlan assigned VLAN ID
653 */
654 private void addPortToDevice(DeviceId deviceId, PortNumber port,
655 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700656 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700657 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Pier Luigi7b7a29d2018-01-19 10:24:53 +0100658 NextObjective newNextObj;
Charles Chan2199c302016-04-23 17:36:10 -0700659 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700660 // First time someone request this mcast group via this device
661 portBuilder.add(port);
Pier Luigi7b7a29d2018-01-19 10:24:53 +0100662 // New nextObj
663 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
664 portBuilder.build(), null).add();
665 // Store the new port
666 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700667 } else {
668 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700669 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700670 // Stop if the port is already in the nextobj
671 Set<PortNumber> existingPorts = getPorts(nextObj.next());
672 if (existingPorts.contains(port)) {
673 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
674 return;
675 }
Pier Luigi7b7a29d2018-01-19 10:24:53 +0100676 // Let's add the port and reuse the previous one
Yuta HIGUCHI7e57aaa2018-02-09 18:05:23 -0800677 portBuilder.addAll(existingPorts).add(port);
Pier Luigi7b7a29d2018-01-19 10:24:53 +0100678 // Reuse previous nextObj
679 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
680 portBuilder.build(), nextObj.id()).addToExisting();
681 // Store the final next objective and send only the difference to the driver
682 mcastNextObjStore.put(mcastStoreKey, newNextObj);
683 // Add just the new port
684 portBuilder = ImmutableSet.builder();
685 portBuilder.add(port);
686 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
687 portBuilder.build(), nextObj.id()).addToExisting();
Charles Chand55e84d2016-03-30 17:54:24 -0700688 }
689 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700690 ObjectiveContext context = new DefaultObjectiveContext(
691 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
692 mcastIp, deviceId, port.toLong(), assignedVlan),
693 (objective, error) ->
694 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
695 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700696 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700697 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chand55e84d2016-03-30 17:54:24 -0700698 srManager.flowObjectiveService.next(deviceId, newNextObj);
699 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700700 }
701
702 /**
703 * Removes a port from given multicast group on given device.
704 * This involves the update of L3 multicast group and multicast routing
705 * table entry.
706 *
707 * @param deviceId device ID
708 * @param port port to be added
709 * @param mcastIp multicast group
710 * @param assignedVlan assigned VLAN ID
711 * @return true if this is the last sink on this device
712 */
713 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
714 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700715 McastStoreKey mcastStoreKey =
716 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700717 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700718 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700719 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
720 return false;
721 }
Charles Chan2199c302016-04-23 17:36:10 -0700722 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700723
724 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700725 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700726 if (!existingPorts.contains(port)) {
727 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
728 return false;
729 }
730 // Copy and modify the ImmutableSet
731 existingPorts = Sets.newHashSet(existingPorts);
732 existingPorts.remove(port);
733
734 NextObjective newNextObj;
Pier Luigib525fe92018-01-19 10:24:53 +0100735 ObjectiveContext context;
Charles Chand55e84d2016-03-30 17:54:24 -0700736 ForwardingObjective fwdObj;
737 if (existingPorts.isEmpty()) {
Pier Luigib525fe92018-01-19 10:24:53 +0100738 // If this is the last sink, remove flows and last bucket
Charles Chand55e84d2016-03-30 17:54:24 -0700739 // NOTE: Rely on GroupStore garbage collection rather than explicitly
740 // remove L3MG since there might be other flows/groups refer to
741 // the same L2IG
Pier Luigib525fe92018-01-19 10:24:53 +0100742 context = new DefaultObjectiveContext(
Charles Chan2199c302016-04-23 17:36:10 -0700743 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
744 mcastIp, deviceId, port.toLong(), assignedVlan),
745 (objective, error) ->
746 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
747 mcastIp, deviceId, port.toLong(), assignedVlan, error));
748 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
749 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700750 } else {
751 // If this is not the last sink, update flows and groups
Pier Luigib525fe92018-01-19 10:24:53 +0100752 context = new DefaultObjectiveContext(
Charles Chan2199c302016-04-23 17:36:10 -0700753 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
754 mcastIp, deviceId, port.toLong(), assignedVlan),
755 (objective, error) ->
756 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
757 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Pier Luigib525fe92018-01-19 10:24:53 +0100758 // Here we store the next objective with the remaining port
759 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
760 existingPorts, nextObj.id()).removeFromExisting();
Charles Chanfc5c7802016-05-17 13:13:55 -0700761 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan2199c302016-04-23 17:36:10 -0700762 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700763 }
Pier Luigib525fe92018-01-19 10:24:53 +0100764 // Let's modify the next objective removing the bucket
765 newNextObj = nextObjBuilder(mcastIp, assignedVlan,
766 ImmutableSet.of(port), nextObj.id()).removeFromExisting();
767 srManager.flowObjectiveService.next(deviceId, newNextObj);
768 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700769 return existingPorts.isEmpty();
770 }
771
Charles Chan2199c302016-04-23 17:36:10 -0700772 /**
773 * Removes entire group on given device.
774 *
775 * @param deviceId device ID
776 * @param mcastIp multicast group to be removed
777 * @param assignedVlan assigned VLAN ID
778 */
779 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
780 VlanId assignedVlan) {
781 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
782 // This device is not serving this multicast group
783 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
784 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
785 return;
786 }
787 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
788 // NOTE: Rely on GroupStore garbage collection rather than explicitly
789 // remove L3MG since there might be other flows/groups refer to
790 // the same L2IG
791 ObjectiveContext context = new DefaultObjectiveContext(
792 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
793 mcastIp, deviceId, assignedVlan),
794 (objective, error) ->
795 log.warn("Failed to remove {} on {}, vlan {}: {}",
796 mcastIp, deviceId, assignedVlan, error));
797 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
798 srManager.flowObjectiveService.forward(deviceId, fwdObj);
799 mcastNextObjStore.remove(mcastStoreKey);
800 mcastRoleStore.remove(mcastStoreKey);
801 }
802
Pier Luigi24592652018-01-16 10:47:50 +0100803 private void installPath(IpAddress mcastIp, ConnectPoint source, Path mcastPath) {
804 // Get Links
805 List<Link> links = mcastPath.links();
806 // For each link, modify the next on the source device adding the src port
807 // and a new filter objective on the destination port
808 links.forEach(link -> {
809 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
810 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
811 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null),
812 mcastIp);
813 });
814 // Setup new transit mcast role
815 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
816 McastRole.TRANSIT);
Charles Chan2199c302016-04-23 17:36:10 -0700817 }
818
Charles Chand55e84d2016-03-30 17:54:24 -0700819 /**
820 * Creates a next objective builder for multicast.
821 *
822 * @param mcastIp multicast group
823 * @param assignedVlan assigned VLAN ID
824 * @param outPorts set of output port numbers
825 * @return next objective builder
826 */
827 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
Pier Luigi7b7a29d2018-01-19 10:24:53 +0100828 VlanId assignedVlan, Set<PortNumber> outPorts, Integer nextId) {
829 // If nextId is null allocate a new one
830 if (nextId == null) {
831 nextId = srManager.flowObjectiveService.allocateNextId();
832 }
Charles Chand55e84d2016-03-30 17:54:24 -0700833
834 TrafficSelector metadata =
835 DefaultTrafficSelector.builder()
836 .matchVlanId(assignedVlan)
837 .matchIPDst(mcastIp.toIpPrefix())
838 .build();
839
840 NextObjective.Builder nextObjBuilder = DefaultNextObjective
841 .builder().withId(nextId)
842 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
843 .withMeta(metadata);
844
845 outPorts.forEach(port -> {
846 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
847 if (egressVlan().equals(VlanId.NONE)) {
848 tBuilder.popVlan();
849 }
850 tBuilder.setOutput(port);
851 nextObjBuilder.addTreatment(tBuilder.build());
852 });
853
854 return nextObjBuilder;
855 }
856
857 /**
858 * Creates a forwarding objective builder for multicast.
859 *
860 * @param mcastIp multicast group
861 * @param assignedVlan assigned VLAN ID
862 * @param nextId next ID of the L3 multicast group
863 * @return forwarding objective builder
864 */
865 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
866 VlanId assignedVlan, int nextId) {
867 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Ferguson65428c32017-08-10 18:15:24 +0000868 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
869
870 if (mcastIp.isIp4()) {
871 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
872 sbuilder.matchIPDst(mcastPrefix);
873 } else {
874 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
875 sbuilder.matchIPv6Dst(mcastPrefix);
876 }
877
878
Charles Chand55e84d2016-03-30 17:54:24 -0700879 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
880 metabuilder.matchVlanId(assignedVlan);
881
882 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
883 fwdBuilder.withSelector(sbuilder.build())
884 .withMeta(metabuilder.build())
885 .nextStep(nextId)
886 .withFlag(ForwardingObjective.Flag.SPECIFIC)
887 .fromApp(srManager.appId)
888 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
889 return fwdBuilder;
890 }
891
892 /**
893 * Creates a filtering objective builder for multicast.
894 *
895 * @param deviceId Device ID
896 * @param ingressPort ingress port of the multicast stream
897 * @param assignedVlan assigned VLAN ID
898 * @return filtering objective builder
899 */
900 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Julia Ferguson65428c32017-08-10 18:15:24 +0000901 VlanId assignedVlan, IpAddress mcastIp) {
Charles Chand55e84d2016-03-30 17:54:24 -0700902 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan1588e7b2016-06-28 16:50:13 -0700903
Julia Ferguson65428c32017-08-10 18:15:24 +0000904 if (mcastIp.isIp4()) {
905 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
906 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
907 MacAddress.IPV4_MULTICAST_MASK))
908 .addCondition(Criteria.matchVlanId(egressVlan()))
909 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
910 } else {
911 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
912 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
913 MacAddress.IPV6_MULTICAST_MASK))
914 .addCondition(Criteria.matchVlanId(egressVlan()))
915 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
916 }
Charles Chan1588e7b2016-06-28 16:50:13 -0700917 TrafficTreatment tt = DefaultTrafficTreatment.builder()
918 .pushVlan().setVlanId(assignedVlan).build();
919 filtBuilder.withMeta(tt);
920
Charles Chand55e84d2016-03-30 17:54:24 -0700921 return filtBuilder.permit().fromApp(srManager.appId);
922 }
923
924 /**
925 * Gets output ports information from treatments.
926 *
927 * @param treatments collection of traffic treatments
928 * @return set of output port numbers
929 */
930 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
931 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
932 treatments.forEach(treatment -> {
933 treatment.allInstructions().stream()
934 .filter(instr -> instr instanceof OutputInstruction)
935 .forEach(instr -> {
936 builder.add(((OutputInstruction) instr).port());
937 });
938 });
939 return builder.build();
940 }
941
942 /**
943 * Gets a path from src to dst.
944 * If a path was allocated before, returns the allocated path.
945 * Otherwise, randomly pick one from available paths.
946 *
947 * @param src source device ID
948 * @param dst destination device ID
949 * @param mcastIp multicast group
950 * @return an optional path from src to dst
951 */
952 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
Pier Luigi80cb7482018-02-15 16:33:08 +0100953 // Takes a snapshot of the topology
954 final Topology currentTopology = topologyService.currentTopology();
Charles Chand55e84d2016-03-30 17:54:24 -0700955 List<Path> allPaths = Lists.newArrayList(
Pier Luigi80cb7482018-02-15 16:33:08 +0100956 topologyService.getPaths(currentTopology, src, dst)
957 );
Charles Chan2199c302016-04-23 17:36:10 -0700958 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700959 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700960 return Optional.empty();
961 }
962
Pier Luigi5131dad2018-01-23 16:06:38 +0100963 // Create a map index of suitablity-to-list of paths. For example
964 // a path in the list associated to the index 1 shares only the
965 // first hop and it is less suitable of a path belonging to the index
966 // 2 that shares leaf-spine.
967 Map<Integer, List<Path>> eligiblePaths = Maps.newHashMap();
968 // Some init steps
969 int nhop;
970 McastStoreKey mcastStoreKey;
971 Link hop;
972 PortNumber srcPort;
973 Set<PortNumber> existingPorts;
974 NextObjective nextObj;
975 // Iterate over paths looking for eligible paths
976 for (Path path : allPaths) {
977 // Unlikely, it will happen...
978 if (!src.equals(path.links().get(0).src().deviceId())) {
979 continue;
980 }
981 nhop = 0;
982 // Iterate over the links
983 while (nhop < path.links().size()) {
984 // Get the link and verify if a next related
985 // to the src device exist in the store
986 hop = path.links().get(nhop);
987 mcastStoreKey = new McastStoreKey(mcastIp, hop.src().deviceId());
988 // It does not exist in the store, exit
989 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
990 break;
Charles Chand55e84d2016-03-30 17:54:24 -0700991 }
Pier Luigi5131dad2018-01-23 16:06:38 +0100992 // Get the output ports on the next
993 nextObj = mcastNextObjStore.get(mcastStoreKey).value();
994 existingPorts = getPorts(nextObj.next());
995 // And the src port on the link
996 srcPort = hop.src().port();
997 // the src port is not used as output, exit
998 if (!existingPorts.contains(srcPort)) {
999 break;
1000 }
1001 nhop++;
1002 }
1003 // n_hop defines the index
1004 if (nhop > 0) {
1005 eligiblePaths.compute(nhop, (index, paths) -> {
1006 paths = paths == null ? Lists.newArrayList() : paths;
1007 paths.add(path);
1008 return paths;
1009 });
Charles Chand55e84d2016-03-30 17:54:24 -07001010 }
1011 }
Pier Luigi5131dad2018-01-23 16:06:38 +01001012
1013 // No suitable paths
1014 if (eligiblePaths.isEmpty()) {
1015 log.debug("No eligiblePath(s) found from {} to {}", src, dst);
1016 // Otherwise, randomly pick a path
1017 Collections.shuffle(allPaths);
1018 return allPaths.stream().findFirst();
1019 }
1020
1021 // Let's take the best ones
1022 Integer bestIndex = eligiblePaths.keySet()
1023 .stream()
1024 .sorted(Comparator.reverseOrder())
1025 .findFirst().orElse(null);
1026 List<Path> bestPaths = eligiblePaths.get(bestIndex);
1027 log.debug("{} eligiblePath(s) found from {} to {}",
1028 bestPaths.size(), src, dst);
1029 // randomly pick a path on the highest index
1030 Collections.shuffle(bestPaths);
1031 return bestPaths.stream().findFirst();
Charles Chand55e84d2016-03-30 17:54:24 -07001032 }
1033
1034 /**
Charles Chan2199c302016-04-23 17:36:10 -07001035 * Gets device(s) of given role in given multicast group.
1036 *
1037 * @param mcastIp multicast IP
1038 * @param role multicast role
1039 * @return set of device ID or empty set if not found
1040 */
1041 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
1042 return mcastRoleStore.entrySet().stream()
1043 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
1044 entry.getValue().value() == role)
1045 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
1046 .collect(Collectors.toSet());
1047 }
1048
1049 /**
Charles Chan8d449862016-05-16 18:44:13 -07001050 * Gets source connect point of given multicast group.
1051 *
1052 * @param mcastIp multicast IP
1053 * @return source connect point or null if not found
1054 */
1055 private ConnectPoint getSource(IpAddress mcastIp) {
1056 return srManager.multicastRouteService.getRoutes().stream()
1057 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
1058 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
1059 .findAny().orElse(null);
1060 }
1061
1062 /**
Charles Chan2199c302016-04-23 17:36:10 -07001063 * Gets groups which is affected by the link down event.
1064 *
1065 * @param link link going down
1066 * @return a set of multicast IpAddress
1067 */
1068 private Set<IpAddress> getAffectedGroups(Link link) {
1069 DeviceId deviceId = link.src().deviceId();
1070 PortNumber port = link.src().port();
1071 return mcastNextObjStore.entrySet().stream()
1072 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
1073 getPorts(entry.getValue().value().next()).contains(port))
1074 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
1075 .collect(Collectors.toSet());
1076 }
1077
1078 /**
Pier Luigi24592652018-01-16 10:47:50 +01001079 * Gets groups which are affected by the device down event.
1080 *
1081 * @param deviceId device going down
1082 * @return a set of multicast IpAddress
1083 */
1084 private Set<IpAddress> getAffectedGroups(DeviceId deviceId) {
1085 return mcastNextObjStore.entrySet().stream()
1086 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
1087 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
1088 .collect(Collectors.toSet());
1089 }
1090
1091 /**
Charles Chand55e84d2016-03-30 17:54:24 -07001092 * Gets egress VLAN from McastConfig.
1093 *
1094 * @return egress VLAN or VlanId.NONE if not configured
1095 */
1096 private VlanId egressVlan() {
1097 McastConfig mcastConfig =
1098 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
1099 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
1100 }
1101
1102 /**
1103 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chan8d449862016-05-16 18:44:13 -07001104 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chand55e84d2016-03-30 17:54:24 -07001105 *
Charles Chan8d449862016-05-16 18:44:13 -07001106 * @param cp connect point; Can be null if not specified
1107 * @return assigned VLAN ID
Charles Chand55e84d2016-03-30 17:54:24 -07001108 */
Charles Chan8d449862016-05-16 18:44:13 -07001109 private VlanId assignedVlan(ConnectPoint cp) {
1110 // Use the egressVlan if it is tagged
1111 if (!egressVlan().equals(VlanId.NONE)) {
1112 return egressVlan();
1113 }
1114 // Reuse unicast VLAN if the port has subnet configured
1115 if (cp != null) {
Charles Chanb4879a52017-10-20 19:09:16 -07001116 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan59cc16d2017-02-02 16:20:42 -08001117 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chan8d449862016-05-16 18:44:13 -07001118 }
Charles Chan59cc16d2017-02-02 16:20:42 -08001119 // Use DEFAULT_VLAN if none of the above matches
1120 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -07001121 }
Charles Chan2199c302016-04-23 17:36:10 -07001122
1123 /**
1124 * Gets the spine-facing port on ingress device of given multicast group.
1125 *
1126 * @param mcastIp multicast IP
1127 * @return spine-facing port on ingress device
1128 */
1129 private PortNumber ingressTransitPort(IpAddress mcastIp) {
1130 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
1131 .stream().findAny().orElse(null);
1132 if (ingressDevice != null) {
1133 NextObjective nextObj = mcastNextObjStore
1134 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
1135 Set<PortNumber> ports = getPorts(nextObj.next());
1136
1137 for (PortNumber port : ports) {
1138 // Spine-facing port should have no subnet and no xconnect
1139 if (srManager.deviceConfiguration != null &&
Pier Ventre10bd8d12016-11-26 21:05:22 -08001140 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chanfc5c7802016-05-17 13:13:55 -07001141 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan2199c302016-04-23 17:36:10 -07001142 return port;
1143 }
1144 }
1145 }
1146 return null;
1147 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001148
1149 /**
Pier Luigi24592652018-01-16 10:47:50 +01001150 * Verify if the given device has sinks
1151 * for the multicast group.
1152 *
1153 * @param deviceId device Id
1154 * @param mcastIp multicast IP
1155 * @return true if the device has sink for the group.
1156 * False otherwise.
1157 */
1158 private boolean hasSinks(DeviceId deviceId, IpAddress mcastIp) {
1159 if (deviceId != null) {
1160 // Get the nextobjective
1161 Versioned<NextObjective> versionedNextObj = mcastNextObjStore.get(
1162 new McastStoreKey(mcastIp, deviceId)
1163 );
1164 // If it exists
1165 if (versionedNextObj != null) {
1166 NextObjective nextObj = versionedNextObj.value();
1167 // Retrieves all the output ports
1168 Set<PortNumber> ports = getPorts(nextObj.next());
1169 // Tries to find at least one port that is not spine-facing
1170 for (PortNumber port : ports) {
1171 // Spine-facing port should have no subnet and no xconnect
1172 if (srManager.deviceConfiguration != null &&
1173 (!srManager.deviceConfiguration.getPortSubnets(deviceId, port).isEmpty() ||
1174 srManager.xConnectHandler.hasXConnect(new ConnectPoint(deviceId, port)))) {
1175 return true;
1176 }
1177 }
1178 }
1179 }
1180 return false;
1181 }
1182
1183 /**
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001184 * Removes filtering objective for given device and port.
1185 *
1186 * @param deviceId device ID
1187 * @param port ingress port number
1188 * @param assignedVlan assigned VLAN ID
1189 * @param mcastIp multicast IP address
1190 */
1191 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
1192 // Do nothing if the port is configured as suppressed
1193 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
1194 SegmentRoutingAppConfig appConfig = srManager.cfgService
1195 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
1196 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
1197 log.info("Ignore suppressed port {}", connectPoint);
1198 return;
1199 }
1200
1201 FilteringObjective.Builder filtObjBuilder =
1202 filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
1203 ObjectiveContext context = new DefaultObjectiveContext(
1204 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
1205 deviceId, port.toLong(), assignedVlan),
1206 (objective, error) ->
1207 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
1208 deviceId, port.toLong(), assignedVlan, error));
1209 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
1210 }
1211
1212 /**
Pier Luigi3dfd8352018-01-25 16:16:02 +01001213 * Updates filtering objective for given device and port.
1214 * It is called in general when the mcast config has been
1215 * changed.
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001216 *
1217 * @param deviceId device ID
1218 * @param portNum ingress port number
1219 * @param vlanId assigned VLAN ID
1220 * @param install true to add, false to remove
1221 */
1222 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
1223 VlanId vlanId, boolean install) {
Pier Luigi3dfd8352018-01-25 16:16:02 +01001224 lastMcastChange = Instant.now();
1225 mcastLock();
1226 try {
1227 // Iterates over the route and updates properly the filtering objective
1228 // on the source device.
1229 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
1230 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
1231 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
1232 if (install) {
1233 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1234 } else {
1235 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
1236 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001237 }
Pier Luigi3dfd8352018-01-25 16:16:02 +01001238 });
1239 } finally {
1240 mcastUnlock();
1241 }
1242 }
1243
Pier Luigi1c34a442018-02-02 16:19:11 +01001244 private boolean isLeader(ConnectPoint source) {
1245 // Continue only when we have the mastership on the operation
1246 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
1247 // When the source is available we just check the mastership
1248 if (srManager.deviceService.isAvailable(source.deviceId())) {
1249 return false;
1250 }
1251 // Fallback with Leadership service
1252 // source id is used a topic
1253 NodeId leader = srManager.leadershipService.runForLeadership(
1254 source.deviceId().toString()).leaderNodeId();
1255 // Verify if this node is the leader
1256 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1257 return false;
1258 }
1259 }
1260 // Done
1261 return true;
1262 }
1263
Pier Luigi3dfd8352018-01-25 16:16:02 +01001264 /**
1265 * Performs bucket verification operation for all mcast groups in the devices.
1266 * Firstly, it verifies that mcast is stable before trying verification operation.
1267 * Verification consists in creating new nexts with VERIFY operation. Actually,
1268 * the operation is totally delegated to the driver.
1269 */
1270 private final class McastBucketCorrector implements Runnable {
1271
1272 @Override
1273 public void run() {
1274 // Verify if the Mcast has been stable for MCAST_STABLITY_THRESHOLD
1275 if (!isMcastStable()) {
1276 return;
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001277 }
Pier Luigi3dfd8352018-01-25 16:16:02 +01001278 // Acquires lock
1279 mcastLock();
1280 try {
1281 // Iterates over the routes and verify the related next objectives
1282 srManager.multicastRouteService.getRoutes()
1283 .stream()
1284 .map(McastRoute::group)
1285 .forEach(mcastIp -> {
1286 log.trace("Running mcast buckets corrector for mcast group: {}",
1287 mcastIp);
1288
1289 // For each group we get current information in the store
1290 // and issue a check of the next objectives in place
1291 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
1292 .stream().findAny().orElse(null);
1293 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
1294 .stream().findAny().orElse(null);
1295 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
1296 ConnectPoint source = getSource(mcastIp);
1297
1298 // Do not proceed if ingress device or source of this group are missing
1299 if (ingressDevice == null || source == null) {
1300 log.warn("Unable to run buckets corrector. " +
1301 "Missing ingress {} or source {} for group {}",
1302 ingressDevice, source, mcastIp);
1303 return;
1304 }
1305
1306 // Continue only when this instance is the master of source device
1307 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
1308 log.trace("Unable to run buckets corrector. " +
1309 "Skip {} due to lack of mastership " +
1310 "of the source device {}",
1311 mcastIp, source.deviceId());
1312 return;
1313 }
1314
1315 // Create the set of the devices to be processed
1316 ImmutableSet.Builder<DeviceId> devicesBuilder = ImmutableSet.builder();
1317 devicesBuilder.add(ingressDevice);
1318 if (transitDevice != null) {
1319 devicesBuilder.add(transitDevice);
1320 }
1321 if (!egressDevices.isEmpty()) {
1322 devicesBuilder.addAll(egressDevices);
1323 }
1324 Set<DeviceId> devicesToProcess = devicesBuilder.build();
1325
1326 // Iterate over the devices
1327 devicesToProcess.forEach(deviceId -> {
1328 McastStoreKey currentKey = new McastStoreKey(mcastIp, deviceId);
1329 // If next exists in our store verify related next objective
1330 if (mcastNextObjStore.containsKey(currentKey)) {
1331 NextObjective currentNext = mcastNextObjStore.get(currentKey).value();
1332 // Get current ports
1333 Set<PortNumber> currentPorts = getPorts(currentNext.next());
1334 // Rebuild the next objective
1335 currentNext = nextObjBuilder(
1336 mcastIp,
1337 assignedVlan(deviceId.equals(source.deviceId()) ? source : null),
1338 currentPorts,
1339 currentNext.id()
1340 ).verify();
1341 // Send to the flowobjective service
1342 srManager.flowObjectiveService.next(deviceId, currentNext);
1343 } else {
Pier Luigi80cb7482018-02-15 16:33:08 +01001344 log.warn("Unable to run buckets corrector. " +
Pier Luigi3dfd8352018-01-25 16:16:02 +01001345 "Missing next for {} and group {}",
1346 deviceId, mcastIp);
1347 }
1348 });
1349
1350 });
1351 } finally {
1352 // Finally, it releases the lock
1353 mcastUnlock();
1354 }
1355
1356 }
Jonghwan Hyun42fe1052017-08-25 17:48:36 -07001357 }
Pier Luigi0b14d6c2018-01-15 18:06:43 +01001358
1359 public Map<McastStoreKey, Integer> getMcastNextIds(IpAddress mcastIp) {
1360 // If mcast ip is present
1361 if (mcastIp != null) {
1362 return mcastNextObjStore.entrySet().stream()
1363 .filter(mcastEntry -> mcastIp.equals(mcastEntry.getKey().mcastIp()))
1364 .collect(Collectors.toMap(Map.Entry::getKey,
1365 entry -> entry.getValue().value().id()));
1366 }
1367 // Otherwise take all the groups
1368 return mcastNextObjStore.entrySet().stream()
1369 .collect(Collectors.toMap(Map.Entry::getKey,
1370 entry -> entry.getValue().value().id()));
1371 }
1372
1373 public Map<McastStoreKey, McastHandler.McastRole> getMcastRoles(IpAddress mcastIp) {
1374 // If mcast ip is present
1375 if (mcastIp != null) {
1376 return mcastRoleStore.entrySet().stream()
1377 .filter(mcastEntry -> mcastIp.equals(mcastEntry.getKey().mcastIp()))
1378 .collect(Collectors.toMap(Map.Entry::getKey,
1379 entry -> entry.getValue().value()));
1380 }
1381 // Otherwise take all the groups
1382 return mcastRoleStore.entrySet().stream()
1383 .collect(Collectors.toMap(Map.Entry::getKey,
1384 entry -> entry.getValue().value()));
1385 }
1386
1387 public Map<ConnectPoint, List<ConnectPoint>> getMcastPaths(IpAddress mcastIp) {
1388 Map<ConnectPoint, List<ConnectPoint>> mcastPaths = Maps.newHashMap();
1389 // Get the source
1390 ConnectPoint source = getSource(mcastIp);
1391 // Source cannot be null, we don't know the starting point
1392 if (source != null) {
1393 // Init steps
1394 Set<DeviceId> visited = Sets.newHashSet();
1395 List<ConnectPoint> currentPath = Lists.newArrayList(
1396 source
1397 );
1398 // Build recursively the mcast paths
1399 buildMcastPaths(source.deviceId(), visited, mcastPaths, currentPath, mcastIp);
1400 }
1401 return mcastPaths;
1402 }
1403
1404 private void buildMcastPaths(DeviceId toVisit, Set<DeviceId> visited,
1405 Map<ConnectPoint, List<ConnectPoint>> mcastPaths,
1406 List<ConnectPoint> currentPath, IpAddress mcastIp) {
1407 // If we have visited the node to visit
1408 // there is a loop
1409 if (visited.contains(toVisit)) {
1410 return;
1411 }
1412 // Visit next-hop
1413 visited.add(toVisit);
1414 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, toVisit);
1415 // Looking for next-hops
1416 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
1417 // Build egress connectpoints
1418 NextObjective nextObjective = mcastNextObjStore.get(mcastStoreKey).value();
1419 // Get Ports
1420 Set<PortNumber> outputPorts = getPorts(nextObjective.next());
1421 // Build relative cps
1422 ImmutableSet.Builder<ConnectPoint> cpBuilder = ImmutableSet.builder();
1423 outputPorts.forEach(portNumber -> cpBuilder.add(new ConnectPoint(toVisit, portNumber)));
1424 Set<ConnectPoint> egressPoints = cpBuilder.build();
1425 // Define other variables for the next steps
1426 Set<Link> egressLinks;
1427 List<ConnectPoint> newCurrentPath;
1428 Set<DeviceId> newVisited;
1429 DeviceId newToVisit;
1430 for (ConnectPoint egressPoint : egressPoints) {
1431 egressLinks = srManager.linkService.getEgressLinks(egressPoint);
1432 // If it does not have egress links, stop
1433 if (egressLinks.isEmpty()) {
1434 // Add the connect points to the path
1435 newCurrentPath = Lists.newArrayList(currentPath);
1436 newCurrentPath.add(0, egressPoint);
1437 // Save in the map
1438 mcastPaths.put(egressPoint, newCurrentPath);
1439 } else {
1440 newVisited = Sets.newHashSet(visited);
1441 // Iterate over the egress links for the next hops
1442 for (Link egressLink : egressLinks) {
1443 // Update to visit
1444 newToVisit = egressLink.dst().deviceId();
1445 // Add the connect points to the path
1446 newCurrentPath = Lists.newArrayList(currentPath);
1447 newCurrentPath.add(0, egressPoint);
1448 newCurrentPath.add(0, egressLink.dst());
1449 // Go to the next hop
1450 buildMcastPaths(newToVisit, newVisited, mcastPaths, newCurrentPath, mcastIp);
1451 }
1452 }
1453 }
1454 }
1455 }
1456
Charles Chand55e84d2016-03-30 17:54:24 -07001457}