blob: 6f42cf61decab64f22415f07558f60da9bee55ba [file] [log] [blame]
Charles Chand55e84d2016-03-30 17:54:24 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Sets;
22import org.onlab.packet.Ethernet;
Charles Chanf0994cc2016-05-11 20:39:35 -070023import org.onlab.packet.Ip4Prefix;
Charles Chand55e84d2016-03-30 17:54:24 -070024import org.onlab.packet.IpAddress;
25import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onlab.util.KryoNamespace;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
31import org.onosproject.incubator.net.config.basics.McastConfig;
32import org.onosproject.net.ConnectPoint;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.Link;
35import org.onosproject.net.Path;
36import org.onosproject.net.PortNumber;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.flow.criteria.Criteria;
42import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070046import org.onosproject.net.flowobjective.DefaultObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070047import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
Charles Chan2199c302016-04-23 17:36:10 -070050import org.onosproject.net.flowobjective.ObjectiveContext;
Charles Chand55e84d2016-03-30 17:54:24 -070051import org.onosproject.net.mcast.McastEvent;
52import org.onosproject.net.mcast.McastRouteInfo;
53import org.onosproject.net.topology.TopologyService;
Charles Chan6ea94fc2016-05-10 17:29:47 -070054import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
Charles Chan2199c302016-04-23 17:36:10 -070055import org.onosproject.segmentrouting.storekey.McastStoreKey;
Charles Chand55e84d2016-03-30 17:54:24 -070056import org.onosproject.store.serializers.KryoNamespaces;
57import org.onosproject.store.service.ConsistentMap;
58import org.onosproject.store.service.Serializer;
59import org.onosproject.store.service.StorageService;
60import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.util.Collection;
64import java.util.Collections;
65import java.util.List;
Charles Chan2199c302016-04-23 17:36:10 -070066import java.util.Map;
Charles Chand55e84d2016-03-30 17:54:24 -070067import java.util.Optional;
68import java.util.Set;
Charles Chan2199c302016-04-23 17:36:10 -070069import java.util.stream.Collectors;
70
71import static com.google.common.base.Preconditions.checkState;
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
199 // When source and sink are on the same device
200 if (source.deviceId().equals(sink.deviceId())) {
201 // Source and sink are on even the same port. There must be something wrong.
202 if (source.port().equals(sink.port())) {
203 log.warn("Sink is on the same port of source. Abort");
204 return;
205 }
Charles Chan8d449862016-05-16 18:44:13 -0700206 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chand55e84d2016-03-30 17:54:24 -0700207 return;
208 }
209
210 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700211 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700212 if (isLast) {
213 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
214 }
Charles Chand55e84d2016-03-30 17:54:24 -0700215
216 // If this is the last sink on the device, also update upstream
217 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
218 if (mcastPath.isPresent()) {
219 List<Link> links = Lists.newArrayList(mcastPath.get().links());
220 Collections.reverse(links);
221 for (Link link : links) {
222 if (isLast) {
223 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
Charles Chan8d449862016-05-16 18:44:13 -0700224 mcastIp,
225 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan2199c302016-04-23 17:36:10 -0700226 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chand55e84d2016-03-30 17:54:24 -0700227 }
228 }
229 }
230 }
231
232 /**
233 * Establishes a path from source to sink for given multicast group.
234 *
235 * @param source connect point of the multicast source
236 * @param sink connection point of the multicast sink
237 * @param mcastIp multicast group IP address
238 */
239 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
240 IpAddress mcastIp) {
Charles Chan2199c302016-04-23 17:36:10 -0700241 // Process the ingress device
Charles Chan8d449862016-05-16 18:44:13 -0700242 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700243
Charles Chand55e84d2016-03-30 17:54:24 -0700244 // When source and sink are on the same device
245 if (source.deviceId().equals(sink.deviceId())) {
246 // Source and sink are on even the same port. There must be something wrong.
247 if (source.port().equals(sink.port())) {
248 log.warn("Sink is on the same port of source. Abort");
249 return;
250 }
Charles Chan8d449862016-05-16 18:44:13 -0700251 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700252 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chand55e84d2016-03-30 17:54:24 -0700253 return;
254 }
255
Charles Chand55e84d2016-03-30 17:54:24 -0700256 // Find a path. If present, create/update groups and flows for each hop
257 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
258 if (mcastPath.isPresent()) {
Charles Chan2199c302016-04-23 17:36:10 -0700259 List<Link> links = mcastPath.get().links();
260 checkState(links.size() == 2,
261 "Path in leaf-spine topology should always be two hops: ", links);
262
263 links.forEach(link -> {
Charles Chan8d449862016-05-16 18:44:13 -0700264 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
265 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
266 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null));
Charles Chand55e84d2016-03-30 17:54:24 -0700267 });
Charles Chan2199c302016-04-23 17:36:10 -0700268
Charles Chand55e84d2016-03-30 17:54:24 -0700269 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700270 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700271
272 // Setup mcast roles
273 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
274 McastRole.INGRESS);
275 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
276 McastRole.TRANSIT);
277 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
278 McastRole.EGRESS);
279 } else {
280 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
281 source.deviceId(), sink.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700282 }
283 }
284
285 /**
Charles Chan2199c302016-04-23 17:36:10 -0700286 * Processes the LINK_DOWN event.
287 *
288 * @param affectedLink Link that is going down
289 */
290 protected void processLinkDown(Link affectedLink) {
Charles Chan2199c302016-04-23 17:36:10 -0700291 getAffectedGroups(affectedLink).forEach(mcastIp -> {
292 // Find out the ingress, transit and egress device of affected group
293 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
294 .stream().findAny().orElse(null);
295 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
296 .stream().findAny().orElse(null);
297 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
Charles Chan8d449862016-05-16 18:44:13 -0700298 ConnectPoint source = getSource(mcastIp);
299
300 // Do not proceed if any of these info is missing
301 if (ingressDevice == null || transitDevice == null
302 || egressDevices == null || source == null) {
303 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
304 ingressDevice, transitDevice, egressDevices, source);
Charles Chan2199c302016-04-23 17:36:10 -0700305 return;
306 }
307
308 // Remove entire transit
Charles Chan8d449862016-05-16 18:44:13 -0700309 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700310
311 // Remove transit-facing port on ingress device
312 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
313 if (ingressTransitPort != null) {
Charles Chan8d449862016-05-16 18:44:13 -0700314 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700315 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 -> {
Charles Chan8d449862016-05-16 18:44:13 -0700324 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
325 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
326 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700327 });
328 // Setup new transit mcast role
329 mcastRoleStore.put(new McastStoreKey(mcastIp,
330 links.get(0).dst().deviceId()), McastRole.TRANSIT);
331 } else {
332 log.warn("Fail to recover egress device {} from link failure {}",
333 egressDevice, affectedLink);
Charles Chan8d449862016-05-16 18:44:13 -0700334 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700335 }
336 });
337 });
338 }
339
340 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700341 * Adds filtering objective for given device and port.
342 *
343 * @param deviceId device ID
344 * @param port ingress port number
345 * @param assignedVlan assigned VLAN ID
346 */
347 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
348 // Do nothing if the port is configured as suppressed
Charles Chan6ea94fc2016-05-10 17:29:47 -0700349 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
350 SegmentRoutingAppConfig appConfig = srManager.cfgService
351 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
352 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
353 log.info("Ignore suppressed port {}", connectPoint);
Charles Chand55e84d2016-03-30 17:54:24 -0700354 return;
355 }
356
Charles Chanf0994cc2016-05-11 20:39:35 -0700357 // Reuse unicast VLAN if the port has subnet configured
358 Ip4Prefix portSubnet = srManager.deviceConfiguration.getPortSubnet(deviceId, port);
359 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
360 final VlanId finalVlanId = (unicastVlan != null) ? unicastVlan : assignedVlan;
361
Charles Chand55e84d2016-03-30 17:54:24 -0700362 FilteringObjective.Builder filtObjBuilder =
Charles Chanf0994cc2016-05-11 20:39:35 -0700363 filterObjBuilder(deviceId, port, finalVlanId);
Charles Chan2199c302016-04-23 17:36:10 -0700364 ObjectiveContext context = new DefaultObjectiveContext(
365 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700366 deviceId, port.toLong(), finalVlanId),
Charles Chan2199c302016-04-23 17:36:10 -0700367 (objective, error) ->
368 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chanf0994cc2016-05-11 20:39:35 -0700369 deviceId, port.toLong(), finalVlanId, error));
Charles Chan2199c302016-04-23 17:36:10 -0700370 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700371 }
372
373 /**
374 * Adds a port to given multicast group on given device. This involves the
375 * update of L3 multicast group and multicast routing table entry.
376 *
377 * @param deviceId device ID
378 * @param port port to be added
379 * @param mcastIp multicast group
380 * @param assignedVlan assigned VLAN ID
381 */
382 private void addPortToDevice(DeviceId deviceId, PortNumber port,
383 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700384 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700385 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan2199c302016-04-23 17:36:10 -0700386 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700387 // First time someone request this mcast group via this device
388 portBuilder.add(port);
389 } else {
390 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700391 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700392 // Stop if the port is already in the nextobj
393 Set<PortNumber> existingPorts = getPorts(nextObj.next());
394 if (existingPorts.contains(port)) {
395 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
396 return;
397 }
398 portBuilder.addAll(existingPorts).add(port).build();
399 }
400 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700401 ObjectiveContext context = new DefaultObjectiveContext(
402 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
403 mcastIp, deviceId, port.toLong(), assignedVlan),
404 (objective, error) ->
405 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
406 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700407 NextObjective newNextObj =
408 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
409 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700410 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
411 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700412 srManager.flowObjectiveService.next(deviceId, newNextObj);
413 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700414 }
415
416 /**
417 * Removes a port from given multicast group on given device.
418 * This involves the update of L3 multicast group and multicast routing
419 * table entry.
420 *
421 * @param deviceId device ID
422 * @param port port to be added
423 * @param mcastIp multicast group
424 * @param assignedVlan assigned VLAN ID
425 * @return true if this is the last sink on this device
426 */
427 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
428 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700429 McastStoreKey mcastStoreKey =
430 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700431 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700432 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700433 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
434 return false;
435 }
Charles Chan2199c302016-04-23 17:36:10 -0700436 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700437
438 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700439 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700440 if (!existingPorts.contains(port)) {
441 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
442 return false;
443 }
444 // Copy and modify the ImmutableSet
445 existingPorts = Sets.newHashSet(existingPorts);
446 existingPorts.remove(port);
447
448 NextObjective newNextObj;
449 ForwardingObjective fwdObj;
450 if (existingPorts.isEmpty()) {
451 // If this is the last sink, remove flows and groups
452 // NOTE: Rely on GroupStore garbage collection rather than explicitly
453 // remove L3MG since there might be other flows/groups refer to
454 // the same L2IG
Charles Chan2199c302016-04-23 17:36:10 -0700455 ObjectiveContext context = new DefaultObjectiveContext(
456 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
457 mcastIp, deviceId, port.toLong(), assignedVlan),
458 (objective, error) ->
459 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
460 mcastIp, deviceId, port.toLong(), assignedVlan, error));
461 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
462 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700463 srManager.flowObjectiveService.forward(deviceId, fwdObj);
464 } else {
465 // If this is not the last sink, update flows and groups
Charles Chan2199c302016-04-23 17:36:10 -0700466 ObjectiveContext context = new DefaultObjectiveContext(
467 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
468 mcastIp, deviceId, port.toLong(), assignedVlan),
469 (objective, error) ->
470 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
471 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700472 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chanfc5c7802016-05-17 13:13:55 -0700473 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan2199c302016-04-23 17:36:10 -0700474 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700475 srManager.flowObjectiveService.next(deviceId, newNextObj);
476 srManager.flowObjectiveService.forward(deviceId, fwdObj);
477 }
Charles Chand55e84d2016-03-30 17:54:24 -0700478 return existingPorts.isEmpty();
479 }
480
Charles Chan2199c302016-04-23 17:36:10 -0700481
482 /**
483 * Removes entire group on given device.
484 *
485 * @param deviceId device ID
486 * @param mcastIp multicast group to be removed
487 * @param assignedVlan assigned VLAN ID
488 */
489 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
490 VlanId assignedVlan) {
491 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
492 // This device is not serving this multicast group
493 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
494 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
495 return;
496 }
497 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
498 // NOTE: Rely on GroupStore garbage collection rather than explicitly
499 // remove L3MG since there might be other flows/groups refer to
500 // the same L2IG
501 ObjectiveContext context = new DefaultObjectiveContext(
502 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
503 mcastIp, deviceId, assignedVlan),
504 (objective, error) ->
505 log.warn("Failed to remove {} on {}, vlan {}: {}",
506 mcastIp, deviceId, assignedVlan, error));
507 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
508 srManager.flowObjectiveService.forward(deviceId, fwdObj);
509 mcastNextObjStore.remove(mcastStoreKey);
510 mcastRoleStore.remove(mcastStoreKey);
511 }
512
513 /**
514 * Remove all groups on given device.
515 *
516 * @param deviceId device ID
517 */
518 public void removeDevice(DeviceId deviceId) {
Charles Chana061fb3f2016-06-17 14:28:07 -0700519 mcastNextObjStore.entrySet().stream()
520 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
521 .forEach(entry -> {
522 ConnectPoint source = getSource(entry.getKey().mcastIp());
523 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(),
524 assignedVlan(deviceId.equals(source.deviceId()) ? source : null));
525 mcastNextObjStore.remove(entry.getKey());
526 });
527 log.debug("{} is removed from mcastNextObjStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700528
Charles Chana061fb3f2016-06-17 14:28:07 -0700529 mcastRoleStore.entrySet().stream()
530 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
531 .forEach(entry -> {
532 mcastRoleStore.remove(entry.getKey());
533 });
534 log.debug("{} is removed from mcastRoleStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700535 }
536
Charles Chand55e84d2016-03-30 17:54:24 -0700537 /**
538 * Creates a next objective builder for multicast.
539 *
540 * @param mcastIp multicast group
541 * @param assignedVlan assigned VLAN ID
542 * @param outPorts set of output port numbers
543 * @return next objective builder
544 */
545 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
546 VlanId assignedVlan, Set<PortNumber> outPorts) {
547 int nextId = srManager.flowObjectiveService.allocateNextId();
548
549 TrafficSelector metadata =
550 DefaultTrafficSelector.builder()
551 .matchVlanId(assignedVlan)
552 .matchIPDst(mcastIp.toIpPrefix())
553 .build();
554
555 NextObjective.Builder nextObjBuilder = DefaultNextObjective
556 .builder().withId(nextId)
557 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
558 .withMeta(metadata);
559
560 outPorts.forEach(port -> {
561 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
562 if (egressVlan().equals(VlanId.NONE)) {
563 tBuilder.popVlan();
564 }
565 tBuilder.setOutput(port);
566 nextObjBuilder.addTreatment(tBuilder.build());
567 });
568
569 return nextObjBuilder;
570 }
571
572 /**
573 * Creates a forwarding objective builder for multicast.
574 *
575 * @param mcastIp multicast group
576 * @param assignedVlan assigned VLAN ID
577 * @param nextId next ID of the L3 multicast group
578 * @return forwarding objective builder
579 */
580 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
581 VlanId assignedVlan, int nextId) {
582 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
583 IpPrefix mcastPrefix = IpPrefix.valueOf(mcastIp, IpPrefix.MAX_INET_MASK_LENGTH);
584 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
585 sbuilder.matchIPDst(mcastPrefix);
586 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
587 metabuilder.matchVlanId(assignedVlan);
588
589 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
590 fwdBuilder.withSelector(sbuilder.build())
591 .withMeta(metabuilder.build())
592 .nextStep(nextId)
593 .withFlag(ForwardingObjective.Flag.SPECIFIC)
594 .fromApp(srManager.appId)
595 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
596 return fwdBuilder;
597 }
598
599 /**
600 * Creates a filtering objective builder for multicast.
601 *
602 * @param deviceId Device ID
603 * @param ingressPort ingress port of the multicast stream
604 * @param assignedVlan assigned VLAN ID
605 * @return filtering objective builder
606 */
607 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
608 VlanId assignedVlan) {
609 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
610 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
611 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
612 MacAddress.IPV4_MULTICAST_MASK))
613 .addCondition(Criteria.matchVlanId(egressVlan()))
614 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
615 // vlan assignment is valid only if this instance is master
616 if (srManager.mastershipService.isLocalMaster(deviceId)) {
617 TrafficTreatment tt = DefaultTrafficTreatment.builder()
618 .pushVlan().setVlanId(assignedVlan).build();
619 filtBuilder.withMeta(tt);
620 }
621 return filtBuilder.permit().fromApp(srManager.appId);
622 }
623
624 /**
625 * Gets output ports information from treatments.
626 *
627 * @param treatments collection of traffic treatments
628 * @return set of output port numbers
629 */
630 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
631 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
632 treatments.forEach(treatment -> {
633 treatment.allInstructions().stream()
634 .filter(instr -> instr instanceof OutputInstruction)
635 .forEach(instr -> {
636 builder.add(((OutputInstruction) instr).port());
637 });
638 });
639 return builder.build();
640 }
641
642 /**
643 * Gets a path from src to dst.
644 * If a path was allocated before, returns the allocated path.
645 * Otherwise, randomly pick one from available paths.
646 *
647 * @param src source device ID
648 * @param dst destination device ID
649 * @param mcastIp multicast group
650 * @return an optional path from src to dst
651 */
652 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
653 List<Path> allPaths = Lists.newArrayList(
654 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700655 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700656 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700657 return Optional.empty();
658 }
659
660 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700661 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
662 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
663 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700664 Set<PortNumber> existingPorts = getPorts(nextObj.next());
665 for (Path path : allPaths) {
666 PortNumber srcPort = path.links().get(0).src().port();
667 if (existingPorts.contains(srcPort)) {
668 return Optional.of(path);
669 }
670 }
671 }
672 // Otherwise, randomly pick a path
673 Collections.shuffle(allPaths);
674 return allPaths.stream().findFirst();
675 }
676
677 /**
Charles Chan2199c302016-04-23 17:36:10 -0700678 * Gets device(s) of given role in given multicast group.
679 *
680 * @param mcastIp multicast IP
681 * @param role multicast role
682 * @return set of device ID or empty set if not found
683 */
684 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
685 return mcastRoleStore.entrySet().stream()
686 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
687 entry.getValue().value() == role)
688 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
689 .collect(Collectors.toSet());
690 }
691
692 /**
Charles Chan8d449862016-05-16 18:44:13 -0700693 * Gets source connect point of given multicast group.
694 *
695 * @param mcastIp multicast IP
696 * @return source connect point or null if not found
697 */
698 private ConnectPoint getSource(IpAddress mcastIp) {
699 return srManager.multicastRouteService.getRoutes().stream()
700 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
701 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
702 .findAny().orElse(null);
703 }
704
705 /**
Charles Chan2199c302016-04-23 17:36:10 -0700706 * Gets groups which is affected by the link down event.
707 *
708 * @param link link going down
709 * @return a set of multicast IpAddress
710 */
711 private Set<IpAddress> getAffectedGroups(Link link) {
712 DeviceId deviceId = link.src().deviceId();
713 PortNumber port = link.src().port();
714 return mcastNextObjStore.entrySet().stream()
715 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
716 getPorts(entry.getValue().value().next()).contains(port))
717 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
718 .collect(Collectors.toSet());
719 }
720
721 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700722 * Gets egress VLAN from McastConfig.
723 *
724 * @return egress VLAN or VlanId.NONE if not configured
725 */
726 private VlanId egressVlan() {
727 McastConfig mcastConfig =
728 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
729 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
730 }
731
732 /**
733 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chan8d449862016-05-16 18:44:13 -0700734 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chand55e84d2016-03-30 17:54:24 -0700735 *
Charles Chan8d449862016-05-16 18:44:13 -0700736 * @param cp connect point; Can be null if not specified
737 * @return assigned VLAN ID
Charles Chand55e84d2016-03-30 17:54:24 -0700738 */
Charles Chan8d449862016-05-16 18:44:13 -0700739 private VlanId assignedVlan(ConnectPoint cp) {
740 // Use the egressVlan if it is tagged
741 if (!egressVlan().equals(VlanId.NONE)) {
742 return egressVlan();
743 }
744 // Reuse unicast VLAN if the port has subnet configured
745 if (cp != null) {
746 Ip4Prefix portSubnet = srManager.deviceConfiguration
747 .getPortSubnet(cp.deviceId(), cp.port());
748 VlanId unicastVlan = srManager.getSubnetAssignedVlanId(cp.deviceId(), portSubnet);
749 if (unicastVlan != null) {
750 return unicastVlan;
751 }
752 }
753 // By default, use VLAN_NO_SUBNET
754 return VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET);
Charles Chand55e84d2016-03-30 17:54:24 -0700755 }
Charles Chan2199c302016-04-23 17:36:10 -0700756
757 /**
758 * Gets the spine-facing port on ingress device of given multicast group.
759 *
760 * @param mcastIp multicast IP
761 * @return spine-facing port on ingress device
762 */
763 private PortNumber ingressTransitPort(IpAddress mcastIp) {
764 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
765 .stream().findAny().orElse(null);
766 if (ingressDevice != null) {
767 NextObjective nextObj = mcastNextObjStore
768 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
769 Set<PortNumber> ports = getPorts(nextObj.next());
770
771 for (PortNumber port : ports) {
772 // Spine-facing port should have no subnet and no xconnect
773 if (srManager.deviceConfiguration != null &&
774 srManager.deviceConfiguration.getPortSubnet(ingressDevice, port) == null &&
Charles Chanfc5c7802016-05-17 13:13:55 -0700775 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan2199c302016-04-23 17:36:10 -0700776 return port;
777 }
778 }
779 }
780 return null;
781 }
Charles Chand55e84d2016-03-30 17:54:24 -0700782}