blob: 96a2337089f4cc3457ffeb59d3a0b4ef348084a8 [file] [log] [blame]
Charles Chand55e84d2016-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;
Charles Chanf0994cc2016-05-11 20:39:35 -070023import org.onlab.packet.Ip4Prefix;
Charles Chand55e84d2016-03-30 17:54:24 -070024import org.onlab.packet.IpAddress;
25import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onlab.util.KryoNamespace;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
31import org.onosproject.incubator.net.config.basics.McastConfig;
32import org.onosproject.net.ConnectPoint;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.Link;
35import org.onosproject.net.Path;
36import org.onosproject.net.PortNumber;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.flow.criteria.Criteria;
42import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070046import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070047import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070050import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070051import org.onosproject.net.mcast.McastEvent;
52import org.onosproject.net.mcast.McastRouteInfo;
53import org.onosproject.net.topology.TopologyService;
Charles Chan6ea94fc2016-05-10 17:29:47 -070054import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan2199c302016-04-23 17:36:10 -070055import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chand55e84d2016-03-30 17:54:24 -070056import org.onosproject.store.serializers.KryoNamespaces;
57import org.onosproject.store.service.ConsistentMap;
58import org.onosproject.store.service.Serializer;
59import org.onosproject.store.service.StorageService;
Charles Chan2199c302016-04-23 17:36:10 -070060import org.onosproject.store.service.Versioned;
Charles Chand55e84d2016-03-30 17:54:24 -070061import org.slf4j.Logger;
62import org.slf4j.LoggerFactory;
63
64import java.util.Collection;
65import java.util.Collections;
Charles Chan2199c302016-04-23 17:36:10 -070066import java.util.Iterator;
Charles Chand55e84d2016-03-30 17:54:24 -070067import java.util.List;
Charles Chan2199c302016-04-23 17:36:10 -070068import java.util.Map;
Charles Chand55e84d2016-03-30 17:54:24 -070069import java.util.Optional;
70import java.util.Set;
Charles Chan2199c302016-04-23 17:36:10 -070071import java.util.stream.Collectors;
72
73import static com.google.common.base.Preconditions.checkState;
Charles Chand55e84d2016-03-30 17:54:24 -070074
75/**
Charles Chand2990362016-04-18 13:44:03 -070076 * Handles multicast-related events.
Charles Chand55e84d2016-03-30 17:54:24 -070077 */
Charles Chand2990362016-04-18 13:44:03 -070078public class McastHandler {
79 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chand55e84d2016-03-30 17:54:24 -070080 private final SegmentRoutingManager srManager;
81 private final ApplicationId coreAppId;
82 private StorageService storageService;
83 private TopologyService topologyService;
Charles Chan2199c302016-04-23 17:36:10 -070084 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
85 private final KryoNamespace.Builder mcastKryo;
86 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
87
88 /**
89 * Role in the multicast tree.
90 */
91 public enum McastRole {
92 /**
93 * The device is the ingress device of this group.
94 */
95 INGRESS,
96 /**
97 * The device is the transit device of this group.
98 */
99 TRANSIT,
100 /**
101 * The device is the egress device of this group.
102 */
103 EGRESS
104 }
Charles Chand55e84d2016-03-30 17:54:24 -0700105
106 /**
107 * Constructs the McastEventHandler.
108 *
109 * @param srManager Segment Routing manager
110 */
Charles Chand2990362016-04-18 13:44:03 -0700111 public McastHandler(SegmentRoutingManager srManager) {
Charles Chand55e84d2016-03-30 17:54:24 -0700112 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chand55e84d2016-03-30 17:54:24 -0700113 this.srManager = srManager;
114 this.storageService = srManager.storageService;
115 this.topologyService = srManager.topologyService;
Charles Chan2199c302016-04-23 17:36:10 -0700116 mcastKryo = new KryoNamespace.Builder()
Charles Chand55e84d2016-03-30 17:54:24 -0700117 .register(KryoNamespaces.API)
Charles Chan2199c302016-04-23 17:36:10 -0700118 .register(McastStoreKey.class)
119 .register(McastRole.class);
Charles Chand55e84d2016-03-30 17:54:24 -0700120 mcastNextObjStore = storageService
Charles Chan2199c302016-04-23 17:36:10 -0700121 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chand55e84d2016-03-30 17:54:24 -0700122 .withName("onos-mcast-nextobj-store")
Charles Chan2199c302016-04-23 17:36:10 -0700123 .withSerializer(Serializer.using(mcastKryo.build()))
Charles Chand55e84d2016-03-30 17:54:24 -0700124 .build();
Charles Chan2199c302016-04-23 17:36:10 -0700125 mcastRoleStore = storageService
126 .<McastStoreKey, McastRole>consistentMapBuilder()
127 .withName("onos-mcast-role-store")
128 .withSerializer(Serializer.using(mcastKryo.build()))
129 .build();
130 }
131
132 /**
133 * Read initial multicast from mcast store.
134 */
135 public void init() {
136 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
137 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
138 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
139 sinks.forEach(sink -> {
140 processSinkAddedInternal(source, sink, mcastRoute.group());
141 });
142 });
Charles Chand55e84d2016-03-30 17:54:24 -0700143 }
144
145 /**
146 * Processes the SOURCE_ADDED event.
147 *
148 * @param event McastEvent with SOURCE_ADDED type
149 */
150 protected void processSourceAdded(McastEvent event) {
151 log.info("processSourceAdded {}", event);
152 McastRouteInfo mcastRouteInfo = event.subject();
153 if (!mcastRouteInfo.isComplete()) {
154 log.info("Incompleted McastRouteInfo. Abort.");
155 return;
156 }
157 ConnectPoint source = mcastRouteInfo.source().orElse(null);
158 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
159 IpAddress mcastIp = mcastRouteInfo.route().group();
160
161 sinks.forEach(sink -> {
162 processSinkAddedInternal(source, sink, mcastIp);
163 });
164 }
165
166 /**
167 * Processes the SINK_ADDED event.
168 *
169 * @param event McastEvent with SINK_ADDED type
170 */
171 protected void processSinkAdded(McastEvent event) {
172 log.info("processSinkAdded {}", event);
173 McastRouteInfo mcastRouteInfo = event.subject();
174 if (!mcastRouteInfo.isComplete()) {
175 log.info("Incompleted McastRouteInfo. Abort.");
176 return;
177 }
178 ConnectPoint source = mcastRouteInfo.source().orElse(null);
179 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
180 IpAddress mcastIp = mcastRouteInfo.route().group();
181
182 processSinkAddedInternal(source, sink, mcastIp);
183 }
184
185 /**
186 * Processes the SINK_REMOVED event.
187 *
188 * @param event McastEvent with SINK_REMOVED type
189 */
190 protected void processSinkRemoved(McastEvent event) {
191 log.info("processSinkRemoved {}", event);
192 McastRouteInfo mcastRouteInfo = event.subject();
193 if (!mcastRouteInfo.isComplete()) {
194 log.info("Incompleted McastRouteInfo. Abort.");
195 return;
196 }
197 ConnectPoint source = mcastRouteInfo.source().orElse(null);
198 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
199 IpAddress mcastIp = mcastRouteInfo.route().group();
200 VlanId assignedVlan = assignedVlan();
201
202 // When source and sink are on the same device
203 if (source.deviceId().equals(sink.deviceId())) {
204 // Source and sink are on even the same port. There must be something wrong.
205 if (source.port().equals(sink.port())) {
206 log.warn("Sink is on the same port of source. Abort");
207 return;
208 }
209 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
210 return;
211 }
212
213 // Process the egress device
214 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700215 if (isLast) {
216 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
217 }
Charles Chand55e84d2016-03-30 17:54:24 -0700218
219 // If this is the last sink on the device, also update upstream
220 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
221 if (mcastPath.isPresent()) {
222 List<Link> links = Lists.newArrayList(mcastPath.get().links());
223 Collections.reverse(links);
224 for (Link link : links) {
225 if (isLast) {
226 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
227 mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700228 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chand55e84d2016-03-30 17:54:24 -0700229 }
230 }
231 }
232 }
233
234 /**
235 * Establishes a path from source to sink for given multicast group.
236 *
237 * @param source connect point of the multicast source
238 * @param sink connection point of the multicast sink
239 * @param mcastIp multicast group IP address
240 */
241 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
242 IpAddress mcastIp) {
243 VlanId assignedVlan = assignedVlan();
244
Charles Chan2199c302016-04-23 17:36:10 -0700245 // Process the ingress device
246 addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
247
Charles Chand55e84d2016-03-30 17:54:24 -0700248 // When source and sink are on the same device
249 if (source.deviceId().equals(sink.deviceId())) {
250 // Source and sink are on even the same port. There must be something wrong.
251 if (source.port().equals(sink.port())) {
252 log.warn("Sink is on the same port of source. Abort");
253 return;
254 }
255 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700256 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chand55e84d2016-03-30 17:54:24 -0700257 return;
258 }
259
Charles Chand55e84d2016-03-30 17:54:24 -0700260 // Find a path. If present, create/update groups and flows for each hop
261 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
262 if (mcastPath.isPresent()) {
Charles Chan2199c302016-04-23 17:36:10 -0700263 List<Link> links = mcastPath.get().links();
264 checkState(links.size() == 2,
265 "Path in leaf-spine topology should always be two hops: ", links);
266
267 links.forEach(link -> {
Charles Chand55e84d2016-03-30 17:54:24 -0700268 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
269 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
270 });
Charles Chan2199c302016-04-23 17:36:10 -0700271
Charles Chand55e84d2016-03-30 17:54:24 -0700272 // Process the egress device
273 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700274
275 // Setup mcast roles
276 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
277 McastRole.INGRESS);
278 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
279 McastRole.TRANSIT);
280 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
281 McastRole.EGRESS);
282 } else {
283 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
284 source.deviceId(), sink.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700285 }
286 }
287
288 /**
Charles Chan2199c302016-04-23 17:36:10 -0700289 * Processes the LINK_DOWN event.
290 *
291 * @param affectedLink Link that is going down
292 */
293 protected void processLinkDown(Link affectedLink) {
294 VlanId assignedVlan = assignedVlan();
295
296 getAffectedGroups(affectedLink).forEach(mcastIp -> {
297 // Find out the ingress, transit and egress device of 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 if (ingressDevice == null || transitDevice == null || egressDevices == null) {
304 log.warn("Missing ingress {}, transit {}, or egress {} devices",
305 ingressDevice, transitDevice, egressDevices);
306 return;
307 }
308
309 // Remove entire transit
310 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan);
311
312 // Remove transit-facing port on ingress device
313 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
314 if (ingressTransitPort != null) {
315 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan);
316 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
317 }
318
319 // Construct a new path for each egress device
320 egressDevices.forEach(egressDevice -> {
321 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
322 if (mcastPath.isPresent()) {
323 List<Link> links = mcastPath.get().links();
324 links.forEach(link -> {
325 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
326 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
327 });
328 // Setup new transit mcast role
329 mcastRoleStore.put(new McastStoreKey(mcastIp,
330 links.get(0).dst().deviceId()), McastRole.TRANSIT);
331 } else {
332 log.warn("Fail to recover egress device {} from link failure {}",
333 egressDevice, affectedLink);
334 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan);
335 }
336 });
337 });
338 }
339
340 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700341 * Adds filtering objective for given device and port.
342 *
343 * @param deviceId device ID
344 * @param port ingress port number
345 * @param assignedVlan assigned VLAN ID
346 */
347 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
348 // Do nothing if the port is configured as suppressed
Charles Chan6ea94fc2016-05-10 17:29:47 -0700349 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
350 SegmentRoutingAppConfig appConfig = srManager.cfgService
351 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
352 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
353 log.info("Ignore suppressed port {}", connectPoint);
Charles Chand55e84d2016-03-30 17:54:24 -0700354 return;
355 }
356
Charles Chanf0994cc2016-05-11 20:39:35 -0700357 // Reuse unicast VLAN if the port has subnet configured
358 Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortSubnet(deviceId, port);
359 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
360 final VlanId finalVlanId = (unicastVlan != null) ? unicastVlan : assignedVlan;
361
Charles Chand55e84d2016-03-30 17:54:24 -0700362 FilteringObjective.Builder filtObjBuilder =
Charles Chanf0994cc2016-05-11 20:39:35 -0700363 filterObjBuilder(deviceId, port, finalVlanId);
Charles Chan2199c302016-04-23 17:36:10 -0700364 ObjectiveContext context = new DefaultObjectiveContext(
365 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700366 deviceId, port.toLong(), finalVlanId),
Charles Chan2199c302016-04-23 17:36:10 -0700367 (objective, error) ->
368 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700369 deviceId, port.toLong(), finalVlanId, error));
Charles Chan2199c302016-04-23 17:36:10 -0700370 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700371 }
372
373 /**
374 * Adds a port to given multicast group on given device. This involves the
375 * update of L3 multicast group and multicast routing table entry.
376 *
377 * @param deviceId device ID
378 * @param port port to be added
379 * @param mcastIp multicast group
380 * @param assignedVlan assigned VLAN ID
381 */
382 private void addPortToDevice(DeviceId deviceId, PortNumber port,
383 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700384 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700385 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan2199c302016-04-23 17:36:10 -0700386 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700387 // First time someone request this mcast group via this device
388 portBuilder.add(port);
389 } else {
390 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700391 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700392 // Stop if the port is already in the nextobj
393 Set<PortNumber> existingPorts = getPorts(nextObj.next());
394 if (existingPorts.contains(port)) {
395 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
396 return;
397 }
398 portBuilder.addAll(existingPorts).add(port).build();
399 }
400 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700401 ObjectiveContext context = new DefaultObjectiveContext(
402 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
403 mcastIp, deviceId, port.toLong(), assignedVlan),
404 (objective, error) ->
405 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
406 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700407 NextObjective newNextObj =
408 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
409 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700410 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
411 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700412 srManager.flowObjectiveService.next(deviceId, newNextObj);
413 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700414 }
415
416 /**
417 * Removes a port from given multicast group on given device.
418 * This involves the update of L3 multicast group and multicast routing
419 * table entry.
420 *
421 * @param deviceId device ID
422 * @param port port to be added
423 * @param mcastIp multicast group
424 * @param assignedVlan assigned VLAN ID
425 * @return true if this is the last sink on this device
426 */
427 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
428 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700429 McastStoreKey mcastStoreKey =
430 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700431 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700432 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700433 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
434 return false;
435 }
Charles Chan2199c302016-04-23 17:36:10 -0700436 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700437
438 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700439 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700440 if (!existingPorts.contains(port)) {
441 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
442 return false;
443 }
444 // Copy and modify the ImmutableSet
445 existingPorts = Sets.newHashSet(existingPorts);
446 existingPorts.remove(port);
447
448 NextObjective newNextObj;
449 ForwardingObjective fwdObj;
450 if (existingPorts.isEmpty()) {
451 // If this is the last sink, remove flows and groups
452 // NOTE: Rely on GroupStore garbage collection rather than explicitly
453 // remove L3MG since there might be other flows/groups refer to
454 // the same L2IG
Charles Chan2199c302016-04-23 17:36:10 -0700455 ObjectiveContext context = new DefaultObjectiveContext(
456 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
457 mcastIp, deviceId, port.toLong(), assignedVlan),
458 (objective, error) ->
459 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
460 mcastIp, deviceId, port.toLong(), assignedVlan, error));
461 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
462 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700463 srManager.flowObjectiveService.forward(deviceId, fwdObj);
464 } else {
465 // If this is not the last sink, update flows and groups
Charles Chan2199c302016-04-23 17:36:10 -0700466 ObjectiveContext context = new DefaultObjectiveContext(
467 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
468 mcastIp, deviceId, port.toLong(), assignedVlan),
469 (objective, error) ->
470 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
471 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700472 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
473 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
Charles Chan2199c302016-04-23 17:36:10 -0700474 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700475 srManager.flowObjectiveService.next(deviceId, newNextObj);
476 srManager.flowObjectiveService.forward(deviceId, fwdObj);
477 }
Charles Chand55e84d2016-03-30 17:54:24 -0700478 return existingPorts.isEmpty();
479 }
480
Charles Chan2199c302016-04-23 17:36:10 -0700481
482 /**
483 * Removes entire group on given device.
484 *
485 * @param deviceId device ID
486 * @param mcastIp multicast group to be removed
487 * @param assignedVlan assigned VLAN ID
488 */
489 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
490 VlanId assignedVlan) {
491 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
492 // This device is not serving this multicast group
493 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
494 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
495 return;
496 }
497 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
498 // NOTE: Rely on GroupStore garbage collection rather than explicitly
499 // remove L3MG since there might be other flows/groups refer to
500 // the same L2IG
501 ObjectiveContext context = new DefaultObjectiveContext(
502 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
503 mcastIp, deviceId, assignedVlan),
504 (objective, error) ->
505 log.warn("Failed to remove {} on {}, vlan {}: {}",
506 mcastIp, deviceId, assignedVlan, error));
507 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
508 srManager.flowObjectiveService.forward(deviceId, fwdObj);
509 mcastNextObjStore.remove(mcastStoreKey);
510 mcastRoleStore.remove(mcastStoreKey);
511 }
512
513 /**
514 * Remove all groups on given device.
515 *
516 * @param deviceId device ID
517 */
518 public void removeDevice(DeviceId deviceId) {
519 Iterator<Map.Entry<McastStoreKey, Versioned<NextObjective>>> itNextObj =
520 mcastNextObjStore.entrySet().iterator();
521 while (itNextObj.hasNext()) {
522 Map.Entry<McastStoreKey, Versioned<NextObjective>> entry = itNextObj.next();
523 if (entry.getKey().deviceId().equals(deviceId)) {
524 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(), assignedVlan());
525 itNextObj.remove();
526 }
527 }
528
529 Iterator<Map.Entry<McastStoreKey, Versioned<McastRole>>> itRole =
530 mcastRoleStore.entrySet().iterator();
531 while (itRole.hasNext()) {
532 Map.Entry<McastStoreKey, Versioned<McastRole>> entry = itRole.next();
533 if (entry.getKey().deviceId().equals(deviceId)) {
534 itRole.remove();
535 }
536 }
537
538 }
539
Charles Chand55e84d2016-03-30 17:54:24 -0700540 /**
541 * Creates a next objective builder for multicast.
542 *
543 * @param mcastIp multicast group
544 * @param assignedVlan assigned VLAN ID
545 * @param outPorts set of output port numbers
546 * @return next objective builder
547 */
548 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
549 VlanId assignedVlan, Set<PortNumber> outPorts) {
550 int nextId = srManager.flowObjectiveService.allocateNextId();
551
552 TrafficSelector metadata =
553 DefaultTrafficSelector.builder()
554 .matchVlanId(assignedVlan)
555 .matchIPDst(mcastIp.toIpPrefix())
556 .build();
557
558 NextObjective.Builder nextObjBuilder = DefaultNextObjective
559 .builder().withId(nextId)
560 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
561 .withMeta(metadata);
562
563 outPorts.forEach(port -> {
564 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
565 if (egressVlan().equals(VlanId.NONE)) {
566 tBuilder.popVlan();
567 }
568 tBuilder.setOutput(port);
569 nextObjBuilder.addTreatment(tBuilder.build());
570 });
571
572 return nextObjBuilder;
573 }
574
575 /**
576 * Creates a forwarding objective builder for multicast.
577 *
578 * @param mcastIp multicast group
579 * @param assignedVlan assigned VLAN ID
580 * @param nextId next ID of the L3 multicast group
581 * @return forwarding objective builder
582 */
583 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
584 VlanId assignedVlan, int nextId) {
585 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
586 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
587 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
588 sbuilder.matchIPDst(mcastPrefix);
589 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
590 metabuilder.matchVlanId(assignedVlan);
591
592 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
593 fwdBuilder.withSelector(sbuilder.build())
594 .withMeta(metabuilder.build())
595 .nextStep(nextId)
596 .withFlag(ForwardingObjective.Flag.SPECIFIC)
597 .fromApp(srManager.appId)
598 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
599 return fwdBuilder;
600 }
601
602 /**
603 * Creates a filtering objective builder for multicast.
604 *
605 * @param deviceId Device ID
606 * @param ingressPort ingress port of the multicast stream
607 * @param assignedVlan assigned VLAN ID
608 * @return filtering objective builder
609 */
610 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
611 VlanId assignedVlan) {
612 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
613 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
614 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
615 MacAddress.IPV4_MULTICAST_MASK))
616 .addCondition(Criteria.matchVlanId(egressVlan()))
617 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
618 // vlan assignment is valid only if this instance is master
619 if (srManager.mastershipService.isLocalMaster(deviceId)) {
620 TrafficTreatment tt = DefaultTrafficTreatment.builder()
621 .pushVlan().setVlanId(assignedVlan).build();
622 filtBuilder.withMeta(tt);
623 }
624 return filtBuilder.permit().fromApp(srManager.appId);
625 }
626
627 /**
628 * Gets output ports information from treatments.
629 *
630 * @param treatments collection of traffic treatments
631 * @return set of output port numbers
632 */
633 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
634 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
635 treatments.forEach(treatment -> {
636 treatment.allInstructions().stream()
637 .filter(instr -> instr instanceof OutputInstruction)
638 .forEach(instr -> {
639 builder.add(((OutputInstruction) instr).port());
640 });
641 });
642 return builder.build();
643 }
644
645 /**
646 * Gets a path from src to dst.
647 * If a path was allocated before, returns the allocated path.
648 * Otherwise, randomly pick one from available paths.
649 *
650 * @param src source device ID
651 * @param dst destination device ID
652 * @param mcastIp multicast group
653 * @return an optional path from src to dst
654 */
655 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
656 List<Path> allPaths = Lists.newArrayList(
657 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700658 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700659 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700660 return Optional.empty();
661 }
662
663 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700664 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
665 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
666 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700667 Set<PortNumber> existingPorts = getPorts(nextObj.next());
668 for (Path path : allPaths) {
669 PortNumber srcPort = path.links().get(0).src().port();
670 if (existingPorts.contains(srcPort)) {
671 return Optional.of(path);
672 }
673 }
674 }
675 // Otherwise, randomly pick a path
676 Collections.shuffle(allPaths);
677 return allPaths.stream().findFirst();
678 }
679
680 /**
Charles Chan2199c302016-04-23 17:36:10 -0700681 * Gets device(s) of given role in given multicast group.
682 *
683 * @param mcastIp multicast IP
684 * @param role multicast role
685 * @return set of device ID or empty set if not found
686 */
687 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
688 return mcastRoleStore.entrySet().stream()
689 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
690 entry.getValue().value() == role)
691 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
692 .collect(Collectors.toSet());
693 }
694
695 /**
696 * Gets groups which is affected by the link down event.
697 *
698 * @param link link going down
699 * @return a set of multicast IpAddress
700 */
701 private Set<IpAddress> getAffectedGroups(Link link) {
702 DeviceId deviceId = link.src().deviceId();
703 PortNumber port = link.src().port();
704 return mcastNextObjStore.entrySet().stream()
705 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
706 getPorts(entry.getValue().value().next()).contains(port))
707 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
708 .collect(Collectors.toSet());
709 }
710
711 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700712 * Gets egress VLAN from McastConfig.
713 *
714 * @return egress VLAN or VlanId.NONE if not configured
715 */
716 private VlanId egressVlan() {
717 McastConfig mcastConfig =
718 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
719 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
720 }
721
722 /**
723 * Gets assigned VLAN according to the value of egress VLAN.
724 *
725 * @return assigned VLAN
726 */
727 private VlanId assignedVlan() {
728 return (egressVlan().equals(VlanId.NONE)) ?
729 VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) :
730 egressVlan();
731 }
Charles Chan2199c302016-04-23 17:36:10 -0700732
733 /**
734 * Gets the spine-facing port on ingress device of given multicast group.
735 *
736 * @param mcastIp multicast IP
737 * @return spine-facing port on ingress device
738 */
739 private PortNumber ingressTransitPort(IpAddress mcastIp) {
740 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
741 .stream().findAny().orElse(null);
742 if (ingressDevice != null) {
743 NextObjective nextObj = mcastNextObjStore
744 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
745 Set<PortNumber> ports = getPorts(nextObj.next());
746
747 for (PortNumber port : ports) {
748 // Spine-facing port should have no subnet and no xconnect
749 if (srManager.deviceConfiguration != null &&
750 srManager.deviceConfiguration.getPortSubnet(ingressDevice, port) == null &&
751 srManager.deviceConfiguration.getXConnects().values().stream()
752 .allMatch(connectPoints ->
753 connectPoints.stream().noneMatch(connectPoint ->
754 connectPoint.port().equals(port))
755 )) {
756 return port;
757 }
758 }
759 }
760 return null;
761 }
Charles Chand55e84d2016-03-30 17:54:24 -0700762}