blob: 6f5417e5cddc5840c4e4b6bb554687b6885e2529 [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 Chan3bb17c62018-03-02 15:41:41 -0800388 filterObjBuilder(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 *
Charles Chand55e84d2016-03-30 17:54:24 -0700635 * @param ingressPort ingress port of the multicast stream
636 * @param assignedVlan assigned VLAN ID
Charles Chanc6e64bb2018-03-02 13:26:22 -0800637 * @param routerMac router MAC. This is carried in metadata and used from some switches that
638 * need to put unicast entry before multicast entry in TMAC table.
Charles Chand55e84d2016-03-30 17:54:24 -0700639 * @return filtering objective builder
640 */
Charles Chan3bb17c62018-03-02 15:41:41 -0800641 private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort,
Charles Chanc6e64bb2018-03-02 13:26:22 -0800642 VlanId assignedVlan, IpAddress mcastIp, MacAddress routerMac) {
Charles Chand55e84d2016-03-30 17:54:24 -0700643 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
Charles Chan1588e7b2016-06-28 16:50:13 -0700644
Julia Fergusond8f145e2017-08-10 18:15:24 +0000645 if (mcastIp.isIp4()) {
646 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
647 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
648 MacAddress.IPV4_MULTICAST_MASK))
649 .addCondition(Criteria.matchVlanId(egressVlan()))
650 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
651 } else {
652 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
653 .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
654 MacAddress.IPV6_MULTICAST_MASK))
655 .addCondition(Criteria.matchVlanId(egressVlan()))
656 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
657 }
Charles Chan1588e7b2016-06-28 16:50:13 -0700658 TrafficTreatment tt = DefaultTrafficTreatment.builder()
Charles Chanc6e64bb2018-03-02 13:26:22 -0800659 .pushVlan().setVlanId(assignedVlan)
660 .setEthDst(routerMac)
661 .build();
Charles Chan1588e7b2016-06-28 16:50:13 -0700662 filtBuilder.withMeta(tt);
663
Charles Chand55e84d2016-03-30 17:54:24 -0700664 return filtBuilder.permit().fromApp(srManager.appId);
665 }
666
667 /**
668 * Gets output ports information from treatments.
669 *
670 * @param treatments collection of traffic treatments
671 * @return set of output port numbers
672 */
673 private Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
674 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
675 treatments.forEach(treatment -> {
676 treatment.allInstructions().stream()
677 .filter(instr -> instr instanceof OutputInstruction)
678 .forEach(instr -> {
679 builder.add(((OutputInstruction) instr).port());
680 });
681 });
682 return builder.build();
683 }
684
685 /**
686 * Gets a path from src to dst.
687 * If a path was allocated before, returns the allocated path.
688 * Otherwise, randomly pick one from available paths.
689 *
690 * @param src source device ID
691 * @param dst destination device ID
692 * @param mcastIp multicast group
693 * @return an optional path from src to dst
694 */
695 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
696 List<Path> allPaths = Lists.newArrayList(
697 topologyService.getPaths(topologyService.currentTopology(), src, dst));
Charles Chan2199c302016-04-23 17:36:10 -0700698 log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
Charles Chand55e84d2016-03-30 17:54:24 -0700699 if (allPaths.isEmpty()) {
Charles Chand55e84d2016-03-30 17:54:24 -0700700 return Optional.empty();
701 }
702
703 // If one of the available path is used before, use the same path
Charles Chan2199c302016-04-23 17:36:10 -0700704 McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
705 if (mcastNextObjStore.containsKey(mcastStoreKey)) {
706 NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Charles Chand55e84d2016-03-30 17:54:24 -0700707 Set<PortNumber> existingPorts = getPorts(nextObj.next());
708 for (Path path : allPaths) {
709 PortNumber srcPort = path.links().get(0).src().port();
710 if (existingPorts.contains(srcPort)) {
711 return Optional.of(path);
712 }
713 }
714 }
715 // Otherwise, randomly pick a path
716 Collections.shuffle(allPaths);
717 return allPaths.stream().findFirst();
718 }
719
720 /**
Charles Chan2199c302016-04-23 17:36:10 -0700721 * Gets device(s) of given role in given multicast group.
722 *
723 * @param mcastIp multicast IP
724 * @param role multicast role
725 * @return set of device ID or empty set if not found
726 */
727 private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
728 return mcastRoleStore.entrySet().stream()
729 .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
730 entry.getValue().value() == role)
731 .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
732 .collect(Collectors.toSet());
733 }
734
735 /**
Charles Chan8d449862016-05-16 18:44:13 -0700736 * Gets source connect point of given multicast group.
737 *
738 * @param mcastIp multicast IP
739 * @return source connect point or null if not found
740 */
741 private ConnectPoint getSource(IpAddress mcastIp) {
742 return srManager.multicastRouteService.getRoutes().stream()
743 .filter(mcastRoute -> mcastRoute.group().equals(mcastIp))
744 .map(mcastRoute -> srManager.multicastRouteService.fetchSource(mcastRoute))
745 .findAny().orElse(null);
746 }
747
748 /**
Charles Chan2199c302016-04-23 17:36:10 -0700749 * Gets groups which is affected by the link down event.
750 *
751 * @param link link going down
752 * @return a set of multicast IpAddress
753 */
754 private Set<IpAddress> getAffectedGroups(Link link) {
755 DeviceId deviceId = link.src().deviceId();
756 PortNumber port = link.src().port();
757 return mcastNextObjStore.entrySet().stream()
758 .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
759 getPorts(entry.getValue().value().next()).contains(port))
760 .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
761 .collect(Collectors.toSet());
762 }
763
764 /**
Charles Chand55e84d2016-03-30 17:54:24 -0700765 * Gets egress VLAN from McastConfig.
766 *
767 * @return egress VLAN or VlanId.NONE if not configured
768 */
769 private VlanId egressVlan() {
770 McastConfig mcastConfig =
771 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
772 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
773 }
774
775 /**
776 * Gets assigned VLAN according to the value of egress VLAN.
Charles Chan8d449862016-05-16 18:44:13 -0700777 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
Charles Chand55e84d2016-03-30 17:54:24 -0700778 *
Charles Chan8d449862016-05-16 18:44:13 -0700779 * @param cp connect point; Can be null if not specified
780 * @return assigned VLAN ID
Charles Chand55e84d2016-03-30 17:54:24 -0700781 */
Charles Chan8d449862016-05-16 18:44:13 -0700782 private VlanId assignedVlan(ConnectPoint cp) {
783 // Use the egressVlan if it is tagged
784 if (!egressVlan().equals(VlanId.NONE)) {
785 return egressVlan();
786 }
787 // Reuse unicast VLAN if the port has subnet configured
788 if (cp != null) {
Charles Chan206cac02017-10-20 19:09:16 -0700789 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Charles Chan59cc16d2017-02-02 16:20:42 -0800790 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
Charles Chan8d449862016-05-16 18:44:13 -0700791 }
Charles Chan59cc16d2017-02-02 16:20:42 -0800792 // Use DEFAULT_VLAN if none of the above matches
793 return SegmentRoutingManager.INTERNAL_VLAN;
Charles Chand55e84d2016-03-30 17:54:24 -0700794 }
Charles Chan2199c302016-04-23 17:36:10 -0700795
796 /**
797 * Gets the spine-facing port on ingress device of given multicast group.
798 *
799 * @param mcastIp multicast IP
800 * @return spine-facing port on ingress device
801 */
802 private PortNumber ingressTransitPort(IpAddress mcastIp) {
803 DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
804 .stream().findAny().orElse(null);
805 if (ingressDevice != null) {
806 NextObjective nextObj = mcastNextObjStore
807 .get(new McastStoreKey(mcastIp, ingressDevice)).value();
808 Set<PortNumber> ports = getPorts(nextObj.next());
809
810 for (PortNumber port : ports) {
811 // Spine-facing port should have no subnet and no xconnect
812 if (srManager.deviceConfiguration != null &&
Pier Ventre10bd8d12016-11-26 21:05:22 -0800813 srManager.deviceConfiguration.getPortSubnets(ingressDevice, port).isEmpty() &&
Charles Chanfc5c7802016-05-17 13:13:55 -0700814 !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) {
Charles Chan2199c302016-04-23 17:36:10 -0700815 return port;
816 }
817 }
818 }
819 return null;
820 }
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700821
822 /**
823 * Removes filtering objective for given device and port.
824 *
825 * @param deviceId device ID
826 * @param port ingress port number
827 * @param assignedVlan assigned VLAN ID
828 * @param mcastIp multicast IP address
829 */
830 private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
831 // Do nothing if the port is configured as suppressed
832 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
833 SegmentRoutingAppConfig appConfig = srManager.cfgService
834 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
835 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
836 log.info("Ignore suppressed port {}", connectPoint);
837 return;
838 }
839
Charles Chanc6e64bb2018-03-02 13:26:22 -0800840 MacAddress routerMac;
841 try {
842 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
843 } catch (DeviceConfigNotFoundException dcnfe) {
844 log.warn("Fail to push filtering objective since device is not configured. Abort");
845 return;
846 }
847
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700848 FilteringObjective.Builder filtObjBuilder =
Charles Chan3bb17c62018-03-02 15:41:41 -0800849 filterObjBuilder(port, assignedVlan, mcastIp, routerMac);
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700850 ObjectiveContext context = new DefaultObjectiveContext(
851 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
852 deviceId, port.toLong(), assignedVlan),
853 (objective, error) ->
854 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
855 deviceId, port.toLong(), assignedVlan, error));
856 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
857 }
858
859 /**
860 * Adds or removes filtering objective for given device and port.
861 *
862 * @param deviceId device ID
863 * @param portNum ingress port number
864 * @param vlanId assigned VLAN ID
865 * @param install true to add, false to remove
866 */
867 protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
868 VlanId vlanId, boolean install) {
869 srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
870 ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
871 if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
872 if (install) {
873 addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
874 } else {
875 removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
876 }
877 }
878 });
879 }
Charles Chand55e84d2016-03-30 17:54:24 -0700880}