blob: 270307fddc52de0e566600eb866e448216e50917 [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;
Ray Milkey6c1f0f02017-08-15 11:02:29 -070030import org.onosproject.net.config.basics.McastConfig;
Charles Chand55e84d2016-03-30 17:54:24 -070031import 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 Chanc6e64bb2018-03-02 13:26:22 -080053import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
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 Chan59cc16d2017-02-02 16:20:42 -080072import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -070073
74/**
Charles Chand2990362016-04-18 13:44:03 -070075 * Handles multicast-related events.
Charles Chand55e84d2016-03-30 17:54:24 -070076 */
Charles Chand2990362016-04-18 13:44:03 -070077public class McastHandler {
78 private static final Logger log = LoggerFactory.getLogger(McastHandler.class);
Charles Chand55e84d2016-03-30 17:54:24 -070079 private final SegmentRoutingManager srManager;
80 private final ApplicationId coreAppId;
Charles Chanfc5c7802016-05-17 13:13:55 -070081 private final StorageService storageService;
82 private final TopologyService topologyService;
Charles Chan2199c302016-04-23 17:36:10 -070083 private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
84 private final KryoNamespace.Builder mcastKryo;
85 private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
86
87 /**
88 * Role in the multicast tree.
89 */
90 public enum McastRole {
91 /**
92 * The device is the ingress device of this group.
93 */
94 INGRESS,
95 /**
96 * The device is the transit device of this group.
97 */
98 TRANSIT,
99 /**
100 * The device is the egress device of this group.
101 */
102 EGRESS
103 }
Charles Chand55e84d2016-03-30 17:54:24 -0700104
105 /**
106 * Constructs the McastEventHandler.
107 *
108 * @param srManager Segment Routing manager
109 */
Charles Chand2990362016-04-18 13:44:03 -0700110 public McastHandler(SegmentRoutingManager srManager) {
Charles Chand55e84d2016-03-30 17:54:24 -0700111 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
Charles Chand55e84d2016-03-30 17:54:24 -0700112 this.srManager = srManager;
113 this.storageService = srManager.storageService;
114 this.topologyService = srManager.topologyService;
Charles Chan2199c302016-04-23 17:36:10 -0700115 mcastKryo = new KryoNamespace.Builder()
Charles Chand55e84d2016-03-30 17:54:24 -0700116 .register(KryoNamespaces.API)
Charles Chan2199c302016-04-23 17:36:10 -0700117 .register(McastStoreKey.class)
118 .register(McastRole.class);
Charles Chand55e84d2016-03-30 17:54:24 -0700119 mcastNextObjStore = storageService
Charles Chan2199c302016-04-23 17:36:10 -0700120 .<McastStoreKey, NextObjective>consistentMapBuilder()
Charles Chand55e84d2016-03-30 17:54:24 -0700121 .withName("onos-mcast-nextobj-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700122 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-NextObj")))
Charles Chand55e84d2016-03-30 17:54:24 -0700123 .build();
Charles Chan2199c302016-04-23 17:36:10 -0700124 mcastRoleStore = storageService
125 .<McastStoreKey, McastRole>consistentMapBuilder()
126 .withName("onos-mcast-role-store")
Charles Chaneefdedf2016-05-23 16:45:45 -0700127 .withSerializer(Serializer.using(mcastKryo.build("McastHandler-Role")))
Charles Chan2199c302016-04-23 17:36:10 -0700128 .build();
129 }
130
131 /**
132 * Read initial multicast from mcast store.
133 */
Charles Chanfc5c7802016-05-17 13:13:55 -0700134 protected void init() {
Charles Chan2199c302016-04-23 17:36:10 -0700135 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
136 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
137 Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
138 sinks.forEach(sink -> {
139 processSinkAddedInternal(source, sink, mcastRoute.group());
140 });
141 });
Charles Chand55e84d2016-03-30 17:54:24 -0700142 }
143
144 /**
145 * Processes the SOURCE_ADDED event.
146 *
147 * @param event McastEvent with SOURCE_ADDED type
148 */
149 protected void processSourceAdded(McastEvent event) {
150 log.info("processSourceAdded {}", event);
151 McastRouteInfo mcastRouteInfo = event.subject();
152 if (!mcastRouteInfo.isComplete()) {
153 log.info("Incompleted McastRouteInfo. Abort.");
154 return;
155 }
156 ConnectPoint source = mcastRouteInfo.source().orElse(null);
157 Set<ConnectPoint> sinks = mcastRouteInfo.sinks();
158 IpAddress mcastIp = mcastRouteInfo.route().group();
159
160 sinks.forEach(sink -> {
161 processSinkAddedInternal(source, sink, mcastIp);
162 });
163 }
164
165 /**
166 * Processes the SINK_ADDED event.
167 *
168 * @param event McastEvent with SINK_ADDED type
169 */
170 protected void processSinkAdded(McastEvent event) {
171 log.info("processSinkAdded {}", event);
172 McastRouteInfo mcastRouteInfo = event.subject();
173 if (!mcastRouteInfo.isComplete()) {
174 log.info("Incompleted McastRouteInfo. Abort.");
175 return;
176 }
177 ConnectPoint source = mcastRouteInfo.source().orElse(null);
178 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
179 IpAddress mcastIp = mcastRouteInfo.route().group();
180
181 processSinkAddedInternal(source, sink, mcastIp);
182 }
183
184 /**
185 * Processes the SINK_REMOVED event.
186 *
187 * @param event McastEvent with SINK_REMOVED type
188 */
189 protected void processSinkRemoved(McastEvent event) {
190 log.info("processSinkRemoved {}", event);
191 McastRouteInfo mcastRouteInfo = event.subject();
192 if (!mcastRouteInfo.isComplete()) {
193 log.info("Incompleted McastRouteInfo. Abort.");
194 return;
195 }
196 ConnectPoint source = mcastRouteInfo.source().orElse(null);
197 ConnectPoint sink = mcastRouteInfo.sink().orElse(null);
198 IpAddress mcastIp = mcastRouteInfo.route().group();
Charles Chand55e84d2016-03-30 17:54:24 -0700199
Charles Chan1588e7b2016-06-28 16:50:13 -0700200 // Continue only when this instance is the master of source device
201 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
202 log.info("Skip {} due to lack of mastership of the source device {}",
203 mcastIp, source.deviceId());
204 return;
205 }
206
Charles Chand55e84d2016-03-30 17:54:24 -0700207 // When source and sink are on the same device
208 if (source.deviceId().equals(sink.deviceId())) {
209 // Source and sink are on even the same port. There must be something wrong.
210 if (source.port().equals(sink.port())) {
211 log.warn("Sink is on the same port of source. Abort");
212 return;
213 }
Charles Chan8d449862016-05-16 18:44:13 -0700214 removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chand55e84d2016-03-30 17:54:24 -0700215 return;
216 }
217
218 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700219 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700220 if (isLast) {
221 mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
222 }
Charles Chand55e84d2016-03-30 17:54:24 -0700223
224 // If this is the last sink on the device, also update upstream
225 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
226 if (mcastPath.isPresent()) {
227 List<Link> links = Lists.newArrayList(mcastPath.get().links());
228 Collections.reverse(links);
229 for (Link link : links) {
230 if (isLast) {
231 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
Charles Chan8d449862016-05-16 18:44:13 -0700232 mcastIp,
233 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Charles Chan2199c302016-04-23 17:36:10 -0700234 mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
Charles Chand55e84d2016-03-30 17:54:24 -0700235 }
236 }
237 }
238 }
239
240 /**
241 * Establishes a path from source to sink for given multicast group.
242 *
243 * @param source connect point of the multicast source
244 * @param sink connection point of the multicast sink
245 * @param mcastIp multicast group IP address
246 */
247 private void processSinkAddedInternal(ConnectPoint source, ConnectPoint sink,
248 IpAddress mcastIp) {
Charles Chan1588e7b2016-06-28 16:50:13 -0700249 // Continue only when this instance is the master of source device
250 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
251 log.info("Skip {} due to lack of mastership of the source device {}",
252 source.deviceId());
253 return;
254 }
255
Charles Chan2199c302016-04-23 17:36:10 -0700256 // Process the ingress device
Julia Fergusond8f145e2017-08-10 18:15:24 +0000257 addFilterToDevice(source.deviceId(), source.port(), assignedVlan(source), mcastIp);
Charles Chan2199c302016-04-23 17:36:10 -0700258
Charles Chand55e84d2016-03-30 17:54:24 -0700259 // When source and sink are on the same device
260 if (source.deviceId().equals(sink.deviceId())) {
261 // Source and sink are on even the same port. There must be something wrong.
262 if (source.port().equals(sink.port())) {
263 log.warn("Sink is on the same port of source. Abort");
264 return;
265 }
Charles Chan8d449862016-05-16 18:44:13 -0700266 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700267 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
Charles Chand55e84d2016-03-30 17:54:24 -0700268 return;
269 }
270
Charles Chand55e84d2016-03-30 17:54:24 -0700271 // Find a path. If present, create/update groups and flows for each hop
272 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
273 if (mcastPath.isPresent()) {
Charles Chan2199c302016-04-23 17:36:10 -0700274 List<Link> links = mcastPath.get().links();
275 checkState(links.size() == 2,
276 "Path in leaf-spine topology should always be two hops: ", links);
277
278 links.forEach(link -> {
Charles Chan8d449862016-05-16 18:44:13 -0700279 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
280 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Julia Fergusond8f145e2017-08-10 18:15:24 +0000281 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null), mcastIp);
Charles Chand55e84d2016-03-30 17:54:24 -0700282 });
Charles Chan2199c302016-04-23 17:36:10 -0700283
Charles Chand55e84d2016-03-30 17:54:24 -0700284 // Process the egress device
Charles Chan8d449862016-05-16 18:44:13 -0700285 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700286
287 // Setup mcast roles
288 mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
289 McastRole.INGRESS);
290 mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
291 McastRole.TRANSIT);
292 mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
293 McastRole.EGRESS);
294 } else {
295 log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
296 source.deviceId(), sink.deviceId());
Charles Chand55e84d2016-03-30 17:54:24 -0700297 }
298 }
299
300 /**
Charles Chan2199c302016-04-23 17:36:10 -0700301 * Processes the LINK_DOWN event.
302 *
303 * @param affectedLink Link that is going down
304 */
305 protected void processLinkDown(Link affectedLink) {
Charles Chan2199c302016-04-23 17:36:10 -0700306 getAffectedGroups(affectedLink).forEach(mcastIp -> {
307 // Find out the ingress, transit and egress device of affected group
308 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
309 .stream().findAny().orElse(null);
310 DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
311 .stream().findAny().orElse(null);
312 Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
Charles Chan8d449862016-05-16 18:44:13 -0700313 ConnectPoint source = getSource(mcastIp);
314
315 // Do not proceed if any of these info is missing
316 if (ingressDevice == null || transitDevice == null
317 || egressDevices == null || source == null) {
318 log.warn("Missing ingress {}, transit {}, egress {} devices or source {}",
319 ingressDevice, transitDevice, egressDevices, source);
Charles Chan2199c302016-04-23 17:36:10 -0700320 return;
321 }
322
Charles Chan1588e7b2016-06-28 16:50:13 -0700323 // Continue only when this instance is the master of source device
324 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
325 log.info("Skip {} due to lack of mastership of the source device {}",
326 source.deviceId());
327 return;
328 }
329
Charles Chan2199c302016-04-23 17:36:10 -0700330 // Remove entire transit
Charles Chan8d449862016-05-16 18:44:13 -0700331 removeGroupFromDevice(transitDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700332
333 // Remove transit-facing port on ingress device
334 PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
335 if (ingressTransitPort != null) {
Charles Chan8d449862016-05-16 18:44:13 -0700336 removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan(source));
Charles Chan2199c302016-04-23 17:36:10 -0700337 mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
338 }
339
340 // Construct a new path for each egress device
341 egressDevices.forEach(egressDevice -> {
342 Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
343 if (mcastPath.isPresent()) {
344 List<Link> links = mcastPath.get().links();
345 links.forEach(link -> {
Charles Chan8d449862016-05-16 18:44:13 -0700346 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp,
347 assignedVlan(link.src().deviceId().equals(source.deviceId()) ? source : null));
Julia Fergusond8f145e2017-08-10 18:15:24 +0000348 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan(null), mcastIp);
Charles Chan2199c302016-04-23 17:36:10 -0700349 });
350 // Setup new transit mcast role
351 mcastRoleStore.put(new McastStoreKey(mcastIp,
352 links.get(0).dst().deviceId()), McastRole.TRANSIT);
353 } else {
354 log.warn("Fail to recover egress device {} from link failure {}",
355 egressDevice, affectedLink);
Charles Chan8d449862016-05-16 18:44:13 -0700356 removeGroupFromDevice(egressDevice, mcastIp, assignedVlan(null));
Charles Chan2199c302016-04-23 17:36:10 -0700357 }
358 });
359 });
360 }
361
362 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700363 * Adds filtering objective for given device and port.
364 *
365 * @param deviceId device ID
366 * @param port ingress port number
367 * @param assignedVlan assigned VLAN ID
368 */
Julia Fergusond8f145e2017-08-10 18:15:24 +0000369 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
Charles Chand55e84d2016-03-30 17:54:24 -0700370 // Do nothing if the port is configured as suppressed
Charles Chan6ea94fc2016-05-10 17:29:47 -0700371 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
372 SegmentRoutingAppConfig appConfig = srManager.cfgService
373 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
374 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
375 log.info("Ignore suppressed port {}", connectPoint);
Charles Chand55e84d2016-03-30 17:54:24 -0700376 return;
377 }
378
Charles Chanc6e64bb2018-03-02 13:26:22 -0800379 MacAddress routerMac;
380 try {
381 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
382 } catch (DeviceConfigNotFoundException dcnfe) {
383 log.warn("Fail to push filtering objective since device is not configured. Abort");
384 return;
385 }
386
Charles Chand55e84d2016-03-30 17:54:24 -0700387 FilteringObjective.Builder filtObjBuilder =
Charles Chanc6e64bb2018-03-02 13:26:22 -0800388 filterObjBuilder(deviceId, port, assignedVlan, mcastIp, routerMac);
Charles Chan2199c302016-04-23 17:36:10 -0700389 ObjectiveContext context = new DefaultObjectiveContext(
390 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800391 deviceId, port.toLong(), assignedVlan),
Charles Chan2199c302016-04-23 17:36:10 -0700392 (objective, error) ->
393 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
Charles Chan59cc16d2017-02-02 16:20:42 -0800394 deviceId, port.toLong(), assignedVlan, error));
Charles Chan2199c302016-04-23 17:36:10 -0700395 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
Charles Chand55e84d2016-03-30 17:54:24 -0700396 }
397
398 /**
399 * Adds a port to given multicast group on given device. This involves the
400 * update of L3 multicast group and multicast routing table entry.
401 *
402 * @param deviceId device ID
403 * @param port port to be added
404 * @param mcastIp multicast group
405 * @param assignedVlan assigned VLAN ID
406 */
407 private void addPortToDevice(DeviceId deviceId, PortNumber port,
408 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700409 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700410 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
Charles Chan2199c302016-04-23 17:36:10 -0700411 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700412 // First time someone request this mcast group via this device
413 portBuilder.add(port);
414 } else {
415 // This device already serves some subscribers of this mcast group
Charles Chan2199c302016-04-23 17:36:10 -0700416 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700417 // Stop if the port is already in the nextobj
418 Set<PortNumber> existingPorts = getPorts(nextObj.next());
419 if (existingPorts.contains(port)) {
420 log.info("NextObj for {}/{} already exists. Abort", deviceId, port);
421 return;
422 }
Yuta HIGUCHI1edc36b2018-01-24 23:39:06 -0800423 portBuilder.addAll(existingPorts).add(port);
Charles Chand55e84d2016-03-30 17:54:24 -0700424 }
425 // Create, store and apply the new nextObj and fwdObj
Charles Chan2199c302016-04-23 17:36:10 -0700426 ObjectiveContext context = new DefaultObjectiveContext(
427 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
428 mcastIp, deviceId, port.toLong(), assignedVlan),
429 (objective, error) ->
430 log.warn("Failed to add {} on {}/{}, vlan {}: {}",
431 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700432 NextObjective newNextObj =
433 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
434 ForwardingObjective fwdObj =
Charles Chan2199c302016-04-23 17:36:10 -0700435 fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
436 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700437 srManager.flowObjectiveService.next(deviceId, newNextObj);
438 srManager.flowObjectiveService.forward(deviceId, fwdObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700439 }
440
441 /**
442 * Removes a port from given multicast group on given device.
443 * This involves the update of L3 multicast group and multicast routing
444 * table entry.
445 *
446 * @param deviceId device ID
447 * @param port port to be added
448 * @param mcastIp multicast group
449 * @param assignedVlan assigned VLAN ID
450 * @return true if this is the last sink on this device
451 */
452 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
453 IpAddress mcastIp, VlanId assignedVlan) {
Charles Chan2199c302016-04-23 17:36:10 -0700454 McastStoreKey mcastStoreKey =
455 new McastStoreKey(mcastIp, deviceId);
Charles Chand55e84d2016-03-30 17:54:24 -0700456 // This device is not serving this multicast group
Charles Chan2199c302016-04-23 17:36:10 -0700457 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
Charles Chand55e84d2016-03-30 17:54:24 -0700458 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
459 return false;
460 }
Charles Chan2199c302016-04-23 17:36:10 -0700461 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700462
463 Set<PortNumber> existingPorts = getPorts(nextObj.next());
Charles Chan2199c302016-04-23 17:36:10 -0700464 // This port does not serve this multicast group
Charles Chand55e84d2016-03-30 17:54:24 -0700465 if (!existingPorts.contains(port)) {
466 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
467 return false;
468 }
469 // Copy and modify the ImmutableSet
470 existingPorts = Sets.newHashSet(existingPorts);
471 existingPorts.remove(port);
472
473 NextObjective newNextObj;
474 ForwardingObjective fwdObj;
475 if (existingPorts.isEmpty()) {
476 // If this is the last sink, remove flows and groups
477 // NOTE: Rely on GroupStore garbage collection rather than explicitly
478 // remove L3MG since there might be other flows/groups refer to
479 // the same L2IG
Charles Chan2199c302016-04-23 17:36:10 -0700480 ObjectiveContext context = new DefaultObjectiveContext(
481 (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
482 mcastIp, deviceId, port.toLong(), assignedVlan),
483 (objective, error) ->
484 log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
485 mcastIp, deviceId, port.toLong(), assignedVlan, error));
486 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
487 mcastNextObjStore.remove(mcastStoreKey);
Charles Chand55e84d2016-03-30 17:54:24 -0700488 srManager.flowObjectiveService.forward(deviceId, fwdObj);
489 } else {
490 // If this is not the last sink, update flows and groups
Charles Chan2199c302016-04-23 17:36:10 -0700491 ObjectiveContext context = new DefaultObjectiveContext(
492 (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
493 mcastIp, deviceId, port.toLong(), assignedVlan),
494 (objective, error) ->
495 log.warn("Failed to update {} on {}/{}, vlan {}: {}",
496 mcastIp, deviceId, port.toLong(), assignedVlan, error));
Charles Chand55e84d2016-03-30 17:54:24 -0700497 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
Charles Chanfc5c7802016-05-17 13:13:55 -0700498 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
Charles Chan2199c302016-04-23 17:36:10 -0700499 mcastNextObjStore.put(mcastStoreKey, newNextObj);
Charles Chand55e84d2016-03-30 17:54:24 -0700500 srManager.flowObjectiveService.next(deviceId, newNextObj);
501 srManager.flowObjectiveService.forward(deviceId, fwdObj);
502 }
Charles Chand55e84d2016-03-30 17:54:24 -0700503 return existingPorts.isEmpty();
504 }
505
Charles Chan2199c302016-04-23 17:36:10 -0700506
507 /**
508 * Removes entire group on given device.
509 *
510 * @param deviceId device ID
511 * @param mcastIp multicast group to be removed
512 * @param assignedVlan assigned VLAN ID
513 */
514 private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
515 VlanId assignedVlan) {
516 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
517 // This device is not serving this multicast group
518 if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
519 log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
520 return;
521 }
522 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
523 // NOTE: Rely on GroupStore garbage collection rather than explicitly
524 // remove L3MG since there might be other flows/groups refer to
525 // the same L2IG
526 ObjectiveContext context = new DefaultObjectiveContext(
527 (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
528 mcastIp, deviceId, assignedVlan),
529 (objective, error) ->
530 log.warn("Failed to remove {} on {}, vlan {}: {}",
531 mcastIp, deviceId, assignedVlan, error));
532 ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
533 srManager.flowObjectiveService.forward(deviceId, fwdObj);
534 mcastNextObjStore.remove(mcastStoreKey);
535 mcastRoleStore.remove(mcastStoreKey);
536 }
537
538 /**
539 * Remove all groups on given device.
540 *
541 * @param deviceId device ID
542 */
543 public void removeDevice(DeviceId deviceId) {
Charles Chana061fb3f2016-06-17 14:28:07 -0700544 mcastNextObjStore.entrySet().stream()
545 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
546 .forEach(entry -> {
547 ConnectPoint source = getSource(entry.getKey().mcastIp());
548 removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(),
Charles Chan8d055212018-01-04 14:26:07 -0800549 assignedVlan(source != null && deviceId.equals(source.deviceId()) ? source : null));
Charles Chana061fb3f2016-06-17 14:28:07 -0700550 mcastNextObjStore.remove(entry.getKey());
551 });
552 log.debug("{} is removed from mcastNextObjStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700553
Charles Chana061fb3f2016-06-17 14:28:07 -0700554 mcastRoleStore.entrySet().stream()
555 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
556 .forEach(entry -> {
557 mcastRoleStore.remove(entry.getKey());
558 });
559 log.debug("{} is removed from mcastRoleStore", deviceId);
Charles Chan2199c302016-04-23 17:36:10 -0700560 }
561
Charles Chand55e84d2016-03-30 17:54:24 -0700562 /**
563 * Creates a next objective builder for multicast.
564 *
565 * @param mcastIp multicast group
566 * @param assignedVlan assigned VLAN ID
567 * @param outPorts set of output port numbers
568 * @return next objective builder
569 */
570 private NextObjective.Builder nextObjBuilder(IpAddress mcastIp,
571 VlanId assignedVlan, Set<PortNumber> outPorts) {
572 int nextId = srManager.flowObjectiveService.allocateNextId();
573
574 TrafficSelector metadata =
575 DefaultTrafficSelector.builder()
576 .matchVlanId(assignedVlan)
577 .matchIPDst(mcastIp.toIpPrefix())
578 .build();
579
580 NextObjective.Builder nextObjBuilder = DefaultNextObjective
581 .builder().withId(nextId)
582 .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
583 .withMeta(metadata);
584
585 outPorts.forEach(port -> {
586 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
587 if (egressVlan().equals(VlanId.NONE)) {
588 tBuilder.popVlan();
589 }
590 tBuilder.setOutput(port);
591 nextObjBuilder.addTreatment(tBuilder.build());
592 });
593
594 return nextObjBuilder;
595 }
596
597 /**
598 * Creates a forwarding objective builder for multicast.
599 *
600 * @param mcastIp multicast group
601 * @param assignedVlan assigned VLAN ID
602 * @param nextId next ID of the L3 multicast group
603 * @return forwarding objective builder
604 */
605 private ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
606 VlanId assignedVlan, int nextId) {
607 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
Julia Fergusond8f145e2017-08-10 18:15:24 +0000608 IpPrefix mcastPrefix = mcastIp.toIpPrefix();
609
610 if (mcastIp.isIp4()) {
611 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
612 sbuilder.matchIPDst(mcastPrefix);
613 } else {
614 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
615 sbuilder.matchIPv6Dst(mcastPrefix);
616 }
617
618
Charles Chand55e84d2016-03-30 17:54:24 -0700619 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
620 metabuilder.matchVlanId(assignedVlan);
621
622 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
623 fwdBuilder.withSelector(sbuilder.build())
624 .withMeta(metabuilder.build())
625 .nextStep(nextId)
626 .withFlag(ForwardingObjective.Flag.SPECIFIC)
627 .fromApp(srManager.appId)
628 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
629 return fwdBuilder;
630 }
631
632 /**
633 * Creates a filtering objective builder for multicast.
634 *
635 * @param deviceId Device ID
636 * @param ingressPort ingress port of the multicast stream
637 * @param assignedVlan assigned VLAN ID
Charles Chanc6e64bb2018-03-02 13:26:22 -0800638 * @param routerMac router MAC. This is carried in metadata and used from some switches that
639 * need to put unicast entry before multicast entry in TMAC table.
Charles Chand55e84d2016-03-30 17:54:24 -0700640 * @return filtering objective builder
641 */
642 private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
Charles Chanc6e64bb2018-03-02 13:26:22 -0800643 VlanId assignedVlan, IpAddress mcastIp, MacAddress routerMac) {
Charles Chand55e84d2016-03-30 17:54:24 -0700644 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan1588e7b2016-06-28 16:50:13 -0700645
Julia Fergusond8f145e2017-08-10 18:15:24 +0000646 if (mcastIp.isIp4()) {
647 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
648 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
649 MacAddress.IPV4_MULTICAST_MASK))
650 .addCondition(Criteria.matchVlanId(egressVlan()))
651 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
652 } else {
653 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
654 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
655 MacAddress.IPV6_MULTICAST_MASK))
656 .addCondition(Criteria.matchVlanId(egressVlan()))
657 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
658 }
Charles Chan1588e7b2016-06-28 16:50:13 -0700659 TrafficTreatment tt = DefaultTrafficTreatment.builder()
Charles Chanc6e64bb2018-03-02 13:26:22 -0800660 .pushVlan().setVlanId(assignedVlan)
661 .setEthDst(routerMac)
662 .build();
Charles Chan1588e7b2016-06-28 16:50:13 -0700663 filtBuilder.withMeta(tt);
664
Charles Chand55e84d2016-03-30 17:54:24 -0700665 return filtBuilder.permit().fromApp(srManager.appId);
666 }
667
668 /**
669 * Gets output ports information from treatments.
670 *
671 * @param treatments collection of traffic treatments
672 * @return set of output port numbers
673 */
674 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
675 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
676 treatments.forEach(treatment -> {
677 treatment.allInstructions().stream()
678 .filter(instr -> instr instanceof OutputInstruction)
679 .forEach(instr -> {
680 builder.add(((OutputInstruction) instr).port());
681 });
682 });
683 return builder.build();
684 }
685
686 /**
687 * Gets a path from src to dst.
688 * If a path was allocated before, returns the allocated path.
689 * Otherwise, randomly pick one from available paths.
690 *
691 * @param src source device ID
692 * @param dst destination device ID
693 * @param mcastIp multicast group
694 * @return an optional path from src to dst
695 */
696 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
697 List<Path> allPaths = Lists.newArrayList(
698 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700699 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700700 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700701 return Optional.empty();
702 }
703
704 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700705 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
706 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
707 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700708 Set<PortNumber> existingPorts = getPorts(nextObj.next());
709 for (Path path : allPaths) {
710 PortNumber srcPort = path.links().get(0).src().port();
711 if (existingPorts.contains(srcPort)) {
712 return Optional.of(path);
713 }
714 }
715 }
716 // Otherwise, randomly pick a path
717 Collections.shuffle(allPaths);
718 return allPaths.stream().findFirst();
719 }
720
721 /**
Charles Chan2199c302016-04-23 17:36:10 -0700722 * Gets device(s) of given role in given multicast group.
723 *
724 * @param mcastIp multicast IP
725 * @param role multicast role
726 * @return set of device ID or empty set if not found
727 */
728 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
729 return mcastRoleStore.entrySet().stream()
730 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
731 entry.getValue().value() == role)
732 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
733 .collect(Collectors.toSet());
734 }
735
736 /**
Charles Chan8d449862016-05-16 18:44:13 -0700737 * Gets source connect point of given multicast group.
738 *
739 * @param mcastIp multicast IP
740 * @return source connect point or null if not found
741 */
742 private ConnectPoint getSource(IpAddress mcastIp) {
743 return srManager.multicastRouteService.getRoutes().stream()
744 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
745 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
746 .findAny().orElse(null);
747 }
748
749 /**
Charles Chan2199c302016-04-23 17:36:10 -0700750 * Gets groups which is affected by the link down event.
751 *
752 * @param link link going down
753 * @return a set of multicast IpAddress
754 */
755 private Set<IpAddress> getAffectedGroups(Link link) {
756 DeviceId deviceId = link.src().deviceId();
757 PortNumber port = link.src().port();
758 return mcastNextObjStore.entrySet().stream()
759 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
760 getPorts(entry.getValue().value().next()).contains(port))
761 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
762 .collect(Collectors.toSet());
763 }
764
765 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700766 * Gets egress VLAN from McastConfig.
767 *
768 * @return egress VLAN or VlanId.NONE if not configured
769 */
770 private VlanId egressVlan() {
771 McastConfig mcastConfig =
772 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
773 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
774 }
775
776 /**
777 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chan8d449862016-05-16 18:44:13 -0700778 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chand55e84d2016-03-30 17:54:24 -0700779 *
Charles Chan8d449862016-05-16 18:44:13 -0700780 * @param cp connect point; Can be null if not specified
781 * @return assigned VLAN ID
Charles Chand55e84d2016-03-30 17:54:24 -0700782 */
Charles Chan8d449862016-05-16 18:44:13 -0700783 private VlanId assignedVlan(ConnectPoint cp) {
784 // Use the egressVlan if it is tagged
785 if (!egressVlan().equals(VlanId.NONE)) {
786 return egressVlan();
787 }
788 // Reuse unicast VLAN if the port has subnet configured
789 if (cp != null) {
Charles Chan206cac02017-10-20 19:09:16 -0700790 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan59cc16d2017-02-02 16:20:42 -0800791 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chan8d449862016-05-16 18:44:13 -0700792 }
Charles Chan59cc16d2017-02-02 16:20:42 -0800793 // Use DEFAULT_VLAN if none of the above matches
794 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -0700795 }
Charles Chan2199c302016-04-23 17:36:10 -0700796
797 /**
798 * Gets the spine-facing port on ingress device of given multicast group.
799 *
800 * @param mcastIp multicast IP
801 * @return spine-facing port on ingress device
802 */
803 private PortNumber ingressTransitPort(IpAddress mcastIp) {
804 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
805 .stream().findAny().orElse(null);
806 if (ingressDevice != null) {
807 NextObjective nextObj = mcastNextObjStore
808 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
809 Set<PortNumber> ports = getPorts(nextObj.next());
810
811 for (PortNumber port : ports) {
812 // Spine-facing port should have no subnet and no xconnect
813 if (srManager.deviceConfiguration != null &&
Pier Ventre10bd8d12016-11-26 21:05:22 -0800814 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chanfc5c7802016-05-17 13:13:55 -0700815 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan2199c302016-04-23 17:36:10 -0700816 return port;
817 }
818 }
819 }
820 return null;
821 }
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700822
823 /**
824 * Removes filtering objective for given device and port.
825 *
826 * @param deviceId device ID
827 * @param port ingress port number
828 * @param assignedVlan assigned VLAN ID
829 * @param mcastIp multicast IP address
830 */
831 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
832 // Do nothing if the port is configured as suppressed
833 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
834 SegmentRoutingAppConfig appConfig = srManager.cfgService
835 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
836 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
837 log.info("Ignore suppressed port {}", connectPoint);
838 return;
839 }
840
Charles Chanc6e64bb2018-03-02 13:26:22 -0800841 MacAddress routerMac;
842 try {
843 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
844 } catch (DeviceConfigNotFoundException dcnfe) {
845 log.warn("Fail to push filtering objective since device is not configured. Abort");
846 return;
847 }
848
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700849 FilteringObjective.Builder filtObjBuilder =
Charles Chanc6e64bb2018-03-02 13:26:22 -0800850 filterObjBuilder(deviceId, port, assignedVlan, mcastIp, routerMac);
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700851 ObjectiveContext context = new DefaultObjectiveContext(
852 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
853 deviceId, port.toLong(), assignedVlan),
854 (objective, error) ->
855 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
856 deviceId, port.toLong(), assignedVlan, error));
857 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
858 }
859
860 /**
861 * Adds or removes filtering objective for given device and port.
862 *
863 * @param deviceId device ID
864 * @param portNum ingress port number
865 * @param vlanId assigned VLAN ID
866 * @param install true to add, false to remove
867 */
868 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
869 VlanId vlanId, boolean install) {
870 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
871 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
872 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
873 if (install) {
874 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
875 } else {
876 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
877 }
878 }
879 });
880 }
Charles Chand55e84d2016-03-30 17:54:24 -0700881}