blob: b65c0fd5bb7844b282e3c4342cb98d09d3c9e74a [file] [log] [blame]
Charles Chand55e84d2016-03-30 17:54:24 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chand55e84d2016-03-30 17:54:24 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
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 Chan2199c302016-04-23 17:36:10 -070045import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070046import org.onosproject.net.flowobjective.FilteringObjective;
47import org.onosproject.net.flowobjective.ForwardingObjective;
48import org.onosproject.net.flowobjective.NextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070049import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070050import org.onosproject.net.mcast.McastEvent;
51import org.onosproject.net.mcast.McastRouteInfo;
52import org.onosproject.net.topology.TopologyService;
Charles Chan6ea94fc2016-05-10 17:29:47 -070053import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
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;
59import org.slf4j.Logger;
60import org.slf4j.LoggerFactory;
61
62import java.util.Collection;
63import java.util.Collections;
64import java.util.List;
Charles Chan2199c302016-04-23 17:36:10 -070065import java.util.Map;
Charles Chand55e84d2016-03-30 17:54:24 -070066import java.util.Optional;
67import java.util.Set;
Charles Chan2199c302016-04-23 17:36:10 -070068import java.util.stream.Collectors;
69
70import static com.google.common.base.Preconditions.checkState;
Charles Chan59cc16d2017-02-02 16:20:42 -080071import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -070072
73/**
Charles Chand2990362016-04-18 13:44:03 -070074 * Handles multicast-related events.
Charles Chand55e84d2016-03-30 17:54:24 -070075 */
Charles Chand2990362016-04-18 13:44:03 -070076public class McastHandler {
77 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chand55e84d2016-03-30 17:54:24 -070078 private final SegmentRoutingManager srManager;
79 private final ApplicationId coreAppId;
Charles Chanfc5c7802016-05-17 13:13:55 -070080 private final StorageService storageService;
81 private final TopologyService topologyService;
Charles Chan2199c302016-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 Chand55e84d2016-03-30 17:54:24 -0700103
104 /**
105 * Constructs the McastEventHandler.
106 *
107 * @param srManager Segment Routing manager
108 */
Charles Chand2990362016-04-18 13:44:03 -0700109 public McastHandler(SegmentRoutingManager srManager) {
Charles Chand55e84d2016-03-30 17:54:24 -0700110 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chand55e84d2016-03-30 17:54:24 -0700111 this.srManager = srManager;
112 this.storageService = srManager.storageService;
113 this.topologyService = srManager.topologyService;
Charles Chan2199c302016-04-23 17:36:10 -0700114 mcastKryo = new KryoNamespace.Builder()
Charles Chand55e84d2016-03-30 17:54:24 -0700115 .register(KryoNamespaces.API)
Charles Chan2199c302016-04-23 17:36:10 -0700116 .register(McastStoreKey.class)
117 .register(McastRole.class);
Charles Chand55e84d2016-03-30 17:54:24 -0700118 mcastNextObjStore = storageService
Charles Chan2199c302016-04-23 17:36:10 -0700119 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chand55e84d2016-03-30 17:54:24 -0700120 .withName("onos-mcast-nextobj-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700121 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chand55e84d2016-03-30 17:54:24 -0700122 .build();
Charles Chan2199c302016-04-23 17:36:10 -0700123 mcastRoleStore = storageService
124 .<McastStoreKey, McastRole>consistentMapBuilder()
125 .withName("onos-mcast-role-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700126 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan2199c302016-04-23 17:36:10 -0700127 .build();
128 }
129
130 /**
131 * Read initial multicast from mcast store.
132 */
Charles Chanfc5c7802016-05-17 13:13:55 -0700133 protected void init() {
Charles Chan2199c302016-04-23 17:36:10 -0700134 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 Chand55e84d2016-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();
Charles Chand55e84d2016-03-30 17:54:24 -0700198
Charles Chan1588e7b2016-06-28 16:50:13 -0700199 // Continue only when this instance is the master of source device
200 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
201 log.info("Skip {} due to lack of mastership of the source device {}",
202 mcastIp, source.deviceId());
203 return;
204 }
205
Charles Chand55e84d2016-03-30 17:54:24 -0700206 // When source and sink are on the same device
207 if (source.deviceId().equals(sink.deviceId())) {
208 // Source and sink are on even the same port. There must be something wrong.
209 if (source.port().equals(sink.port())) {
210 log.warn("Sink is on the same port of source. Abort");
211 return;
212 }
Charles Chan8d449862016-05-16 18:44:13 -0700213 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chand55e84d2016-03-30 17:54:24 -0700214 return;
215 }
216
217 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700218 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700219 if (isLast) {
220 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
221 }
Charles Chand55e84d2016-03-30 17:54:24 -0700222
223 // If this is the last sink on the device, also update upstream
224 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
225 if (mcastPath.isPresent()) {
226 List<Link> links = Lists.newArrayList(mcastPath.get().links());
227 Collections.reverse(links);
228 for (Link link : links) {
229 if (isLast) {
230 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
Charles Chan8d449862016-05-16 18:44:13 -0700231 mcastIp,
232 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan2199c302016-04-23 17:36:10 -0700233 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chand55e84d2016-03-30 17:54:24 -0700234 }
235 }
236 }
237 }
238
239 /**
240 * Establishes a path from source to sink for given multicast group.
241 *
242 * @param source connect point of the multicast source
243 * @param sink connection point of the multicast sink
244 * @param mcastIp multicast group IP address
245 */
246 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
247 IpAddress mcastIp) {
Charles Chan1588e7b2016-06-28 16:50:13 -0700248 // Continue only when this instance is the master of source device
249 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
250 log.info("Skip {} due to lack of mastership of the source device {}",
251 source.deviceId());
252 return;
253 }
254
Charles Chan2199c302016-04-23 17:36:10 -0700255 // Process the ingress device
Charles Chan8d449862016-05-16 18:44:13 -0700256 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700257
Charles Chand55e84d2016-03-30 17:54:24 -0700258 // When source and sink are on the same device
259 if (source.deviceId().equals(sink.deviceId())) {
260 // Source and sink are on even the same port. There must be something wrong.
261 if (source.port().equals(sink.port())) {
262 log.warn("Sink is on the same port of source. Abort");
263 return;
264 }
Charles Chan8d449862016-05-16 18:44:13 -0700265 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700266 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chand55e84d2016-03-30 17:54:24 -0700267 return;
268 }
269
Charles Chand55e84d2016-03-30 17:54:24 -0700270 // Find a path. If present, create/update groups and flows for each hop
271 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
272 if (mcastPath.isPresent()) {
Charles Chan2199c302016-04-23 17:36:10 -0700273 List<Link> links = mcastPath.get().links();
274 checkState(links.size() == 2,
275 "Path in leaf-spine topology should always be two hops: ", links);
276
277 links.forEach(link -> {
Charles Chan8d449862016-05-16 18:44:13 -0700278 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
279 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
280 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null));
Charles Chand55e84d2016-03-30 17:54:24 -0700281 });
Charles Chan2199c302016-04-23 17:36:10 -0700282
Charles Chand55e84d2016-03-30 17:54:24 -0700283 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700284 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700285
286 // Setup mcast roles
287 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
288 McastRole.INGRESS);
289 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
290 McastRole.TRANSIT);
291 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
292 McastRole.EGRESS);
293 } else {
294 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
295 source.deviceId(), sink.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700296 }
297 }
298
299 /**
Charles Chan2199c302016-04-23 17:36:10 -0700300 * Processes the LINK_DOWN event.
301 *
302 * @param affectedLink Link that is going down
303 */
304 protected void processLinkDown(Link affectedLink) {
Charles Chan2199c302016-04-23 17:36:10 -0700305 getAffectedGroups(affectedLink).forEach(mcastIp -> {
306 // Find out the ingress, transit and egress device of affected group
307 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
308 .stream().findAny().orElse(null);
309 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
310 .stream().findAny().orElse(null);
311 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
Charles Chan8d449862016-05-16 18:44:13 -0700312 ConnectPoint source = getSource(mcastIp);
313
314 // Do not proceed if any of these info is missing
315 if (ingressDevice == null || transitDevice == null
316 || egressDevices == null || source == null) {
317 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
318 ingressDevice, transitDevice, egressDevices, source);
Charles Chan2199c302016-04-23 17:36:10 -0700319 return;
320 }
321
Charles Chan1588e7b2016-06-28 16:50:13 -0700322 // Continue only when this instance is the master of source device
323 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
324 log.info("Skip {} due to lack of mastership of the source device {}",
325 source.deviceId());
326 return;
327 }
328
Charles Chan2199c302016-04-23 17:36:10 -0700329 // Remove entire transit
Charles Chan8d449862016-05-16 18:44:13 -0700330 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700331
332 // Remove transit-facing port on ingress device
333 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
334 if (ingressTransitPort != null) {
Charles Chan8d449862016-05-16 18:44:13 -0700335 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700336 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
337 }
338
339 // Construct a new path for each egress device
340 egressDevices.forEach(egressDevice -> {
341 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
342 if (mcastPath.isPresent()) {
343 List<Link> links = mcastPath.get().links();
344 links.forEach(link -> {
Charles Chan8d449862016-05-16 18:44:13 -0700345 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
346 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
347 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700348 });
349 // Setup new transit mcast role
350 mcastRoleStore.put(new McastStoreKey(mcastIp,
351 links.get(0).dst().deviceId()), McastRole.TRANSIT);
352 } else {
353 log.warn("Fail to recover egress device {} from link failure {}",
354 egressDevice, affectedLink);
Charles Chan8d449862016-05-16 18:44:13 -0700355 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700356 }
357 });
358 });
359 }
360
361 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700362 * Adds filtering objective for given device and port.
363 *
364 * @param deviceId device ID
365 * @param port ingress port number
366 * @param assignedVlan assigned VLAN ID
367 */
368 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
369 // Do nothing if the port is configured as suppressed
Charles Chan6ea94fc2016-05-10 17:29:47 -0700370 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
371 SegmentRoutingAppConfig appConfig = srManager.cfgService
372 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
373 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
374 log.info("Ignore suppressed port {}", connectPoint);
Charles Chand55e84d2016-03-30 17:54:24 -0700375 return;
376 }
377
378 FilteringObjective.Builder filtObjBuilder =
Charles Chan59cc16d2017-02-02 16:20:42 -0800379 filterObjBuilder(deviceId, port, assignedVlan);
Charles Chan2199c302016-04-23 17:36:10 -0700380 ObjectiveContext context = new DefaultObjectiveContext(
381 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800382 deviceId, port.toLong(), assignedVlan),
Charles Chan2199c302016-04-23 17:36:10 -0700383 (objective, error) ->
384 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800385 deviceId, port.toLong(), assignedVlan, error));
Charles Chan2199c302016-04-23 17:36:10 -0700386 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700387 }
388
389 /**
390 * Adds a port to given multicast group on given device. This involves the
391 * update of L3 multicast group and multicast routing table entry.
392 *
393 * @param deviceId device ID
394 * @param port port to be added
395 * @param mcastIp multicast group
396 * @param assignedVlan assigned VLAN ID
397 */
398 private void addPortToDevice(DeviceId deviceId, PortNumber port,
399 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700400 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700401 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan2199c302016-04-23 17:36:10 -0700402 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700403 // First time someone request this mcast group via this device
404 portBuilder.add(port);
405 } else {
406 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700407 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700408 // Stop if the port is already in the nextobj
409 Set<PortNumber> existingPorts = getPorts(nextObj.next());
410 if (existingPorts.contains(port)) {
411 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
412 return;
413 }
414 portBuilder.addAll(existingPorts).add(port).build();
415 }
416 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700417 ObjectiveContext context = new DefaultObjectiveContext(
418 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
419 mcastIp, deviceId, port.toLong(), assignedVlan),
420 (objective, error) ->
421 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
422 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700423 NextObjective newNextObj =
424 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
425 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700426 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
427 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700428 srManager.flowObjectiveService.next(deviceId, newNextObj);
429 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700430 }
431
432 /**
433 * Removes a port from given multicast group on given device.
434 * This involves the update of L3 multicast group and multicast routing
435 * table entry.
436 *
437 * @param deviceId device ID
438 * @param port port to be added
439 * @param mcastIp multicast group
440 * @param assignedVlan assigned VLAN ID
441 * @return true if this is the last sink on this device
442 */
443 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
444 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700445 McastStoreKey mcastStoreKey =
446 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700447 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700448 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700449 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
450 return false;
451 }
Charles Chan2199c302016-04-23 17:36:10 -0700452 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700453
454 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700455 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700456 if (!existingPorts.contains(port)) {
457 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
458 return false;
459 }
460 // Copy and modify the ImmutableSet
461 existingPorts = Sets.newHashSet(existingPorts);
462 existingPorts.remove(port);
463
464 NextObjective newNextObj;
465 ForwardingObjective fwdObj;
466 if (existingPorts.isEmpty()) {
467 // If this is the last sink, remove flows and groups
468 // NOTE: Rely on GroupStore garbage collection rather than explicitly
469 // remove L3MG since there might be other flows/groups refer to
470 // the same L2IG
Charles Chan2199c302016-04-23 17:36:10 -0700471 ObjectiveContext context = new DefaultObjectiveContext(
472 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
473 mcastIp, deviceId, port.toLong(), assignedVlan),
474 (objective, error) ->
475 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
476 mcastIp, deviceId, port.toLong(), assignedVlan, error));
477 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
478 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700479 srManager.flowObjectiveService.forward(deviceId, fwdObj);
480 } else {
481 // If this is not the last sink, update flows and groups
Charles Chan2199c302016-04-23 17:36:10 -0700482 ObjectiveContext context = new DefaultObjectiveContext(
483 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
484 mcastIp, deviceId, port.toLong(), assignedVlan),
485 (objective, error) ->
486 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
487 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700488 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chanfc5c7802016-05-17 13:13:55 -0700489 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan2199c302016-04-23 17:36:10 -0700490 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700491 srManager.flowObjectiveService.next(deviceId, newNextObj);
492 srManager.flowObjectiveService.forward(deviceId, fwdObj);
493 }
Charles Chand55e84d2016-03-30 17:54:24 -0700494 return existingPorts.isEmpty();
495 }
496
Charles Chan2199c302016-04-23 17:36:10 -0700497
498 /**
499 * Removes entire group on given device.
500 *
501 * @param deviceId device ID
502 * @param mcastIp multicast group to be removed
503 * @param assignedVlan assigned VLAN ID
504 */
505 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
506 VlanId assignedVlan) {
507 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
508 // This device is not serving this multicast group
509 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
510 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
511 return;
512 }
513 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
514 // NOTE: Rely on GroupStore garbage collection rather than explicitly
515 // remove L3MG since there might be other flows/groups refer to
516 // the same L2IG
517 ObjectiveContext context = new DefaultObjectiveContext(
518 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
519 mcastIp, deviceId, assignedVlan),
520 (objective, error) ->
521 log.warn("Failed to remove {} on {}, vlan {}: {}",
522 mcastIp, deviceId, assignedVlan, error));
523 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
524 srManager.flowObjectiveService.forward(deviceId, fwdObj);
525 mcastNextObjStore.remove(mcastStoreKey);
526 mcastRoleStore.remove(mcastStoreKey);
527 }
528
529 /**
530 * Remove all groups on given device.
531 *
532 * @param deviceId device ID
533 */
534 public void removeDevice(DeviceId deviceId) {
Charles Chana061fb3f2016-06-17 14:28:07 -0700535 mcastNextObjStore.entrySet().stream()
536 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
537 .forEach(entry -> {
538 ConnectPoint source = getSource(entry.getKey().mcastIp());
539 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(),
540 assignedVlan(deviceId.equals(source.deviceId()) ? source : null));
541 mcastNextObjStore.remove(entry.getKey());
542 });
543 log.debug("{} is removed from mcastNextObjStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700544
Charles Chana061fb3f2016-06-17 14:28:07 -0700545 mcastRoleStore.entrySet().stream()
546 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
547 .forEach(entry -> {
548 mcastRoleStore.remove(entry.getKey());
549 });
550 log.debug("{} is removed from mcastRoleStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700551 }
552
Charles Chand55e84d2016-03-30 17:54:24 -0700553 /**
554 * Creates a next objective builder for multicast.
555 *
556 * @param mcastIp multicast group
557 * @param assignedVlan assigned VLAN ID
558 * @param outPorts set of output port numbers
559 * @return next objective builder
560 */
561 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
562 VlanId assignedVlan, Set<PortNumber> outPorts) {
563 int nextId = srManager.flowObjectiveService.allocateNextId();
564
565 TrafficSelector metadata =
566 DefaultTrafficSelector.builder()
567 .matchVlanId(assignedVlan)
568 .matchIPDst(mcastIp.toIpPrefix())
569 .build();
570
571 NextObjective.Builder nextObjBuilder = DefaultNextObjective
572 .builder().withId(nextId)
573 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
574 .withMeta(metadata);
575
576 outPorts.forEach(port -> {
577 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
578 if (egressVlan().equals(VlanId.NONE)) {
579 tBuilder.popVlan();
580 }
581 tBuilder.setOutput(port);
582 nextObjBuilder.addTreatment(tBuilder.build());
583 });
584
585 return nextObjBuilder;
586 }
587
588 /**
589 * Creates a forwarding objective builder for multicast.
590 *
591 * @param mcastIp multicast group
592 * @param assignedVlan assigned VLAN ID
593 * @param nextId next ID of the L3 multicast group
594 * @return forwarding objective builder
595 */
596 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
597 VlanId assignedVlan, int nextId) {
598 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
599 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
600 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
601 sbuilder.matchIPDst(mcastPrefix);
602 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
603 metabuilder.matchVlanId(assignedVlan);
604
605 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
606 fwdBuilder.withSelector(sbuilder.build())
607 .withMeta(metabuilder.build())
608 .nextStep(nextId)
609 .withFlag(ForwardingObjective.Flag.SPECIFIC)
610 .fromApp(srManager.appId)
611 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
612 return fwdBuilder;
613 }
614
615 /**
616 * Creates a filtering objective builder for multicast.
617 *
618 * @param deviceId Device ID
619 * @param ingressPort ingress port of the multicast stream
620 * @param assignedVlan assigned VLAN ID
621 * @return filtering objective builder
622 */
623 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
624 VlanId assignedVlan) {
625 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
626 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
627 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
628 MacAddress.IPV4_MULTICAST_MASK))
629 .addCondition(Criteria.matchVlanId(egressVlan()))
630 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
Charles Chan1588e7b2016-06-28 16:50:13 -0700631
632 TrafficTreatment tt = DefaultTrafficTreatment.builder()
633 .pushVlan().setVlanId(assignedVlan).build();
634 filtBuilder.withMeta(tt);
635
Charles Chand55e84d2016-03-30 17:54:24 -0700636 return filtBuilder.permit().fromApp(srManager.appId);
637 }
638
639 /**
640 * Gets output ports information from treatments.
641 *
642 * @param treatments collection of traffic treatments
643 * @return set of output port numbers
644 */
645 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
646 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
647 treatments.forEach(treatment -> {
648 treatment.allInstructions().stream()
649 .filter(instr -> instr instanceof OutputInstruction)
650 .forEach(instr -> {
651 builder.add(((OutputInstruction) instr).port());
652 });
653 });
654 return builder.build();
655 }
656
657 /**
658 * Gets a path from src to dst.
659 * If a path was allocated before, returns the allocated path.
660 * Otherwise, randomly pick one from available paths.
661 *
662 * @param src source device ID
663 * @param dst destination device ID
664 * @param mcastIp multicast group
665 * @return an optional path from src to dst
666 */
667 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
668 List<Path> allPaths = Lists.newArrayList(
669 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700670 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700671 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700672 return Optional.empty();
673 }
674
675 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700676 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
677 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
678 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700679 Set<PortNumber> existingPorts = getPorts(nextObj.next());
680 for (Path path : allPaths) {
681 PortNumber srcPort = path.links().get(0).src().port();
682 if (existingPorts.contains(srcPort)) {
683 return Optional.of(path);
684 }
685 }
686 }
687 // Otherwise, randomly pick a path
688 Collections.shuffle(allPaths);
689 return allPaths.stream().findFirst();
690 }
691
692 /**
Charles Chan2199c302016-04-23 17:36:10 -0700693 * Gets device(s) of given role in given multicast group.
694 *
695 * @param mcastIp multicast IP
696 * @param role multicast role
697 * @return set of device ID or empty set if not found
698 */
699 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
700 return mcastRoleStore.entrySet().stream()
701 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
702 entry.getValue().value() == role)
703 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
704 .collect(Collectors.toSet());
705 }
706
707 /**
Charles Chan8d449862016-05-16 18:44:13 -0700708 * Gets source connect point of given multicast group.
709 *
710 * @param mcastIp multicast IP
711 * @return source connect point or null if not found
712 */
713 private ConnectPoint getSource(IpAddress mcastIp) {
714 return srManager.multicastRouteService.getRoutes().stream()
715 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
716 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
717 .findAny().orElse(null);
718 }
719
720 /**
Charles Chan2199c302016-04-23 17:36:10 -0700721 * Gets groups which is affected by the link down event.
722 *
723 * @param link link going down
724 * @return a set of multicast IpAddress
725 */
726 private Set<IpAddress> getAffectedGroups(Link link) {
727 DeviceId deviceId = link.src().deviceId();
728 PortNumber port = link.src().port();
729 return mcastNextObjStore.entrySet().stream()
730 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
731 getPorts(entry.getValue().value().next()).contains(port))
732 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
733 .collect(Collectors.toSet());
734 }
735
736 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700737 * Gets egress VLAN from McastConfig.
738 *
739 * @return egress VLAN or VlanId.NONE if not configured
740 */
741 private VlanId egressVlan() {
742 McastConfig mcastConfig =
743 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
744 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
745 }
746
747 /**
748 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chan8d449862016-05-16 18:44:13 -0700749 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chand55e84d2016-03-30 17:54:24 -0700750 *
Charles Chan8d449862016-05-16 18:44:13 -0700751 * @param cp connect point; Can be null if not specified
752 * @return assigned VLAN ID
Charles Chand55e84d2016-03-30 17:54:24 -0700753 */
Charles Chan8d449862016-05-16 18:44:13 -0700754 private VlanId assignedVlan(ConnectPoint cp) {
755 // Use the egressVlan if it is tagged
756 if (!egressVlan().equals(VlanId.NONE)) {
757 return egressVlan();
758 }
759 // Reuse unicast VLAN if the port has subnet configured
760 if (cp != null) {
Charles Chan59cc16d2017-02-02 16:20:42 -0800761 VlanId untaggedVlan = srManager.getUntaggedVlanId(cp);
762 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chan8d449862016-05-16 18:44:13 -0700763 }
Charles Chan59cc16d2017-02-02 16:20:42 -0800764 // Use DEFAULT_VLAN if none of the above matches
765 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -0700766 }
Charles Chan2199c302016-04-23 17:36:10 -0700767
768 /**
769 * Gets the spine-facing port on ingress device of given multicast group.
770 *
771 * @param mcastIp multicast IP
772 * @return spine-facing port on ingress device
773 */
774 private PortNumber ingressTransitPort(IpAddress mcastIp) {
775 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
776 .stream().findAny().orElse(null);
777 if (ingressDevice != null) {
778 NextObjective nextObj = mcastNextObjStore
779 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
780 Set<PortNumber> ports = getPorts(nextObj.next());
781
782 for (PortNumber port : ports) {
783 // Spine-facing port should have no subnet and no xconnect
784 if (srManager.deviceConfiguration != null &&
Pier Ventre10bd8d12016-11-26 21:05:22 -0800785 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chanfc5c7802016-05-17 13:13:55 -0700786 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan2199c302016-04-23 17:36:10 -0700787 return port;
788 }
789 }
790 }
791 return null;
792 }
Charles Chand55e84d2016-03-30 17:54:24 -0700793}