blob: d3d8759b8850bfeae9dbfd4c9cf277bdddb9b2c4 [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 Chan2199c302016-04-23 17:36:10 -070054import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chand55e84d2016-03-30 17:54:24 -070055import org.onosproject.store.serializers.KryoNamespaces;
56import org.onosproject.store.service.ConsistentMap;
57import org.onosproject.store.service.Serializer;
58import org.onosproject.store.service.StorageService;
Charles Chan2199c302016-04-23 17:36:10 -070059import org.onosproject.store.service.Versioned;
Charles Chand55e84d2016-03-30 17:54:24 -070060import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.util.Collection;
64import java.util.Collections;
Charles Chan2199c302016-04-23 17:36:10 -070065import java.util.Iterator;
Charles Chand55e84d2016-03-30 17:54:24 -070066import java.util.List;
Charles Chan2199c302016-04-23 17:36:10 -070067import java.util.Map;
Charles Chand55e84d2016-03-30 17:54:24 -070068import java.util.Optional;
69import java.util.Set;
Charles Chan2199c302016-04-23 17:36:10 -070070import java.util.stream.Collectors;
71
72import static com.google.common.base.Preconditions.checkState;
Charles Chand55e84d2016-03-30 17:54:24 -070073
74/**
Charles Chand2990362016-04-18 13:44:03 -070075 * Handles multicast-related events.
Charles Chand55e84d2016-03-30 17:54:24 -070076 */
Charles Chand2990362016-04-18 13:44:03 -070077public class McastHandler {
78 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chand55e84d2016-03-30 17:54:24 -070079 private final SegmentRoutingManager srManager;
80 private final ApplicationId coreAppId;
81 private StorageService storageService;
82 private TopologyService topologyService;
Charles Chan2199c302016-04-23 17:36:10 -070083 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
84 private final KryoNamespace.Builder mcastKryo;
85 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
86
87 /**
88 * Role in the multicast tree.
89 */
90 public enum McastRole {
91 /**
92 * The device is the ingress device of this group.
93 */
94 INGRESS,
95 /**
96 * The device is the transit device of this group.
97 */
98 TRANSIT,
99 /**
100 * The device is the egress device of this group.
101 */
102 EGRESS
103 }
Charles Chand55e84d2016-03-30 17:54:24 -0700104
105 /**
106 * Constructs the McastEventHandler.
107 *
108 * @param srManager Segment Routing manager
109 */
Charles Chand2990362016-04-18 13:44:03 -0700110 public McastHandler(SegmentRoutingManager srManager) {
Charles Chand55e84d2016-03-30 17:54:24 -0700111 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chand55e84d2016-03-30 17:54:24 -0700112 this.srManager = srManager;
113 this.storageService = srManager.storageService;
114 this.topologyService = srManager.topologyService;
Charles Chan2199c302016-04-23 17:36:10 -0700115 mcastKryo = new KryoNamespace.Builder()
Charles Chand55e84d2016-03-30 17:54:24 -0700116 .register(KryoNamespaces.API)
Charles Chan2199c302016-04-23 17:36:10 -0700117 .register(McastStoreKey.class)
118 .register(McastRole.class);
Charles Chand55e84d2016-03-30 17:54:24 -0700119 mcastNextObjStore = storageService
Charles Chan2199c302016-04-23 17:36:10 -0700120 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chand55e84d2016-03-30 17:54:24 -0700121 .withName("onos-mcast-nextobj-store")
Charles Chan2199c302016-04-23 17:36:10 -0700122 .withSerializer(Serializer.using(mcastKryo.build()))
Charles Chand55e84d2016-03-30 17:54:24 -0700123 .build();
Charles Chan2199c302016-04-23 17:36:10 -0700124 mcastRoleStore = storageService
125 .<McastStoreKey, McastRole>consistentMapBuilder()
126 .withName("onos-mcast-role-store")
127 .withSerializer(Serializer.using(mcastKryo.build()))
128 .build();
129 }
130
131 /**
132 * Read initial multicast from mcast store.
133 */
134 public void init() {
135 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
136 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
137 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
138 sinks.forEach(sink -> {
139 processSinkAddedInternal(source, sink, mcastRoute.group());
140 });
141 });
Charles Chand55e84d2016-03-30 17:54:24 -0700142 }
143
144 /**
145 * Processes the SOURCE_ADDED event.
146 *
147 * @param event McastEvent with SOURCE_ADDED type
148 */
149 protected void processSourceAdded(McastEvent event) {
150 log.info("processSourceAdded {}", event);
151 McastRouteInfo mcastRouteInfo = event.subject();
152 if (!mcastRouteInfo.isComplete()) {
153 log.info("Incompleted McastRouteInfo. Abort.");
154 return;
155 }
156 ConnectPoint source = mcastRouteInfo.source().orElse(null);
157 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
158 IpAddress mcastIp = mcastRouteInfo.route().group();
159
160 sinks.forEach(sink -> {
161 processSinkAddedInternal(source, sink, mcastIp);
162 });
163 }
164
165 /**
166 * Processes the SINK_ADDED event.
167 *
168 * @param event McastEvent with SINK_ADDED type
169 */
170 protected void processSinkAdded(McastEvent event) {
171 log.info("processSinkAdded {}", event);
172 McastRouteInfo mcastRouteInfo = event.subject();
173 if (!mcastRouteInfo.isComplete()) {
174 log.info("Incompleted McastRouteInfo. Abort.");
175 return;
176 }
177 ConnectPoint source = mcastRouteInfo.source().orElse(null);
178 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
179 IpAddress mcastIp = mcastRouteInfo.route().group();
180
181 processSinkAddedInternal(source, sink, mcastIp);
182 }
183
184 /**
185 * Processes the SINK_REMOVED event.
186 *
187 * @param event McastEvent with SINK_REMOVED type
188 */
189 protected void processSinkRemoved(McastEvent event) {
190 log.info("processSinkRemoved {}", event);
191 McastRouteInfo mcastRouteInfo = event.subject();
192 if (!mcastRouteInfo.isComplete()) {
193 log.info("Incompleted McastRouteInfo. Abort.");
194 return;
195 }
196 ConnectPoint source = mcastRouteInfo.source().orElse(null);
197 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
198 IpAddress mcastIp = mcastRouteInfo.route().group();
199 VlanId assignedVlan = assignedVlan();
200
201 // When source and sink are on the same device
202 if (source.deviceId().equals(sink.deviceId())) {
203 // Source and sink are on even the same port. There must be something wrong.
204 if (source.port().equals(sink.port())) {
205 log.warn("Sink is on the same port of source. Abort");
206 return;
207 }
208 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
209 return;
210 }
211
212 // Process the egress device
213 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700214 if (isLast) {
215 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
216 }
Charles Chand55e84d2016-03-30 17:54:24 -0700217
218 // If this is the last sink on the device, also update upstream
219 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
220 if (mcastPath.isPresent()) {
221 List<Link> links = Lists.newArrayList(mcastPath.get().links());
222 Collections.reverse(links);
223 for (Link link : links) {
224 if (isLast) {
225 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
226 mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700227 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chand55e84d2016-03-30 17:54:24 -0700228 }
229 }
230 }
231 }
232
233 /**
234 * Establishes a path from source to sink for given multicast group.
235 *
236 * @param source connect point of the multicast source
237 * @param sink connection point of the multicast sink
238 * @param mcastIp multicast group IP address
239 */
240 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
241 IpAddress mcastIp) {
242 VlanId assignedVlan = assignedVlan();
243
Charles Chan2199c302016-04-23 17:36:10 -0700244 // Process the ingress device
245 addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
246
Charles Chand55e84d2016-03-30 17:54:24 -0700247 // When source and sink are on the same device
248 if (source.deviceId().equals(sink.deviceId())) {
249 // Source and sink are on even the same port. There must be something wrong.
250 if (source.port().equals(sink.port())) {
251 log.warn("Sink is on the same port of source. Abort");
252 return;
253 }
254 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700255 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chand55e84d2016-03-30 17:54:24 -0700256 return;
257 }
258
Charles Chand55e84d2016-03-30 17:54:24 -0700259 // Find a path. If present, create/update groups and flows for each hop
260 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
261 if (mcastPath.isPresent()) {
Charles Chan2199c302016-04-23 17:36:10 -0700262 List<Link> links = mcastPath.get().links();
263 checkState(links.size() == 2,
264 "Path in leaf-spine topology should always be two hops: ", links);
265
266 links.forEach(link -> {
Charles Chand55e84d2016-03-30 17:54:24 -0700267 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
268 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
269 });
Charles Chan2199c302016-04-23 17:36:10 -0700270
Charles Chand55e84d2016-03-30 17:54:24 -0700271 // Process the egress device
272 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700273
274 // Setup mcast roles
275 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
276 McastRole.INGRESS);
277 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
278 McastRole.TRANSIT);
279 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
280 McastRole.EGRESS);
281 } else {
282 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
283 source.deviceId(), sink.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700284 }
285 }
286
287 /**
Charles Chan2199c302016-04-23 17:36:10 -0700288 * Processes the LINK_DOWN event.
289 *
290 * @param affectedLink Link that is going down
291 */
292 protected void processLinkDown(Link affectedLink) {
293 VlanId assignedVlan = assignedVlan();
294
295 getAffectedGroups(affectedLink).forEach(mcastIp -> {
296 // Find out the ingress, transit and egress device of affected group
297 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
298 .stream().findAny().orElse(null);
299 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
300 .stream().findAny().orElse(null);
301 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
302 if (ingressDevice == null || transitDevice == null || egressDevices == null) {
303 log.warn("Missing ingress {}, transit {}, or egress {} devices",
304 ingressDevice, transitDevice, egressDevices);
305 return;
306 }
307
308 // Remove entire transit
309 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan);
310
311 // Remove transit-facing port on ingress device
312 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
313 if (ingressTransitPort != null) {
314 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan);
315 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
316 }
317
318 // Construct a new path for each egress device
319 egressDevices.forEach(egressDevice -> {
320 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
321 if (mcastPath.isPresent()) {
322 List<Link> links = mcastPath.get().links();
323 links.forEach(link -> {
324 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
325 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
326 });
327 // Setup new transit mcast role
328 mcastRoleStore.put(new McastStoreKey(mcastIp,
329 links.get(0).dst().deviceId()), McastRole.TRANSIT);
330 } else {
331 log.warn("Fail to recover egress device {} from link failure {}",
332 egressDevice, affectedLink);
333 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan);
334 }
335 });
336 });
337 }
338
339 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700340 * Adds filtering objective for given device and port.
341 *
342 * @param deviceId device ID
343 * @param port ingress port number
344 * @param assignedVlan assigned VLAN ID
345 */
346 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
347 // Do nothing if the port is configured as suppressed
348 ConnectPoint connectPt = new ConnectPoint(deviceId, port);
Charles Chan2199c302016-04-23 17:36:10 -0700349 if (srManager.deviceConfiguration == null ||
350 srManager.deviceConfiguration.suppressSubnet().contains(connectPt) ||
Charles Chand55e84d2016-03-30 17:54:24 -0700351 srManager.deviceConfiguration.suppressHost().contains(connectPt)) {
352 log.info("Ignore suppressed port {}", connectPt);
353 return;
354 }
355
Charles Chanf0994cc2016-05-11 20:39:35 -0700356 // Reuse unicast VLAN if the port has subnet configured
357 Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortSubnet(deviceId, port);
358 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
359 final VlanId finalVlanId = (unicastVlan != null) ? unicastVlan : assignedVlan;
360
Charles Chand55e84d2016-03-30 17:54:24 -0700361 FilteringObjective.Builder filtObjBuilder =
Charles Chanf0994cc2016-05-11 20:39:35 -0700362 filterObjBuilder(deviceId, port, finalVlanId);
Charles Chan2199c302016-04-23 17:36:10 -0700363 ObjectiveContext context = new DefaultObjectiveContext(
364 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700365 deviceId, port.toLong(), finalVlanId),
Charles Chan2199c302016-04-23 17:36:10 -0700366 (objective, error) ->
367 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700368 deviceId, port.toLong(), finalVlanId, error));
Charles Chan2199c302016-04-23 17:36:10 -0700369 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700370 }
371
372 /**
373 * Adds a port to given multicast group on given device. This involves the
374 * update of L3 multicast group and multicast routing table entry.
375 *
376 * @param deviceId device ID
377 * @param port port to be added
378 * @param mcastIp multicast group
379 * @param assignedVlan assigned VLAN ID
380 */
381 private void addPortToDevice(DeviceId deviceId, PortNumber port,
382 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700383 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700384 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan2199c302016-04-23 17:36:10 -0700385 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700386 // First time someone request this mcast group via this device
387 portBuilder.add(port);
388 } else {
389 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700390 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700391 // Stop if the port is already in the nextobj
392 Set<PortNumber> existingPorts = getPorts(nextObj.next());
393 if (existingPorts.contains(port)) {
394 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
395 return;
396 }
397 portBuilder.addAll(existingPorts).add(port).build();
398 }
399 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700400 ObjectiveContext context = new DefaultObjectiveContext(
401 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
402 mcastIp, deviceId, port.toLong(), assignedVlan),
403 (objective, error) ->
404 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
405 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700406 NextObjective newNextObj =
407 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
408 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700409 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
410 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700411 srManager.flowObjectiveService.next(deviceId, newNextObj);
412 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700413 }
414
415 /**
416 * Removes a port from given multicast group on given device.
417 * This involves the update of L3 multicast group and multicast routing
418 * table entry.
419 *
420 * @param deviceId device ID
421 * @param port port to be added
422 * @param mcastIp multicast group
423 * @param assignedVlan assigned VLAN ID
424 * @return true if this is the last sink on this device
425 */
426 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
427 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700428 McastStoreKey mcastStoreKey =
429 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700430 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700431 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700432 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
433 return false;
434 }
Charles Chan2199c302016-04-23 17:36:10 -0700435 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700436
437 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700438 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700439 if (!existingPorts.contains(port)) {
440 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
441 return false;
442 }
443 // Copy and modify the ImmutableSet
444 existingPorts = Sets.newHashSet(existingPorts);
445 existingPorts.remove(port);
446
447 NextObjective newNextObj;
448 ForwardingObjective fwdObj;
449 if (existingPorts.isEmpty()) {
450 // If this is the last sink, remove flows and groups
451 // NOTE: Rely on GroupStore garbage collection rather than explicitly
452 // remove L3MG since there might be other flows/groups refer to
453 // the same L2IG
Charles Chan2199c302016-04-23 17:36:10 -0700454 ObjectiveContext context = new DefaultObjectiveContext(
455 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
456 mcastIp, deviceId, port.toLong(), assignedVlan),
457 (objective, error) ->
458 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
459 mcastIp, deviceId, port.toLong(), assignedVlan, error));
460 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
461 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700462 srManager.flowObjectiveService.forward(deviceId, fwdObj);
463 } else {
464 // If this is not the last sink, update flows and groups
Charles Chan2199c302016-04-23 17:36:10 -0700465 ObjectiveContext context = new DefaultObjectiveContext(
466 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
467 mcastIp, deviceId, port.toLong(), assignedVlan),
468 (objective, error) ->
469 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
470 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700471 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
472 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
Charles Chan2199c302016-04-23 17:36:10 -0700473 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700474 srManager.flowObjectiveService.next(deviceId, newNextObj);
475 srManager.flowObjectiveService.forward(deviceId, fwdObj);
476 }
Charles Chand55e84d2016-03-30 17:54:24 -0700477 return existingPorts.isEmpty();
478 }
479
Charles Chan2199c302016-04-23 17:36:10 -0700480
481 /**
482 * Removes entire group on given device.
483 *
484 * @param deviceId device ID
485 * @param mcastIp multicast group to be removed
486 * @param assignedVlan assigned VLAN ID
487 */
488 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
489 VlanId assignedVlan) {
490 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
491 // This device is not serving this multicast group
492 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
493 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
494 return;
495 }
496 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
497 // NOTE: Rely on GroupStore garbage collection rather than explicitly
498 // remove L3MG since there might be other flows/groups refer to
499 // the same L2IG
500 ObjectiveContext context = new DefaultObjectiveContext(
501 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
502 mcastIp, deviceId, assignedVlan),
503 (objective, error) ->
504 log.warn("Failed to remove {} on {}, vlan {}: {}",
505 mcastIp, deviceId, assignedVlan, error));
506 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
507 srManager.flowObjectiveService.forward(deviceId, fwdObj);
508 mcastNextObjStore.remove(mcastStoreKey);
509 mcastRoleStore.remove(mcastStoreKey);
510 }
511
512 /**
513 * Remove all groups on given device.
514 *
515 * @param deviceId device ID
516 */
517 public void removeDevice(DeviceId deviceId) {
518 Iterator<Map.Entry<McastStoreKey, Versioned<NextObjective>>> itNextObj =
519 mcastNextObjStore.entrySet().iterator();
520 while (itNextObj.hasNext()) {
521 Map.Entry<McastStoreKey, Versioned<NextObjective>> entry = itNextObj.next();
522 if (entry.getKey().deviceId().equals(deviceId)) {
523 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(), assignedVlan());
524 itNextObj.remove();
525 }
526 }
527
528 Iterator<Map.Entry<McastStoreKey, Versioned<McastRole>>> itRole =
529 mcastRoleStore.entrySet().iterator();
530 while (itRole.hasNext()) {
531 Map.Entry<McastStoreKey, Versioned<McastRole>> entry = itRole.next();
532 if (entry.getKey().deviceId().equals(deviceId)) {
533 itRole.remove();
534 }
535 }
536
537 }
538
Charles Chand55e84d2016-03-30 17:54:24 -0700539 /**
540 * Creates a next objective builder for multicast.
541 *
542 * @param mcastIp multicast group
543 * @param assignedVlan assigned VLAN ID
544 * @param outPorts set of output port numbers
545 * @return next objective builder
546 */
547 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
548 VlanId assignedVlan, Set<PortNumber> outPorts) {
549 int nextId = srManager.flowObjectiveService.allocateNextId();
550
551 TrafficSelector metadata =
552 DefaultTrafficSelector.builder()
553 .matchVlanId(assignedVlan)
554 .matchIPDst(mcastIp.toIpPrefix())
555 .build();
556
557 NextObjective.Builder nextObjBuilder = DefaultNextObjective
558 .builder().withId(nextId)
559 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
560 .withMeta(metadata);
561
562 outPorts.forEach(port -> {
563 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
564 if (egressVlan().equals(VlanId.NONE)) {
565 tBuilder.popVlan();
566 }
567 tBuilder.setOutput(port);
568 nextObjBuilder.addTreatment(tBuilder.build());
569 });
570
571 return nextObjBuilder;
572 }
573
574 /**
575 * Creates a forwarding objective builder for multicast.
576 *
577 * @param mcastIp multicast group
578 * @param assignedVlan assigned VLAN ID
579 * @param nextId next ID of the L3 multicast group
580 * @return forwarding objective builder
581 */
582 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
583 VlanId assignedVlan, int nextId) {
584 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
585 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
586 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
587 sbuilder.matchIPDst(mcastPrefix);
588 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
589 metabuilder.matchVlanId(assignedVlan);
590
591 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
592 fwdBuilder.withSelector(sbuilder.build())
593 .withMeta(metabuilder.build())
594 .nextStep(nextId)
595 .withFlag(ForwardingObjective.Flag.SPECIFIC)
596 .fromApp(srManager.appId)
597 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
598 return fwdBuilder;
599 }
600
601 /**
602 * Creates a filtering objective builder for multicast.
603 *
604 * @param deviceId Device ID
605 * @param ingressPort ingress port of the multicast stream
606 * @param assignedVlan assigned VLAN ID
607 * @return filtering objective builder
608 */
609 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
610 VlanId assignedVlan) {
611 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
612 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
613 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
614 MacAddress.IPV4_MULTICAST_MASK))
615 .addCondition(Criteria.matchVlanId(egressVlan()))
616 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
617 // vlan assignment is valid only if this instance is master
618 if (srManager.mastershipService.isLocalMaster(deviceId)) {
619 TrafficTreatment tt = DefaultTrafficTreatment.builder()
620 .pushVlan().setVlanId(assignedVlan).build();
621 filtBuilder.withMeta(tt);
622 }
623 return filtBuilder.permit().fromApp(srManager.appId);
624 }
625
626 /**
627 * Gets output ports information from treatments.
628 *
629 * @param treatments collection of traffic treatments
630 * @return set of output port numbers
631 */
632 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
633 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
634 treatments.forEach(treatment -> {
635 treatment.allInstructions().stream()
636 .filter(instr -> instr instanceof OutputInstruction)
637 .forEach(instr -> {
638 builder.add(((OutputInstruction) instr).port());
639 });
640 });
641 return builder.build();
642 }
643
644 /**
645 * Gets a path from src to dst.
646 * If a path was allocated before, returns the allocated path.
647 * Otherwise, randomly pick one from available paths.
648 *
649 * @param src source device ID
650 * @param dst destination device ID
651 * @param mcastIp multicast group
652 * @return an optional path from src to dst
653 */
654 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
655 List<Path> allPaths = Lists.newArrayList(
656 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700657 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700658 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700659 return Optional.empty();
660 }
661
662 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700663 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
664 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
665 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700666 Set<PortNumber> existingPorts = getPorts(nextObj.next());
667 for (Path path : allPaths) {
668 PortNumber srcPort = path.links().get(0).src().port();
669 if (existingPorts.contains(srcPort)) {
670 return Optional.of(path);
671 }
672 }
673 }
674 // Otherwise, randomly pick a path
675 Collections.shuffle(allPaths);
676 return allPaths.stream().findFirst();
677 }
678
679 /**
Charles Chan2199c302016-04-23 17:36:10 -0700680 * Gets device(s) of given role in given multicast group.
681 *
682 * @param mcastIp multicast IP
683 * @param role multicast role
684 * @return set of device ID or empty set if not found
685 */
686 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
687 return mcastRoleStore.entrySet().stream()
688 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
689 entry.getValue().value() == role)
690 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
691 .collect(Collectors.toSet());
692 }
693
694 /**
695 * Gets groups which is affected by the link down event.
696 *
697 * @param link link going down
698 * @return a set of multicast IpAddress
699 */
700 private Set<IpAddress> getAffectedGroups(Link link) {
701 DeviceId deviceId = link.src().deviceId();
702 PortNumber port = link.src().port();
703 return mcastNextObjStore.entrySet().stream()
704 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
705 getPorts(entry.getValue().value().next()).contains(port))
706 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
707 .collect(Collectors.toSet());
708 }
709
710 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700711 * Gets egress VLAN from McastConfig.
712 *
713 * @return egress VLAN or VlanId.NONE if not configured
714 */
715 private VlanId egressVlan() {
716 McastConfig mcastConfig =
717 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
718 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
719 }
720
721 /**
722 * Gets assigned VLAN according to the value of egress VLAN.
723 *
724 * @return assigned VLAN
725 */
726 private VlanId assignedVlan() {
727 return (egressVlan().equals(VlanId.NONE)) ?
728 VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) :
729 egressVlan();
730 }
Charles Chan2199c302016-04-23 17:36:10 -0700731
732 /**
733 * Gets the spine-facing port on ingress device of given multicast group.
734 *
735 * @param mcastIp multicast IP
736 * @return spine-facing port on ingress device
737 */
738 private PortNumber ingressTransitPort(IpAddress mcastIp) {
739 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
740 .stream().findAny().orElse(null);
741 if (ingressDevice != null) {
742 NextObjective nextObj = mcastNextObjStore
743 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
744 Set<PortNumber> ports = getPorts(nextObj.next());
745
746 for (PortNumber port : ports) {
747 // Spine-facing port should have no subnet and no xconnect
748 if (srManager.deviceConfiguration != null &&
749 srManager.deviceConfiguration.getPortSubnet(ingressDevice, port) == null &&
750 srManager.deviceConfiguration.getXConnects().values().stream()
751 .allMatch(connectPoints ->
752 connectPoints.stream().noneMatch(connectPoint ->
753 connectPoint.port().equals(port))
754 )) {
755 return port;
756 }
757 }
758 }
759 return null;
760 }
Charles Chand55e84d2016-03-30 17:54:24 -0700761}