blob: 8139e2710c2034319e7ae4de5484edd37885eb98 [file] [log] [blame]
Charles Chanc91c8782016-03-30 17:54:24 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Sets;
22import org.onlab.packet.Ethernet;
23import org.onlab.packet.IpAddress;
24import org.onlab.packet.IpPrefix;
25import org.onlab.packet.MacAddress;
26import org.onlab.packet.VlanId;
27import org.onlab.util.KryoNamespace;
28import org.onosproject.core.ApplicationId;
29import org.onosproject.core.CoreService;
30import org.onosproject.incubator.net.config.basics.McastConfig;
31import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.DeviceId;
33import org.onosproject.net.Link;
34import org.onosproject.net.Path;
35import org.onosproject.net.PortNumber;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.Criteria;
41import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
42import org.onosproject.net.flowobjective.DefaultFilteringObjective;
43import org.onosproject.net.flowobjective.DefaultForwardingObjective;
44import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan72779502016-04-23 17:36:10 -070045import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070046import org.onosproject.net.flowobjective.FilteringObjective;
47import org.onosproject.net.flowobjective.ForwardingObjective;
48import org.onosproject.net.flowobjective.NextObjective;
Charles Chan72779502016-04-23 17:36:10 -070049import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070050import org.onosproject.net.mcast.McastEvent;
51import org.onosproject.net.mcast.McastRouteInfo;
52import org.onosproject.net.topology.TopologyService;
Charles Chan72779502016-04-23 17:36:10 -070053import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chanc91c8782016-03-30 17:54:24 -070054import org.onosproject.store.serializers.KryoNamespaces;
55import org.onosproject.store.service.ConsistentMap;
56import org.onosproject.store.service.Serializer;
57import org.onosproject.store.service.StorageService;
Charles Chan72779502016-04-23 17:36:10 -070058import org.onosproject.store.service.Versioned;
Charles Chanc91c8782016-03-30 17:54:24 -070059import org.slf4j.Logger;
60import org.slf4j.LoggerFactory;
61
62import java.util.Collection;
63import java.util.Collections;
Charles Chan72779502016-04-23 17:36:10 -070064import java.util.Iterator;
Charles Chanc91c8782016-03-30 17:54:24 -070065import java.util.List;
Charles Chan72779502016-04-23 17:36:10 -070066import java.util.Map;
Charles Chanc91c8782016-03-30 17:54:24 -070067import java.util.Optional;
68import java.util.Set;
Charles Chan72779502016-04-23 17:36:10 -070069import java.util.stream.Collectors;
70
71import static com.google.common.base.Preconditions.checkState;
Charles Chanc91c8782016-03-30 17:54:24 -070072
73/**
Charles Chan1eaf4802016-04-18 13:44:03 -070074 * Handles multicast-related events.
Charles Chanc91c8782016-03-30 17:54:24 -070075 */
Charles Chan1eaf4802016-04-18 13:44:03 -070076public class McastHandler {
77 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chanc91c8782016-03-30 17:54:24 -070078 private final SegmentRoutingManager srManager;
79 private final ApplicationId coreAppId;
80 private StorageService storageService;
81 private TopologyService topologyService;
Charles Chan72779502016-04-23 17:36:10 -070082 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
83 private final KryoNamespace.Builder mcastKryo;
84 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
85
86 /**
87 * Role in the multicast tree.
88 */
89 public enum McastRole {
90 /**
91 * The device is the ingress device of this group.
92 */
93 INGRESS,
94 /**
95 * The device is the transit device of this group.
96 */
97 TRANSIT,
98 /**
99 * The device is the egress device of this group.
100 */
101 EGRESS
102 }
Charles Chanc91c8782016-03-30 17:54:24 -0700103
104 /**
105 * Constructs the McastEventHandler.
106 *
107 * @param srManager Segment Routing manager
108 */
Charles Chan1eaf4802016-04-18 13:44:03 -0700109 public McastHandler(SegmentRoutingManager srManager) {
Charles Chanc91c8782016-03-30 17:54:24 -0700110 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chanc91c8782016-03-30 17:54:24 -0700111 this.srManager = srManager;
112 this.storageService = srManager.storageService;
113 this.topologyService = srManager.topologyService;
Charles Chan72779502016-04-23 17:36:10 -0700114 mcastKryo = new KryoNamespace.Builder()
Charles Chanc91c8782016-03-30 17:54:24 -0700115 .register(KryoNamespaces.API)
Charles Chan72779502016-04-23 17:36:10 -0700116 .register(McastStoreKey.class)
117 .register(McastRole.class);
Charles Chanc91c8782016-03-30 17:54:24 -0700118 mcastNextObjStore = storageService
Charles Chan72779502016-04-23 17:36:10 -0700119 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chanc91c8782016-03-30 17:54:24 -0700120 .withName("onos-mcast-nextobj-store")
Charles Chan72779502016-04-23 17:36:10 -0700121 .withSerializer(Serializer.using(mcastKryo.build()))
Charles Chanc91c8782016-03-30 17:54:24 -0700122 .build();
Charles Chan72779502016-04-23 17:36:10 -0700123 mcastRoleStore = storageService
124 .<McastStoreKey, McastRole>consistentMapBuilder()
125 .withName("onos-mcast-role-store")
126 .withSerializer(Serializer.using(mcastKryo.build()))
127 .build();
128 }
129
130 /**
131 * Read initial multicast from mcast store.
132 */
133 public void init() {
134 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
135 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
136 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
137 sinks.forEach(sink -> {
138 processSinkAddedInternal(source, sink, mcastRoute.group());
139 });
140 });
Charles Chanc91c8782016-03-30 17:54:24 -0700141 }
142
143 /**
144 * Processes the SOURCE_ADDED event.
145 *
146 * @param event McastEvent with SOURCE_ADDED type
147 */
148 protected void processSourceAdded(McastEvent event) {
149 log.info("processSourceAdded {}", event);
150 McastRouteInfo mcastRouteInfo = event.subject();
151 if (!mcastRouteInfo.isComplete()) {
152 log.info("Incompleted McastRouteInfo. Abort.");
153 return;
154 }
155 ConnectPoint source = mcastRouteInfo.source().orElse(null);
156 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
157 IpAddress mcastIp = mcastRouteInfo.route().group();
158
159 sinks.forEach(sink -> {
160 processSinkAddedInternal(source, sink, mcastIp);
161 });
162 }
163
164 /**
165 * Processes the SINK_ADDED event.
166 *
167 * @param event McastEvent with SINK_ADDED type
168 */
169 protected void processSinkAdded(McastEvent event) {
170 log.info("processSinkAdded {}", event);
171 McastRouteInfo mcastRouteInfo = event.subject();
172 if (!mcastRouteInfo.isComplete()) {
173 log.info("Incompleted McastRouteInfo. Abort.");
174 return;
175 }
176 ConnectPoint source = mcastRouteInfo.source().orElse(null);
177 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
178 IpAddress mcastIp = mcastRouteInfo.route().group();
179
180 processSinkAddedInternal(source, sink, mcastIp);
181 }
182
183 /**
184 * Processes the SINK_REMOVED event.
185 *
186 * @param event McastEvent with SINK_REMOVED type
187 */
188 protected void processSinkRemoved(McastEvent event) {
189 log.info("processSinkRemoved {}", event);
190 McastRouteInfo mcastRouteInfo = event.subject();
191 if (!mcastRouteInfo.isComplete()) {
192 log.info("Incompleted McastRouteInfo. Abort.");
193 return;
194 }
195 ConnectPoint source = mcastRouteInfo.source().orElse(null);
196 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
197 IpAddress mcastIp = mcastRouteInfo.route().group();
198 VlanId assignedVlan = assignedVlan();
199
200 // When source and sink are on the same device
201 if (source.deviceId().equals(sink.deviceId())) {
202 // Source and sink are on even the same port. There must be something wrong.
203 if (source.port().equals(sink.port())) {
204 log.warn("Sink is on the same port of source. Abort");
205 return;
206 }
207 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
208 return;
209 }
210
211 // Process the egress device
212 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan72779502016-04-23 17:36:10 -0700213 if (isLast) {
214 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
215 }
Charles Chanc91c8782016-03-30 17:54:24 -0700216
217 // If this is the last sink on the device, also update upstream
218 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
219 if (mcastPath.isPresent()) {
220 List<Link> links = Lists.newArrayList(mcastPath.get().links());
221 Collections.reverse(links);
222 for (Link link : links) {
223 if (isLast) {
224 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
225 mcastIp, assignedVlan);
Charles Chan72779502016-04-23 17:36:10 -0700226 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chanc91c8782016-03-30 17:54:24 -0700227 }
228 }
229 }
230 }
231
232 /**
233 * Establishes a path from source to sink for given multicast group.
234 *
235 * @param source connect point of the multicast source
236 * @param sink connection point of the multicast sink
237 * @param mcastIp multicast group IP address
238 */
239 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
240 IpAddress mcastIp) {
241 VlanId assignedVlan = assignedVlan();
242
Charles Chan72779502016-04-23 17:36:10 -0700243 // Process the ingress device
244 addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
245
Charles Chanc91c8782016-03-30 17:54:24 -0700246 // When source and sink are on the same device
247 if (source.deviceId().equals(sink.deviceId())) {
248 // Source and sink are on even the same port. There must be something wrong.
249 if (source.port().equals(sink.port())) {
250 log.warn("Sink is on the same port of source. Abort");
251 return;
252 }
253 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan72779502016-04-23 17:36:10 -0700254 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chanc91c8782016-03-30 17:54:24 -0700255 return;
256 }
257
Charles Chanc91c8782016-03-30 17:54:24 -0700258 // Find a path. If present, create/update groups and flows for each hop
259 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
260 if (mcastPath.isPresent()) {
Charles Chan72779502016-04-23 17:36:10 -0700261 List<Link> links = mcastPath.get().links();
262 checkState(links.size() == 2,
263 "Path in leaf-spine topology should always be two hops: ", links);
264
265 links.forEach(link -> {
Charles Chanc91c8782016-03-30 17:54:24 -0700266 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
267 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
268 });
Charles Chan72779502016-04-23 17:36:10 -0700269
Charles Chanc91c8782016-03-30 17:54:24 -0700270 // Process the egress device
271 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan72779502016-04-23 17:36:10 -0700272
273 // Setup mcast roles
274 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
275 McastRole.INGRESS);
276 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
277 McastRole.TRANSIT);
278 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
279 McastRole.EGRESS);
280 } else {
281 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
282 source.deviceId(), sink.deviceId());
Charles Chanc91c8782016-03-30 17:54:24 -0700283 }
284 }
285
286 /**
Charles Chan72779502016-04-23 17:36:10 -0700287 * Processes the LINK_DOWN event.
288 *
289 * @param affectedLink Link that is going down
290 */
291 protected void processLinkDown(Link affectedLink) {
292 VlanId assignedVlan = assignedVlan();
293
294 getAffectedGroups(affectedLink).forEach(mcastIp -> {
295 // Find out the ingress, transit and egress device of affected group
296 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
297 .stream().findAny().orElse(null);
298 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
299 .stream().findAny().orElse(null);
300 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
301 if (ingressDevice == null || transitDevice == null || egressDevices == null) {
302 log.warn("Missing ingress {}, transit {}, or egress {} devices",
303 ingressDevice, transitDevice, egressDevices);
304 return;
305 }
306
307 // Remove entire transit
308 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan);
309
310 // Remove transit-facing port on ingress device
311 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
312 if (ingressTransitPort != null) {
313 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan);
314 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
315 }
316
317 // Construct a new path for each egress device
318 egressDevices.forEach(egressDevice -> {
319 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
320 if (mcastPath.isPresent()) {
321 List<Link> links = mcastPath.get().links();
322 links.forEach(link -> {
323 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
324 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
325 });
326 // Setup new transit mcast role
327 mcastRoleStore.put(new McastStoreKey(mcastIp,
328 links.get(0).dst().deviceId()), McastRole.TRANSIT);
329 } else {
330 log.warn("Fail to recover egress device {} from link failure {}",
331 egressDevice, affectedLink);
332 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan);
333 }
334 });
335 });
336 }
337
338 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700339 * Adds filtering objective for given device and port.
340 *
341 * @param deviceId device ID
342 * @param port ingress port number
343 * @param assignedVlan assigned VLAN ID
344 */
345 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
346 // Do nothing if the port is configured as suppressed
347 ConnectPoint connectPt = new ConnectPoint(deviceId, port);
Charles Chan72779502016-04-23 17:36:10 -0700348 if (srManager.deviceConfiguration == null ||
349 srManager.deviceConfiguration.suppressSubnet().contains(connectPt) ||
Charles Chanc91c8782016-03-30 17:54:24 -0700350 srManager.deviceConfiguration.suppressHost().contains(connectPt)) {
351 log.info("Ignore suppressed port {}", connectPt);
352 return;
353 }
354
355 FilteringObjective.Builder filtObjBuilder =
356 filterObjBuilder(deviceId, port, assignedVlan);
Charles Chan72779502016-04-23 17:36:10 -0700357 ObjectiveContext context = new DefaultObjectiveContext(
358 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
359 deviceId, port.toLong(), assignedVlan),
360 (objective, error) ->
361 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
362 deviceId, port.toLong(), assignedVlan, error));
363 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chanc91c8782016-03-30 17:54:24 -0700364 }
365
366 /**
367 * Adds a port to given multicast group on given device. This involves the
368 * update of L3 multicast group and multicast routing table entry.
369 *
370 * @param deviceId device ID
371 * @param port port to be added
372 * @param mcastIp multicast group
373 * @param assignedVlan assigned VLAN ID
374 */
375 private void addPortToDevice(DeviceId deviceId, PortNumber port,
376 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700377 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700378 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan72779502016-04-23 17:36:10 -0700379 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700380 // First time someone request this mcast group via this device
381 portBuilder.add(port);
382 } else {
383 // This device already serves some subscribers of this mcast group
Charles Chan72779502016-04-23 17:36:10 -0700384 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700385 // Stop if the port is already in the nextobj
386 Set<PortNumber> existingPorts = getPorts(nextObj.next());
387 if (existingPorts.contains(port)) {
388 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
389 return;
390 }
391 portBuilder.addAll(existingPorts).add(port).build();
392 }
393 // Create, store and apply the new nextObj and fwdObj
Charles Chan72779502016-04-23 17:36:10 -0700394 ObjectiveContext context = new DefaultObjectiveContext(
395 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
396 mcastIp, deviceId, port.toLong(), assignedVlan),
397 (objective, error) ->
398 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
399 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700400 NextObjective newNextObj =
401 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
402 ForwardingObjective fwdObj =
Charles Chan72779502016-04-23 17:36:10 -0700403 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
404 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700405 srManager.flowObjectiveService.next(deviceId, newNextObj);
406 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700407 }
408
409 /**
410 * Removes a port from given multicast group on given device.
411 * This involves the update of L3 multicast group and multicast routing
412 * table entry.
413 *
414 * @param deviceId device ID
415 * @param port port to be added
416 * @param mcastIp multicast group
417 * @param assignedVlan assigned VLAN ID
418 * @return true if this is the last sink on this device
419 */
420 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
421 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700422 McastStoreKey mcastStoreKey =
423 new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700424 // This device is not serving this multicast group
Charles Chan72779502016-04-23 17:36:10 -0700425 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700426 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
427 return false;
428 }
Charles Chan72779502016-04-23 17:36:10 -0700429 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700430
431 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan72779502016-04-23 17:36:10 -0700432 // This port does not serve this multicast group
Charles Chanc91c8782016-03-30 17:54:24 -0700433 if (!existingPorts.contains(port)) {
434 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
435 return false;
436 }
437 // Copy and modify the ImmutableSet
438 existingPorts = Sets.newHashSet(existingPorts);
439 existingPorts.remove(port);
440
441 NextObjective newNextObj;
442 ForwardingObjective fwdObj;
443 if (existingPorts.isEmpty()) {
444 // If this is the last sink, remove flows and groups
445 // NOTE: Rely on GroupStore garbage collection rather than explicitly
446 // remove L3MG since there might be other flows/groups refer to
447 // the same L2IG
Charles Chan72779502016-04-23 17:36:10 -0700448 ObjectiveContext context = new DefaultObjectiveContext(
449 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
450 mcastIp, deviceId, port.toLong(), assignedVlan),
451 (objective, error) ->
452 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
453 mcastIp, deviceId, port.toLong(), assignedVlan, error));
454 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
455 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700456 srManager.flowObjectiveService.forward(deviceId, fwdObj);
457 } else {
458 // If this is not the last sink, update flows and groups
Charles Chan72779502016-04-23 17:36:10 -0700459 ObjectiveContext context = new DefaultObjectiveContext(
460 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
461 mcastIp, deviceId, port.toLong(), assignedVlan),
462 (objective, error) ->
463 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
464 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700465 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
466 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
Charles Chan72779502016-04-23 17:36:10 -0700467 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700468 srManager.flowObjectiveService.next(deviceId, newNextObj);
469 srManager.flowObjectiveService.forward(deviceId, fwdObj);
470 }
Charles Chanc91c8782016-03-30 17:54:24 -0700471 return existingPorts.isEmpty();
472 }
473
Charles Chan72779502016-04-23 17:36:10 -0700474
475 /**
476 * Removes entire group on given device.
477 *
478 * @param deviceId device ID
479 * @param mcastIp multicast group to be removed
480 * @param assignedVlan assigned VLAN ID
481 */
482 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
483 VlanId assignedVlan) {
484 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
485 // This device is not serving this multicast group
486 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
487 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
488 return;
489 }
490 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
491 // NOTE: Rely on GroupStore garbage collection rather than explicitly
492 // remove L3MG since there might be other flows/groups refer to
493 // the same L2IG
494 ObjectiveContext context = new DefaultObjectiveContext(
495 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
496 mcastIp, deviceId, assignedVlan),
497 (objective, error) ->
498 log.warn("Failed to remove {} on {}, vlan {}: {}",
499 mcastIp, deviceId, assignedVlan, error));
500 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
501 srManager.flowObjectiveService.forward(deviceId, fwdObj);
502 mcastNextObjStore.remove(mcastStoreKey);
503 mcastRoleStore.remove(mcastStoreKey);
504 }
505
506 /**
507 * Remove all groups on given device.
508 *
509 * @param deviceId device ID
510 */
511 public void removeDevice(DeviceId deviceId) {
512 Iterator<Map.Entry<McastStoreKey, Versioned<NextObjective>>> itNextObj =
513 mcastNextObjStore.entrySet().iterator();
514 while (itNextObj.hasNext()) {
515 Map.Entry<McastStoreKey, Versioned<NextObjective>> entry = itNextObj.next();
516 if (entry.getKey().deviceId().equals(deviceId)) {
517 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(), assignedVlan());
518 itNextObj.remove();
519 }
520 }
521
522 Iterator<Map.Entry<McastStoreKey, Versioned<McastRole>>> itRole =
523 mcastRoleStore.entrySet().iterator();
524 while (itRole.hasNext()) {
525 Map.Entry<McastStoreKey, Versioned<McastRole>> entry = itRole.next();
526 if (entry.getKey().deviceId().equals(deviceId)) {
527 itRole.remove();
528 }
529 }
530
531 }
532
Charles Chanc91c8782016-03-30 17:54:24 -0700533 /**
534 * Creates a next objective builder for multicast.
535 *
536 * @param mcastIp multicast group
537 * @param assignedVlan assigned VLAN ID
538 * @param outPorts set of output port numbers
539 * @return next objective builder
540 */
541 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
542 VlanId assignedVlan, Set<PortNumber> outPorts) {
543 int nextId = srManager.flowObjectiveService.allocateNextId();
544
545 TrafficSelector metadata =
546 DefaultTrafficSelector.builder()
547 .matchVlanId(assignedVlan)
548 .matchIPDst(mcastIp.toIpPrefix())
549 .build();
550
551 NextObjective.Builder nextObjBuilder = DefaultNextObjective
552 .builder().withId(nextId)
553 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
554 .withMeta(metadata);
555
556 outPorts.forEach(port -> {
557 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
558 if (egressVlan().equals(VlanId.NONE)) {
559 tBuilder.popVlan();
560 }
561 tBuilder.setOutput(port);
562 nextObjBuilder.addTreatment(tBuilder.build());
563 });
564
565 return nextObjBuilder;
566 }
567
568 /**
569 * Creates a forwarding objective builder for multicast.
570 *
571 * @param mcastIp multicast group
572 * @param assignedVlan assigned VLAN ID
573 * @param nextId next ID of the L3 multicast group
574 * @return forwarding objective builder
575 */
576 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
577 VlanId assignedVlan, int nextId) {
578 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
579 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
580 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
581 sbuilder.matchIPDst(mcastPrefix);
582 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
583 metabuilder.matchVlanId(assignedVlan);
584
585 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
586 fwdBuilder.withSelector(sbuilder.build())
587 .withMeta(metabuilder.build())
588 .nextStep(nextId)
589 .withFlag(ForwardingObjective.Flag.SPECIFIC)
590 .fromApp(srManager.appId)
591 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
592 return fwdBuilder;
593 }
594
595 /**
596 * Creates a filtering objective builder for multicast.
597 *
598 * @param deviceId Device ID
599 * @param ingressPort ingress port of the multicast stream
600 * @param assignedVlan assigned VLAN ID
601 * @return filtering objective builder
602 */
603 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
604 VlanId assignedVlan) {
605 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
606 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
607 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
608 MacAddress.IPV4_MULTICAST_MASK))
609 .addCondition(Criteria.matchVlanId(egressVlan()))
610 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
611 // vlan assignment is valid only if this instance is master
612 if (srManager.mastershipService.isLocalMaster(deviceId)) {
613 TrafficTreatment tt = DefaultTrafficTreatment.builder()
614 .pushVlan().setVlanId(assignedVlan).build();
615 filtBuilder.withMeta(tt);
616 }
617 return filtBuilder.permit().fromApp(srManager.appId);
618 }
619
620 /**
621 * Gets output ports information from treatments.
622 *
623 * @param treatments collection of traffic treatments
624 * @return set of output port numbers
625 */
626 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
627 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
628 treatments.forEach(treatment -> {
629 treatment.allInstructions().stream()
630 .filter(instr -> instr instanceof OutputInstruction)
631 .forEach(instr -> {
632 builder.add(((OutputInstruction) instr).port());
633 });
634 });
635 return builder.build();
636 }
637
638 /**
639 * Gets a path from src to dst.
640 * If a path was allocated before, returns the allocated path.
641 * Otherwise, randomly pick one from available paths.
642 *
643 * @param src source device ID
644 * @param dst destination device ID
645 * @param mcastIp multicast group
646 * @return an optional path from src to dst
647 */
648 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
649 List<Path> allPaths = Lists.newArrayList(
650 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700651 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700652 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700653 return Optional.empty();
654 }
655
656 // If one of the available path is used before, use the same path
Charles Chan72779502016-04-23 17:36:10 -0700657 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
658 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
659 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700660 Set<PortNumber> existingPorts = getPorts(nextObj.next());
661 for (Path path : allPaths) {
662 PortNumber srcPort = path.links().get(0).src().port();
663 if (existingPorts.contains(srcPort)) {
664 return Optional.of(path);
665 }
666 }
667 }
668 // Otherwise, randomly pick a path
669 Collections.shuffle(allPaths);
670 return allPaths.stream().findFirst();
671 }
672
673 /**
Charles Chan72779502016-04-23 17:36:10 -0700674 * Gets device(s) of given role in given multicast group.
675 *
676 * @param mcastIp multicast IP
677 * @param role multicast role
678 * @return set of device ID or empty set if not found
679 */
680 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
681 return mcastRoleStore.entrySet().stream()
682 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
683 entry.getValue().value() == role)
684 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
685 .collect(Collectors.toSet());
686 }
687
688 /**
689 * Gets groups which is affected by the link down event.
690 *
691 * @param link link going down
692 * @return a set of multicast IpAddress
693 */
694 private Set<IpAddress> getAffectedGroups(Link link) {
695 DeviceId deviceId = link.src().deviceId();
696 PortNumber port = link.src().port();
697 return mcastNextObjStore.entrySet().stream()
698 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
699 getPorts(entry.getValue().value().next()).contains(port))
700 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
701 .collect(Collectors.toSet());
702 }
703
704 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700705 * Gets egress VLAN from McastConfig.
706 *
707 * @return egress VLAN or VlanId.NONE if not configured
708 */
709 private VlanId egressVlan() {
710 McastConfig mcastConfig =
711 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
712 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
713 }
714
715 /**
716 * Gets assigned VLAN according to the value of egress VLAN.
717 *
718 * @return assigned VLAN
719 */
720 private VlanId assignedVlan() {
721 return (egressVlan().equals(VlanId.NONE)) ?
722 VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) :
723 egressVlan();
724 }
Charles Chan72779502016-04-23 17:36:10 -0700725
726 /**
727 * Gets the spine-facing port on ingress device of given multicast group.
728 *
729 * @param mcastIp multicast IP
730 * @return spine-facing port on ingress device
731 */
732 private PortNumber ingressTransitPort(IpAddress mcastIp) {
733 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
734 .stream().findAny().orElse(null);
735 if (ingressDevice != null) {
736 NextObjective nextObj = mcastNextObjStore
737 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
738 Set<PortNumber> ports = getPorts(nextObj.next());
739
740 for (PortNumber port : ports) {
741 // Spine-facing port should have no subnet and no xconnect
742 if (srManager.deviceConfiguration != null &&
743 srManager.deviceConfiguration.getPortSubnet(ingressDevice, port) == null &&
744 srManager.deviceConfiguration.getXConnects().values().stream()
745 .allMatch(connectPoints ->
746 connectPoints.stream().noneMatch(connectPoint ->
747 connectPoint.port().equals(port))
748 )) {
749 return port;
750 }
751 }
752 }
753 return null;
754 }
Charles Chanc91c8782016-03-30 17:54:24 -0700755}