blob: f95addc54bce88bc83f0f3cccd058e37f7b58d50 [file] [log] [blame]
Charles Chanc91c8782016-03-30 17:54:24 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Sets;
22import org.onlab.packet.Ethernet;
Charles Chan779fd062016-05-11 20:39:35 -070023import org.onlab.packet.Ip4Prefix;
Charles Chanc91c8782016-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 Chan72779502016-04-23 17:36:10 -070046import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070047import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
Charles Chan72779502016-04-23 17:36:10 -070050import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chanc91c8782016-03-30 17:54:24 -070051import org.onosproject.net.mcast.McastEvent;
52import org.onosproject.net.mcast.McastRouteInfo;
53import org.onosproject.net.topology.TopologyService;
Charles Chan370a65b2016-05-10 17:29:47 -070054import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan72779502016-04-23 17:36:10 -070055import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chanc91c8782016-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;
60import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.util.Collection;
64import java.util.Collections;
65import java.util.List;
Charles Chan72779502016-04-23 17:36:10 -070066import java.util.Map;
Charles Chanc91c8782016-03-30 17:54:24 -070067import java.util.Optional;
68import java.util.Set;
Charles Chan72779502016-04-23 17:36:10 -070069import java.util.stream.Collectors;
70
71import static com.google.common.base.Preconditions.checkState;
Charles Chanc91c8782016-03-30 17:54:24 -070072
73/**
Charles Chan1eaf4802016-04-18 13:44:03 -070074 * Handles multicast-related events.
Charles Chanc91c8782016-03-30 17:54:24 -070075 */
Charles Chan1eaf4802016-04-18 13:44:03 -070076public class McastHandler {
77 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chanc91c8782016-03-30 17:54:24 -070078 private final SegmentRoutingManager srManager;
79 private final ApplicationId coreAppId;
Charles Chan82f19972016-05-17 13:13:55 -070080 private final StorageService storageService;
81 private final TopologyService topologyService;
Charles Chan72779502016-04-23 17:36:10 -070082 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
83 private final KryoNamespace.Builder mcastKryo;
84 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
85
86 /**
87 * Role in the multicast tree.
88 */
89 public enum McastRole {
90 /**
91 * The device is the ingress device of this group.
92 */
93 INGRESS,
94 /**
95 * The device is the transit device of this group.
96 */
97 TRANSIT,
98 /**
99 * The device is the egress device of this group.
100 */
101 EGRESS
102 }
Charles Chanc91c8782016-03-30 17:54:24 -0700103
104 /**
105 * Constructs the McastEventHandler.
106 *
107 * @param srManager Segment Routing manager
108 */
Charles Chan1eaf4802016-04-18 13:44:03 -0700109 public McastHandler(SegmentRoutingManager srManager) {
Charles Chanc91c8782016-03-30 17:54:24 -0700110 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chanc91c8782016-03-30 17:54:24 -0700111 this.srManager = srManager;
112 this.storageService = srManager.storageService;
113 this.topologyService = srManager.topologyService;
Charles Chan72779502016-04-23 17:36:10 -0700114 mcastKryo = new KryoNamespace.Builder()
Charles Chanc91c8782016-03-30 17:54:24 -0700115 .register(KryoNamespaces.API)
Charles Chan72779502016-04-23 17:36:10 -0700116 .register(McastStoreKey.class)
117 .register(McastRole.class);
Charles Chanc91c8782016-03-30 17:54:24 -0700118 mcastNextObjStore = storageService
Charles Chan72779502016-04-23 17:36:10 -0700119 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chanc91c8782016-03-30 17:54:24 -0700120 .withName("onos-mcast-nextobj-store")
Charles Chan4922a172016-05-23 16:45:45 -0700121 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chanc91c8782016-03-30 17:54:24 -0700122 .build();
Charles Chan72779502016-04-23 17:36:10 -0700123 mcastRoleStore = storageService
124 .<McastStoreKey, McastRole>consistentMapBuilder()
125 .withName("onos-mcast-role-store")
Charles Chan4922a172016-05-23 16:45:45 -0700126 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan72779502016-04-23 17:36:10 -0700127 .build();
128 }
129
130 /**
131 * Read initial multicast from mcast store.
132 */
Charles Chan82f19972016-05-17 13:13:55 -0700133 protected void init() {
Charles Chan72779502016-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 Chanc91c8782016-03-30 17:54:24 -0700141 }
142
143 /**
144 * Processes the SOURCE_ADDED event.
145 *
146 * @param event McastEvent with SOURCE_ADDED type
147 */
148 protected void processSourceAdded(McastEvent event) {
149 log.info("processSourceAdded {}", event);
150 McastRouteInfo mcastRouteInfo = event.subject();
151 if (!mcastRouteInfo.isComplete()) {
152 log.info("Incompleted McastRouteInfo. Abort.");
153 return;
154 }
155 ConnectPoint source = mcastRouteInfo.source().orElse(null);
156 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
157 IpAddress mcastIp = mcastRouteInfo.route().group();
158
159 sinks.forEach(sink -> {
160 processSinkAddedInternal(source, sink, mcastIp);
161 });
162 }
163
164 /**
165 * Processes the SINK_ADDED event.
166 *
167 * @param event McastEvent with SINK_ADDED type
168 */
169 protected void processSinkAdded(McastEvent event) {
170 log.info("processSinkAdded {}", event);
171 McastRouteInfo mcastRouteInfo = event.subject();
172 if (!mcastRouteInfo.isComplete()) {
173 log.info("Incompleted McastRouteInfo. Abort.");
174 return;
175 }
176 ConnectPoint source = mcastRouteInfo.source().orElse(null);
177 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
178 IpAddress mcastIp = mcastRouteInfo.route().group();
179
180 processSinkAddedInternal(source, sink, mcastIp);
181 }
182
183 /**
184 * Processes the SINK_REMOVED event.
185 *
186 * @param event McastEvent with SINK_REMOVED type
187 */
188 protected void processSinkRemoved(McastEvent event) {
189 log.info("processSinkRemoved {}", event);
190 McastRouteInfo mcastRouteInfo = event.subject();
191 if (!mcastRouteInfo.isComplete()) {
192 log.info("Incompleted McastRouteInfo. Abort.");
193 return;
194 }
195 ConnectPoint source = mcastRouteInfo.source().orElse(null);
196 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
197 IpAddress mcastIp = mcastRouteInfo.route().group();
Charles Chanc91c8782016-03-30 17:54:24 -0700198
Charles Chan0932eca2016-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 Chanc91c8782016-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 Chana8f9dee2016-05-16 18:44:13 -0700213 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chanc91c8782016-03-30 17:54:24 -0700214 return;
215 }
216
217 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700218 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700219 if (isLast) {
220 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
221 }
Charles Chanc91c8782016-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 Chana8f9dee2016-05-16 18:44:13 -0700231 mcastIp,
232 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan72779502016-04-23 17:36:10 -0700233 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chanc91c8782016-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 Chan0932eca2016-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 Chan72779502016-04-23 17:36:10 -0700255 // Process the ingress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700256 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700257
Charles Chanc91c8782016-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 Chana8f9dee2016-05-16 18:44:13 -0700265 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan72779502016-04-23 17:36:10 -0700266 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chanc91c8782016-03-30 17:54:24 -0700267 return;
268 }
269
Charles Chanc91c8782016-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 Chan72779502016-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 Chana8f9dee2016-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 Chanc91c8782016-03-30 17:54:24 -0700281 });
Charles Chan72779502016-04-23 17:36:10 -0700282
Charles Chanc91c8782016-03-30 17:54:24 -0700283 // Process the egress device
Charles Chana8f9dee2016-05-16 18:44:13 -0700284 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan72779502016-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 Chanc91c8782016-03-30 17:54:24 -0700296 }
297 }
298
299 /**
Charles Chan72779502016-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 Chan72779502016-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 Chana8f9dee2016-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 Chan72779502016-04-23 17:36:10 -0700319 return;
320 }
321
Charles Chan0932eca2016-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 Chan72779502016-04-23 17:36:10 -0700329 // Remove entire transit
Charles Chana8f9dee2016-05-16 18:44:13 -0700330 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700331
332 // Remove transit-facing port on ingress device
333 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
334 if (ingressTransitPort != null) {
Charles Chana8f9dee2016-05-16 18:44:13 -0700335 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan72779502016-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 Chana8f9dee2016-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 Chan72779502016-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 Chana8f9dee2016-05-16 18:44:13 -0700355 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan72779502016-04-23 17:36:10 -0700356 }
357 });
358 });
359 }
360
361 /**
Charles Chanc91c8782016-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 Chan370a65b2016-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 Chanc91c8782016-03-30 17:54:24 -0700375 return;
376 }
377
Charles Chan779fd062016-05-11 20:39:35 -0700378 // Reuse unicast VLAN if the port has subnet configured
Pier Ventreb6a7f342016-11-26 21:05:22 -0800379 Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortIPv4Subnet(deviceId, port);
Charles Chan779fd062016-05-11 20:39:35 -0700380 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
381 final VlanId finalVlanId = (unicastVlan != null) ? unicastVlan : assignedVlan;
382
Charles Chanc91c8782016-03-30 17:54:24 -0700383 FilteringObjective.Builder filtObjBuilder =
Charles Chan779fd062016-05-11 20:39:35 -0700384 filterObjBuilder(deviceId, port, finalVlanId);
Charles Chan72779502016-04-23 17:36:10 -0700385 ObjectiveContext context = new DefaultObjectiveContext(
386 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan779fd062016-05-11 20:39:35 -0700387 deviceId, port.toLong(), finalVlanId),
Charles Chan72779502016-04-23 17:36:10 -0700388 (objective, error) ->
389 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan779fd062016-05-11 20:39:35 -0700390 deviceId, port.toLong(), finalVlanId, error));
Charles Chan72779502016-04-23 17:36:10 -0700391 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chanc91c8782016-03-30 17:54:24 -0700392 }
393
394 /**
395 * Adds a port to given multicast group on given device. This involves the
396 * update of L3 multicast group and multicast routing table entry.
397 *
398 * @param deviceId device ID
399 * @param port port to be added
400 * @param mcastIp multicast group
401 * @param assignedVlan assigned VLAN ID
402 */
403 private void addPortToDevice(DeviceId deviceId, PortNumber port,
404 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700405 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700406 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan72779502016-04-23 17:36:10 -0700407 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700408 // First time someone request this mcast group via this device
409 portBuilder.add(port);
410 } else {
411 // This device already serves some subscribers of this mcast group
Charles Chan72779502016-04-23 17:36:10 -0700412 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700413 // Stop if the port is already in the nextobj
414 Set<PortNumber> existingPorts = getPorts(nextObj.next());
415 if (existingPorts.contains(port)) {
416 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
417 return;
418 }
419 portBuilder.addAll(existingPorts).add(port).build();
420 }
421 // Create, store and apply the new nextObj and fwdObj
Charles Chan72779502016-04-23 17:36:10 -0700422 ObjectiveContext context = new DefaultObjectiveContext(
423 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
424 mcastIp, deviceId, port.toLong(), assignedVlan),
425 (objective, error) ->
426 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
427 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700428 NextObjective newNextObj =
429 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
430 ForwardingObjective fwdObj =
Charles Chan72779502016-04-23 17:36:10 -0700431 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
432 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700433 srManager.flowObjectiveService.next(deviceId, newNextObj);
434 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700435 }
436
437 /**
438 * Removes a port from given multicast group on given device.
439 * This involves the update of L3 multicast group and multicast routing
440 * table entry.
441 *
442 * @param deviceId device ID
443 * @param port port to be added
444 * @param mcastIp multicast group
445 * @param assignedVlan assigned VLAN ID
446 * @return true if this is the last sink on this device
447 */
448 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
449 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan72779502016-04-23 17:36:10 -0700450 McastStoreKey mcastStoreKey =
451 new McastStoreKey(mcastIp, deviceId);
Charles Chanc91c8782016-03-30 17:54:24 -0700452 // This device is not serving this multicast group
Charles Chan72779502016-04-23 17:36:10 -0700453 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chanc91c8782016-03-30 17:54:24 -0700454 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
455 return false;
456 }
Charles Chan72779502016-04-23 17:36:10 -0700457 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700458
459 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan72779502016-04-23 17:36:10 -0700460 // This port does not serve this multicast group
Charles Chanc91c8782016-03-30 17:54:24 -0700461 if (!existingPorts.contains(port)) {
462 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
463 return false;
464 }
465 // Copy and modify the ImmutableSet
466 existingPorts = Sets.newHashSet(existingPorts);
467 existingPorts.remove(port);
468
469 NextObjective newNextObj;
470 ForwardingObjective fwdObj;
471 if (existingPorts.isEmpty()) {
472 // If this is the last sink, remove flows and groups
473 // NOTE: Rely on GroupStore garbage collection rather than explicitly
474 // remove L3MG since there might be other flows/groups refer to
475 // the same L2IG
Charles Chan72779502016-04-23 17:36:10 -0700476 ObjectiveContext context = new DefaultObjectiveContext(
477 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
478 mcastIp, deviceId, port.toLong(), assignedVlan),
479 (objective, error) ->
480 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
481 mcastIp, deviceId, port.toLong(), assignedVlan, error));
482 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
483 mcastNextObjStore.remove(mcastStoreKey);
Charles Chanc91c8782016-03-30 17:54:24 -0700484 srManager.flowObjectiveService.forward(deviceId, fwdObj);
485 } else {
486 // If this is not the last sink, update flows and groups
Charles Chan72779502016-04-23 17:36:10 -0700487 ObjectiveContext context = new DefaultObjectiveContext(
488 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
489 mcastIp, deviceId, port.toLong(), assignedVlan),
490 (objective, error) ->
491 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
492 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chanc91c8782016-03-30 17:54:24 -0700493 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chan82f19972016-05-17 13:13:55 -0700494 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan72779502016-04-23 17:36:10 -0700495 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chanc91c8782016-03-30 17:54:24 -0700496 srManager.flowObjectiveService.next(deviceId, newNextObj);
497 srManager.flowObjectiveService.forward(deviceId, fwdObj);
498 }
Charles Chanc91c8782016-03-30 17:54:24 -0700499 return existingPorts.isEmpty();
500 }
501
Charles Chan72779502016-04-23 17:36:10 -0700502
503 /**
504 * Removes entire group on given device.
505 *
506 * @param deviceId device ID
507 * @param mcastIp multicast group to be removed
508 * @param assignedVlan assigned VLAN ID
509 */
510 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
511 VlanId assignedVlan) {
512 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
513 // This device is not serving this multicast group
514 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
515 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
516 return;
517 }
518 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
519 // NOTE: Rely on GroupStore garbage collection rather than explicitly
520 // remove L3MG since there might be other flows/groups refer to
521 // the same L2IG
522 ObjectiveContext context = new DefaultObjectiveContext(
523 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
524 mcastIp, deviceId, assignedVlan),
525 (objective, error) ->
526 log.warn("Failed to remove {} on {}, vlan {}: {}",
527 mcastIp, deviceId, assignedVlan, error));
528 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
529 srManager.flowObjectiveService.forward(deviceId, fwdObj);
530 mcastNextObjStore.remove(mcastStoreKey);
531 mcastRoleStore.remove(mcastStoreKey);
532 }
533
534 /**
535 * Remove all groups on given device.
536 *
537 * @param deviceId device ID
538 */
539 public void removeDevice(DeviceId deviceId) {
Charles Chanfc115892016-06-17 14:28:07 -0700540 mcastNextObjStore.entrySet().stream()
541 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
542 .forEach(entry -> {
543 ConnectPoint source = getSource(entry.getKey().mcastIp());
544 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(),
545 assignedVlan(deviceId.equals(source.deviceId()) ? source : null));
546 mcastNextObjStore.remove(entry.getKey());
547 });
548 log.debug("{} is removed from mcastNextObjStore", deviceId);
Charles Chan72779502016-04-23 17:36:10 -0700549
Charles Chanfc115892016-06-17 14:28:07 -0700550 mcastRoleStore.entrySet().stream()
551 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
552 .forEach(entry -> {
553 mcastRoleStore.remove(entry.getKey());
554 });
555 log.debug("{} is removed from mcastRoleStore", deviceId);
Charles Chan72779502016-04-23 17:36:10 -0700556 }
557
Charles Chanc91c8782016-03-30 17:54:24 -0700558 /**
559 * Creates a next objective builder for multicast.
560 *
561 * @param mcastIp multicast group
562 * @param assignedVlan assigned VLAN ID
563 * @param outPorts set of output port numbers
564 * @return next objective builder
565 */
566 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
567 VlanId assignedVlan, Set<PortNumber> outPorts) {
568 int nextId = srManager.flowObjectiveService.allocateNextId();
569
570 TrafficSelector metadata =
571 DefaultTrafficSelector.builder()
572 .matchVlanId(assignedVlan)
573 .matchIPDst(mcastIp.toIpPrefix())
574 .build();
575
576 NextObjective.Builder nextObjBuilder = DefaultNextObjective
577 .builder().withId(nextId)
578 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
579 .withMeta(metadata);
580
581 outPorts.forEach(port -> {
582 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
583 if (egressVlan().equals(VlanId.NONE)) {
584 tBuilder.popVlan();
585 }
586 tBuilder.setOutput(port);
587 nextObjBuilder.addTreatment(tBuilder.build());
588 });
589
590 return nextObjBuilder;
591 }
592
593 /**
594 * Creates a forwarding objective builder for multicast.
595 *
596 * @param mcastIp multicast group
597 * @param assignedVlan assigned VLAN ID
598 * @param nextId next ID of the L3 multicast group
599 * @return forwarding objective builder
600 */
601 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
602 VlanId assignedVlan, int nextId) {
603 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
604 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
605 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
606 sbuilder.matchIPDst(mcastPrefix);
607 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
608 metabuilder.matchVlanId(assignedVlan);
609
610 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
611 fwdBuilder.withSelector(sbuilder.build())
612 .withMeta(metabuilder.build())
613 .nextStep(nextId)
614 .withFlag(ForwardingObjective.Flag.SPECIFIC)
615 .fromApp(srManager.appId)
616 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
617 return fwdBuilder;
618 }
619
620 /**
621 * Creates a filtering objective builder for multicast.
622 *
623 * @param deviceId Device ID
624 * @param ingressPort ingress port of the multicast stream
625 * @param assignedVlan assigned VLAN ID
626 * @return filtering objective builder
627 */
628 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
629 VlanId assignedVlan) {
630 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
631 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
632 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
633 MacAddress.IPV4_MULTICAST_MASK))
634 .addCondition(Criteria.matchVlanId(egressVlan()))
635 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
Charles Chan0932eca2016-06-28 16:50:13 -0700636
637 TrafficTreatment tt = DefaultTrafficTreatment.builder()
638 .pushVlan().setVlanId(assignedVlan).build();
639 filtBuilder.withMeta(tt);
640
Charles Chanc91c8782016-03-30 17:54:24 -0700641 return filtBuilder.permit().fromApp(srManager.appId);
642 }
643
644 /**
645 * Gets output ports information from treatments.
646 *
647 * @param treatments collection of traffic treatments
648 * @return set of output port numbers
649 */
650 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
651 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
652 treatments.forEach(treatment -> {
653 treatment.allInstructions().stream()
654 .filter(instr -> instr instanceof OutputInstruction)
655 .forEach(instr -> {
656 builder.add(((OutputInstruction) instr).port());
657 });
658 });
659 return builder.build();
660 }
661
662 /**
663 * Gets a path from src to dst.
664 * If a path was allocated before, returns the allocated path.
665 * Otherwise, randomly pick one from available paths.
666 *
667 * @param src source device ID
668 * @param dst destination device ID
669 * @param mcastIp multicast group
670 * @return an optional path from src to dst
671 */
672 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
673 List<Path> allPaths = Lists.newArrayList(
674 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan72779502016-04-23 17:36:10 -0700675 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chanc91c8782016-03-30 17:54:24 -0700676 if (allPaths.isEmpty()) {
Charles Chanc91c8782016-03-30 17:54:24 -0700677 return Optional.empty();
678 }
679
680 // If one of the available path is used before, use the same path
Charles Chan72779502016-04-23 17:36:10 -0700681 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
682 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
683 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chanc91c8782016-03-30 17:54:24 -0700684 Set<PortNumber> existingPorts = getPorts(nextObj.next());
685 for (Path path : allPaths) {
686 PortNumber srcPort = path.links().get(0).src().port();
687 if (existingPorts.contains(srcPort)) {
688 return Optional.of(path);
689 }
690 }
691 }
692 // Otherwise, randomly pick a path
693 Collections.shuffle(allPaths);
694 return allPaths.stream().findFirst();
695 }
696
697 /**
Charles Chan72779502016-04-23 17:36:10 -0700698 * Gets device(s) of given role in given multicast group.
699 *
700 * @param mcastIp multicast IP
701 * @param role multicast role
702 * @return set of device ID or empty set if not found
703 */
704 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
705 return mcastRoleStore.entrySet().stream()
706 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
707 entry.getValue().value() == role)
708 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
709 .collect(Collectors.toSet());
710 }
711
712 /**
Charles Chana8f9dee2016-05-16 18:44:13 -0700713 * Gets source connect point of given multicast group.
714 *
715 * @param mcastIp multicast IP
716 * @return source connect point or null if not found
717 */
718 private ConnectPoint getSource(IpAddress mcastIp) {
719 return srManager.multicastRouteService.getRoutes().stream()
720 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
721 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
722 .findAny().orElse(null);
723 }
724
725 /**
Charles Chan72779502016-04-23 17:36:10 -0700726 * Gets groups which is affected by the link down event.
727 *
728 * @param link link going down
729 * @return a set of multicast IpAddress
730 */
731 private Set<IpAddress> getAffectedGroups(Link link) {
732 DeviceId deviceId = link.src().deviceId();
733 PortNumber port = link.src().port();
734 return mcastNextObjStore.entrySet().stream()
735 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
736 getPorts(entry.getValue().value().next()).contains(port))
737 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
738 .collect(Collectors.toSet());
739 }
740
741 /**
Charles Chanc91c8782016-03-30 17:54:24 -0700742 * Gets egress VLAN from McastConfig.
743 *
744 * @return egress VLAN or VlanId.NONE if not configured
745 */
746 private VlanId egressVlan() {
747 McastConfig mcastConfig =
748 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
749 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
750 }
751
752 /**
753 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chana8f9dee2016-05-16 18:44:13 -0700754 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chanc91c8782016-03-30 17:54:24 -0700755 *
Charles Chana8f9dee2016-05-16 18:44:13 -0700756 * @param cp connect point; Can be null if not specified
757 * @return assigned VLAN ID
Charles Chanc91c8782016-03-30 17:54:24 -0700758 */
Charles Chana8f9dee2016-05-16 18:44:13 -0700759 private VlanId assignedVlan(ConnectPoint cp) {
760 // Use the egressVlan if it is tagged
761 if (!egressVlan().equals(VlanId.NONE)) {
762 return egressVlan();
763 }
764 // Reuse unicast VLAN if the port has subnet configured
765 if (cp != null) {
766 Ip4Prefix portSubnet = srManager.deviceConfiguration
Pier Ventreb6a7f342016-11-26 21:05:22 -0800767 .getPortIPv4Subnet(cp.deviceId(), cp.port());
Charles Chana8f9dee2016-05-16 18:44:13 -0700768 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(cp.deviceId(), portSubnet);
769 if (unicastVlan != null) {
770 return unicastVlan;
771 }
772 }
773 // By default, use VLAN_NO_SUBNET
774 return VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET);
Charles Chanc91c8782016-03-30 17:54:24 -0700775 }
Charles Chan72779502016-04-23 17:36:10 -0700776
777 /**
778 * Gets the spine-facing port on ingress device of given multicast group.
779 *
780 * @param mcastIp multicast IP
781 * @return spine-facing port on ingress device
782 */
783 private PortNumber ingressTransitPort(IpAddress mcastIp) {
784 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
785 .stream().findAny().orElse(null);
786 if (ingressDevice != null) {
787 NextObjective nextObj = mcastNextObjStore
788 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
789 Set<PortNumber> ports = getPorts(nextObj.next());
790
791 for (PortNumber port : ports) {
792 // Spine-facing port should have no subnet and no xconnect
793 if (srManager.deviceConfiguration != null &&
Pier Ventreb6a7f342016-11-26 21:05:22 -0800794 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chan82f19972016-05-17 13:13:55 -0700795 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan72779502016-04-23 17:36:10 -0700796 return port;
797 }
798 }
799 }
800 return null;
801 }
Charles Chanc91c8782016-03-30 17:54:24 -0700802}