blob: 1f41fda1f8fd4ad06deaf8f5fedcf0f286567f7a [file] [log] [blame]
Charles Chanc7b3c452018-06-19 20:31:57 -07001/*
2 * Copyright 2018-present Open Networking Foundation
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 */
16package org.onosproject.segmentrouting.xconnect.impl;
17
Charles Chand5814aa2018-08-19 19:21:46 -070018import com.google.common.collect.ImmutableMap;
Charles Chanc7b3c452018-06-19 20:31:57 -070019import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Sets;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053027import org.onlab.packet.Ethernet;
Charles Chanc7b3c452018-06-19 20:31:57 -070028import org.onlab.packet.MacAddress;
29import org.onlab.packet.VlanId;
30import org.onlab.util.KryoNamespace;
31import org.onosproject.codec.CodecService;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.mastership.MastershipService;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.DeviceId;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053037import org.onosproject.net.Host;
38import org.onosproject.net.HostLocation;
Charles Chanc7b3c452018-06-19 20:31:57 -070039import org.onosproject.net.PortNumber;
40import org.onosproject.net.config.NetworkConfigService;
41import org.onosproject.net.device.DeviceEvent;
42import org.onosproject.net.device.DeviceListener;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.net.flow.DefaultTrafficSelector;
45import org.onosproject.net.flow.DefaultTrafficTreatment;
46import org.onosproject.net.flow.TrafficSelector;
47import org.onosproject.net.flow.TrafficTreatment;
48import org.onosproject.net.flow.criteria.Criteria;
49import org.onosproject.net.flowobjective.DefaultFilteringObjective;
50import org.onosproject.net.flowobjective.DefaultForwardingObjective;
51import org.onosproject.net.flowobjective.DefaultNextObjective;
52import org.onosproject.net.flowobjective.DefaultObjectiveContext;
53import org.onosproject.net.flowobjective.FilteringObjective;
54import org.onosproject.net.flowobjective.FlowObjectiveService;
55import org.onosproject.net.flowobjective.ForwardingObjective;
56import org.onosproject.net.flowobjective.NextObjective;
57import org.onosproject.net.flowobjective.Objective;
58import org.onosproject.net.flowobjective.ObjectiveContext;
59import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053060import org.onosproject.net.host.HostEvent;
61import org.onosproject.net.host.HostListener;
62import org.onosproject.net.host.HostService;
63import org.onosproject.net.intf.InterfaceService;
Charles Chanc7b3c452018-06-19 20:31:57 -070064import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053065import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chanc7b3c452018-06-19 20:31:57 -070066import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
67import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
68import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
69import org.onosproject.segmentrouting.xconnect.api.XconnectService;
70import org.onosproject.store.serializers.KryoNamespaces;
71import org.onosproject.store.service.ConsistentMap;
72import org.onosproject.store.service.MapEvent;
73import org.onosproject.store.service.MapEventListener;
74import org.onosproject.store.service.Serializer;
75import org.onosproject.store.service.StorageService;
76import org.onosproject.store.service.Versioned;
77import org.slf4j.Logger;
78import org.slf4j.LoggerFactory;
79
80import java.io.Serializable;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053081import java.util.ArrayList;
82import java.util.Collections;
83import java.util.List;
84import java.util.Map;
85import java.util.Optional;
Charles Chanc7b3c452018-06-19 20:31:57 -070086import java.util.Set;
87import java.util.concurrent.CompletableFuture;
Charles Chan168111e2018-08-07 12:48:36 -070088import java.util.concurrent.ExecutorService;
89import java.util.concurrent.Executors;
Charles Chanc7b3c452018-06-19 20:31:57 -070090import java.util.function.BiConsumer;
91import java.util.function.Consumer;
92import java.util.stream.Collectors;
93
Charles Chan168111e2018-08-07 12:48:36 -070094import static org.onlab.util.Tools.groupedThreads;
95
Charles Chanc7b3c452018-06-19 20:31:57 -070096@Service
97@Component(immediate = true)
98public class XconnectManager implements XconnectService {
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 private CoreService coreService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 private CodecService codecService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 private StorageService storageService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 public NetworkConfigService netCfgService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 public DeviceService deviceService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 public FlowObjectiveService flowObjectiveService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 public MastershipService mastershipService;
119
Charles Chandc5fb022018-10-19 16:32:07 -0700120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700121 public SegmentRoutingService srService;
122
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 public InterfaceService interfaceService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 HostService hostService;
128
Charles Chanc7b3c452018-06-19 20:31:57 -0700129 private static final String APP_NAME = "org.onosproject.xconnect";
130 private static final String ERROR_NOT_MASTER = "Not master controller";
131
132 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
133
134 private ApplicationId appId;
135 private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
Charles Chan3e56d9f2018-09-21 11:29:12 -0700136 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chanc7b3c452018-06-19 20:31:57 -0700137
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530138 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
139 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
140
Charles Chanc7b3c452018-06-19 20:31:57 -0700141 private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
142 private final DeviceListener deviceListener = new InternalDeviceListener();
143
Charles Chan168111e2018-08-07 12:48:36 -0700144 private ExecutorService deviceEventExecutor;
145
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530146 private final HostListener hostListener = new InternalHostListener();
147 private ExecutorService hostEventExecutor;
148
149
Charles Chanc7b3c452018-06-19 20:31:57 -0700150 @Activate
151 void activate() {
152 appId = coreService.registerApplication(APP_NAME);
153 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
154
155 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
156 .register(KryoNamespaces.API)
Charles Chan871d9182018-07-23 12:53:16 -0700157 .register(XconnectManager.class)
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530158 .register(XconnectKey.class)
159 .register(VlanNextObjectiveStoreKey.class);
Charles Chanc7b3c452018-06-19 20:31:57 -0700160
161 xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
162 .withName("onos-sr-xconnect")
163 .withRelaxedReadConsistency()
164 .withSerializer(Serializer.using(serializer.build()))
165 .build();
166 xconnectStore.addListener(xconnectListener);
167
Charles Chan3e56d9f2018-09-21 11:29:12 -0700168 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chanc7b3c452018-06-19 20:31:57 -0700169 .withName("onos-sr-xconnect-next")
170 .withRelaxedReadConsistency()
171 .withSerializer(Serializer.using(serializer.build()))
172 .build();
173
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530174 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
175 .withName("onos-sr-xconnect-l2multicast-next")
176 .withSerializer(Serializer.using(serializer.build()))
177 .build();
178 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
179 .withName("onos-sr-xconnect-l2multicast-ports")
180 .withSerializer(Serializer.using(serializer.build()))
181 .build();
182
Charles Chan168111e2018-08-07 12:48:36 -0700183 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
184 groupedThreads("sr-xconnect-device-event", "%d", log));
185
Charles Chanc7b3c452018-06-19 20:31:57 -0700186 deviceService.addListener(deviceListener);
187
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530188 hostEventExecutor = Executors.newSingleThreadExecutor(
189 groupedThreads("sr-xconnect-host-event", "%d", log));
190
191 hostService.addListener(hostListener);
192
Charles Chanc7b3c452018-06-19 20:31:57 -0700193 log.info("Started");
194 }
195
196 @Deactivate
197 void deactivate() {
198 xconnectStore.removeListener(xconnectListener);
199 deviceService.removeListener(deviceListener);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530200 hostService.removeListener(hostListener);
Charles Chanc7b3c452018-06-19 20:31:57 -0700201 codecService.unregisterCodec(XconnectDesc.class);
202
Charles Chan168111e2018-08-07 12:48:36 -0700203 deviceEventExecutor.shutdown();
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530204 hostEventExecutor.shutdown();
Charles Chan168111e2018-08-07 12:48:36 -0700205
Charles Chanc7b3c452018-06-19 20:31:57 -0700206 log.info("Stopped");
207 }
208
209 @Override
210 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
211 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530212 deviceId, vlanId, ports);
Charles Chanc7b3c452018-06-19 20:31:57 -0700213 final XconnectKey key = new XconnectKey(deviceId, vlanId);
214 xconnectStore.put(key, ports);
215 }
216
217 @Override
218 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
219 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530220 deviceId, vlanId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700221 final XconnectKey key = new XconnectKey(deviceId, vlanId);
222 xconnectStore.remove(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530223
224 // Cleanup multicasting support, if any.
225 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
226 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
227 });
228
Charles Chanc7b3c452018-06-19 20:31:57 -0700229 }
230
231 @Override
232 public Set<XconnectDesc> getXconnects() {
233 return xconnectStore.asJavaMap().entrySet().stream()
234 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
235 .collect(Collectors.toSet());
236 }
237
238 @Override
239 public boolean hasXconnect(ConnectPoint cp) {
240 return getXconnects().stream().anyMatch(desc ->
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530241 desc.key().deviceId().equals(cp.deviceId())
242 && desc.ports().contains(cp.port())
Charles Chanc7b3c452018-06-19 20:31:57 -0700243 );
244 }
245
Charles Chand5814aa2018-08-19 19:21:46 -0700246 @Override
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530247 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
248 return getXconnects().stream()
249 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
250 .map(XconnectDesc::key)
251 .map(XconnectKey::vlanId)
252 .collect(Collectors.toList());
253 }
254
255 @Override
256 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
257 return getXconnects().stream()
258 .anyMatch(desc -> desc.key().deviceId().equals(deviceId) && desc.key().vlanId().equals(vlanId));
259 }
260
261 @Override
Charles Chan3e56d9f2018-09-21 11:29:12 -0700262 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chand5814aa2018-08-19 19:21:46 -0700263 if (xconnectNextObjStore != null) {
264 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
265 } else {
266 return ImmutableMap.of();
267 }
268 }
269
270 @Override
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530271 public int getNextId(final DeviceId deviceId, final VlanId vlanId) {
272 Optional<Integer> nextObjective = getNext().entrySet().stream()
273 .filter(d -> d.getKey().deviceId().equals(deviceId) && d.getKey().vlanId().equals(vlanId))
274 .findFirst()
275 .map(Map.Entry::getValue);
276 return nextObjective.isPresent() ? nextObjective.get() : -1;
277 }
278
279 @Override
Charles Chand5814aa2018-08-19 19:21:46 -0700280 public void removeNextId(int nextId) {
281 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700282 if (e.getValue().value() == nextId) {
Charles Chand5814aa2018-08-19 19:21:46 -0700283 xconnectNextObjStore.remove(e.getKey());
284 }
285 });
286 }
287
Charles Chanc7b3c452018-06-19 20:31:57 -0700288 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<PortNumber>> {
289 @Override
290 public void event(MapEvent<XconnectKey, Set<PortNumber>> event) {
291 XconnectKey key = event.key();
292 Versioned<Set<PortNumber>> ports = event.newValue();
293 Versioned<Set<PortNumber>> oldPorts = event.oldValue();
294
295 switch (event.type()) {
296 case INSERT:
297 populateXConnect(key, ports.value());
298 break;
299 case UPDATE:
300 updateXConnect(key, oldPorts.value(), ports.value());
301 break;
302 case REMOVE:
303 revokeXConnect(key, oldPorts.value());
304 break;
305 default:
306 break;
307 }
308 }
309 }
310
311 private class InternalDeviceListener implements DeviceListener {
312 @Override
313 public void event(DeviceEvent event) {
Charles Chan168111e2018-08-07 12:48:36 -0700314 deviceEventExecutor.execute(() -> {
315 DeviceId deviceId = event.subject().id();
316 if (!mastershipService.isLocalMaster(deviceId)) {
317 return;
318 }
Charles Chanc7b3c452018-06-19 20:31:57 -0700319
Charles Chan168111e2018-08-07 12:48:36 -0700320 switch (event.type()) {
321 case DEVICE_ADDED:
322 case DEVICE_AVAILABILITY_CHANGED:
323 case DEVICE_UPDATED:
324 if (deviceService.isAvailable(deviceId)) {
325 init(deviceId);
326 } else {
327 cleanup(deviceId);
328 }
329 break;
330 default:
331 break;
332 }
333 });
Charles Chanc7b3c452018-06-19 20:31:57 -0700334 }
335 }
336
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530337 private class InternalHostListener implements HostListener {
338 @Override
339 public void event(HostEvent event) {
340 hostEventExecutor.execute(() -> {
341
342 switch (event.type()) {
343 case HOST_MOVED:
344 log.trace("Processing host event {}", event);
345
346 Host host = event.subject();
347 Set<HostLocation> prevLocations = event.prevSubject().locations();
348 Set<HostLocation> newLocations = host.locations();
349
350 // Dual-home host port failure
351 // For each old location, in failed and paired devices update L2 vlan groups
352 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
353
354 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
355 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
356
357 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
358 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
359 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
360
361 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
362 prevLocation.port());
363 xconnectVlans.forEach(xconnectVlan -> {
364 // Add single-home host into L2 multicast group at paired device side.
365 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
366 newLocations.stream()
367 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
368 .forEach(location -> populateL2Multicast(location.deviceId(),
369 srService.getPairLocalPort(
370 location.deviceId()).get(),
371 xconnectVlan,
372 Collections.singletonList(
373 location.port())));
374 // Ensure pair-port attached to xconnect vlan flooding group at dual home failed device.
375 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
376 });
377 }
378 });
379
380 // Dual-home host port restoration
381 // For each new location, reverse xconnect loop prevention groups.
382 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
383 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
384 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
385 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
386 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
387
388 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
389 newLocation.port());
390 xconnectVlans.forEach(xconnectVlan -> {
391 // Remove recovered dual homed port from vlan L2 multicast group
392 prevLocations.stream()
393 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
394 .forEach(prevLocation -> revokeL2Multicast(prevLocation.deviceId(),
395 srService.getPairLocalPort(
396 prevLocation.deviceId()).get(),
397 xconnectVlan,
398 Collections.singletonList(newLocation.port()))
399 );
400
401 // Remove pair-port from vlan's flooding group at dual home restored device,if needed.
402 if (!hasAccessPortInMulticastGroup(newLocation.deviceId(),
403 xconnectVlan,
404 pairLocalPort.get())) {
405 updateL2Flooding(newLocation.deviceId(),
406 pairLocalPort.get(),
407 xconnectVlan,
408 false);
409
410 // Clean L2 multicast group at pair-device; also update store.
411 cleanupL2MulticastRule(pairDeviceId.get(),
412 srService.getPairLocalPort(pairDeviceId.get()).get(),
413 xconnectVlan,
414 false);
415 }
416 });
417 }
418 });
419 break;
420
421 default:
422 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
423 break;
424 }
425 });
426 }
427 }
428
Charles Chan3e56d9f2018-09-21 11:29:12 -0700429 private void init(DeviceId deviceId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700430 getXconnects().stream()
431 .filter(desc -> desc.key().deviceId().equals(deviceId))
432 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
433 }
434
Charles Chan3e56d9f2018-09-21 11:29:12 -0700435 private void cleanup(DeviceId deviceId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700436 xconnectNextObjStore.entrySet().stream()
437 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
438 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
439 log.debug("{} is removed from xConnectNextObjStore", deviceId);
440 }
441
442 /**
443 * Populates XConnect groups and flows for given key.
444 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530445 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700446 * @param ports a set of ports to be cross-connected
447 */
448 private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
449 if (!mastershipService.isLocalMaster(key.deviceId())) {
450 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
451 return;
452 }
453
Charles Chanc7b3c452018-06-19 20:31:57 -0700454 populateFilter(key, ports);
455 populateFwd(key, populateNext(key, ports));
456 populateAcl(key);
457 }
458
459 /**
460 * Populates filtering objectives for given XConnect.
461 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530462 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700463 * @param ports XConnect ports
464 */
465 private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
466 ports.forEach(port -> {
467 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
468 ObjectiveContext context = new DefaultObjectiveContext(
469 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530470 key, port),
Charles Chanc7b3c452018-06-19 20:31:57 -0700471 (objective, error) ->
472 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530473 key, port, error));
Charles Chanc7b3c452018-06-19 20:31:57 -0700474 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
475 });
476 }
477
478 /**
479 * Populates next objectives for given XConnect.
480 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530481 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700482 * @param ports XConnect ports
483 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700484 private int populateNext(XconnectKey key, Set<PortNumber> ports) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700485 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700486 int nextId = xconnectNextObjStore.get(key).value();
487 log.debug("NextObj for {} found, id={}", key, nextId);
488 return nextId;
Charles Chanc7b3c452018-06-19 20:31:57 -0700489 } else {
490 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
491 ObjectiveContext nextContext = new DefaultObjectiveContext(
492 // To serialize this with kryo
493 (Serializable & Consumer<Objective>) (objective) ->
494 log.debug("XConnect NextObj for {} added", key),
Charles Chan55b806f2018-08-23 14:30:33 -0700495 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
496 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
497 srService.invalidateNextObj(objective.id());
498 });
Charles Chan3e56d9f2018-09-21 11:29:12 -0700499 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chanc7b3c452018-06-19 20:31:57 -0700500 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan3e56d9f2018-09-21 11:29:12 -0700501 xconnectNextObjStore.put(key, nextObj.id());
Charles Chanc7b3c452018-06-19 20:31:57 -0700502 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan3e56d9f2018-09-21 11:29:12 -0700503 return nextObj.id();
Charles Chanc7b3c452018-06-19 20:31:57 -0700504 }
Charles Chanc7b3c452018-06-19 20:31:57 -0700505 }
506
507 /**
508 * Populates bridging forwarding objectives for given XConnect.
509 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530510 * @param key XConnect store key
Charles Chan3e56d9f2018-09-21 11:29:12 -0700511 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700512 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700513 private void populateFwd(XconnectKey key, int nextId) {
514 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700515 ObjectiveContext fwdContext = new DefaultObjectiveContext(
516 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
517 (objective, error) ->
518 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
519 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
520 }
521
522 /**
523 * Populates ACL forwarding objectives for given XConnect.
524 *
525 * @param key XConnect store key
526 */
527 private void populateAcl(XconnectKey key) {
528 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
529 ObjectiveContext aclContext = new DefaultObjectiveContext(
530 (objective) -> log.debug("XConnect AclObj for {} populated", key),
531 (objective, error) ->
532 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
533 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
534 }
535
536 /**
537 * Revokes XConnect groups and flows for given key.
538 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530539 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700540 * @param ports XConnect ports
541 */
542 private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
543 if (!mastershipService.isLocalMaster(key.deviceId())) {
544 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
545 return;
546 }
547
Charles Chanc7b3c452018-06-19 20:31:57 -0700548 revokeFilter(key, ports);
549 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700550 int nextId = xconnectNextObjStore.get(key).value();
551 revokeFwd(key, nextId, null);
552 revokeNext(key, ports, nextId, null);
Charles Chanc7b3c452018-06-19 20:31:57 -0700553 } else {
554 log.warn("NextObj for {} does not exist in the store.", key);
555 }
556 revokeAcl(key);
557 }
558
559 /**
560 * Revokes filtering objectives for given XConnect.
561 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530562 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700563 * @param ports XConnect ports
564 */
565 private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
566 ports.forEach(port -> {
567 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
568 ObjectiveContext context = new DefaultObjectiveContext(
569 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530570 key, port),
Charles Chanc7b3c452018-06-19 20:31:57 -0700571 (objective, error) ->
572 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530573 key, port, error));
Charles Chanc7b3c452018-06-19 20:31:57 -0700574 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
575 });
576 }
577
578 /**
579 * Revokes next objectives for given XConnect.
580 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530581 * @param key XConnect store key
582 * @param ports ports in the XConnect
583 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700584 * @param nextFuture completable future for this next objective operation
585 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700586 private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
Charles Chanc7b3c452018-06-19 20:31:57 -0700587 CompletableFuture<ObjectiveError> nextFuture) {
588 ObjectiveContext context = new ObjectiveContext() {
589 @Override
590 public void onSuccess(Objective objective) {
591 log.debug("Previous NextObj for {} removed", key);
592 if (nextFuture != null) {
593 nextFuture.complete(null);
594 }
595 }
596
597 @Override
598 public void onError(Objective objective, ObjectiveError error) {
599 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
600 if (nextFuture != null) {
601 nextFuture.complete(error);
602 }
Charles Chan55b806f2018-08-23 14:30:33 -0700603 srService.invalidateNextObj(objective.id());
Charles Chanc7b3c452018-06-19 20:31:57 -0700604 }
605 };
Charles Chan3e56d9f2018-09-21 11:29:12 -0700606 flowObjectiveService.next(key.deviceId(), nextObjBuilder(key, ports, nextId).remove(context));
Charles Chanc7b3c452018-06-19 20:31:57 -0700607 xconnectNextObjStore.remove(key);
608 }
609
610 /**
611 * Revokes bridging forwarding objectives for given XConnect.
612 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530613 * @param key XConnect store key
614 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700615 * @param fwdFuture completable future for this forwarding objective operation
616 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700617 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
618 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700619 ObjectiveContext context = new ObjectiveContext() {
620 @Override
621 public void onSuccess(Objective objective) {
622 log.debug("Previous FwdObj for {} removed", key);
623 if (fwdFuture != null) {
624 fwdFuture.complete(null);
625 }
626 }
627
628 @Override
629 public void onError(Objective objective, ObjectiveError error) {
630 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
631 if (fwdFuture != null) {
632 fwdFuture.complete(error);
633 }
634 }
635 };
636 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
637 }
638
639 /**
640 * Revokes ACL forwarding objectives for given XConnect.
641 *
642 * @param key XConnect store key
643 */
644 private void revokeAcl(XconnectKey key) {
645 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
646 ObjectiveContext aclContext = new DefaultObjectiveContext(
647 (objective) -> log.debug("XConnect AclObj for {} populated", key),
648 (objective, error) ->
649 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
650 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
651 }
652
653 /**
654 * Updates XConnect groups and flows for given key.
655 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530656 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700657 * @param prevPorts previous XConnect ports
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530658 * @param ports new XConnect ports
Charles Chanc7b3c452018-06-19 20:31:57 -0700659 */
660 private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
661 Set<PortNumber> ports) {
662 // NOTE: ACL flow doesn't include port information. No need to update it.
663 // Pair port is built-in and thus not going to change. No need to update it.
664
665 // remove old filter
666 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530667 revokeFilter(key,
668 ImmutableSet.of(port)));
Charles Chanc7b3c452018-06-19 20:31:57 -0700669 // install new filter
670 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530671 populateFilter(key,
672 ImmutableSet.of(port)));
Charles Chanc7b3c452018-06-19 20:31:57 -0700673
674 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
675 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
676
677 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700678 int nextId = xconnectNextObjStore.get(key).value();
679 revokeFwd(key, nextId, fwdFuture);
Charles Chanc7b3c452018-06-19 20:31:57 -0700680
681 fwdFuture.thenAcceptAsync(fwdStatus -> {
682 if (fwdStatus == null) {
683 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan3e56d9f2018-09-21 11:29:12 -0700684 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chanc7b3c452018-06-19 20:31:57 -0700685 }
686 });
687
688 nextFuture.thenAcceptAsync(nextStatus -> {
689 if (nextStatus == null) {
690 log.debug("Installing new group and flow for {}", key);
691 populateFwd(key, populateNext(key, ports));
692 }
693 });
694 } else {
695 log.warn("NextObj for {} does not exist in the store.", key);
696 }
697 }
698
699 /**
Charles Chan3e56d9f2018-09-21 11:29:12 -0700700 * Creates a next objective builder for XConnect with given nextId.
Charles Chanc7b3c452018-06-19 20:31:57 -0700701 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530702 * @param key XConnect key
703 * @param ports set of XConnect ports
Charles Chan3e56d9f2018-09-21 11:29:12 -0700704 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700705 * @return next objective builder
706 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700707 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports, int nextId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700708 TrafficSelector metadata =
709 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
710 NextObjective.Builder nextObjBuilder = DefaultNextObjective
711 .builder().withId(nextId)
712 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
713 .withMeta(metadata);
714 ports.forEach(port -> {
715 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
716 tBuilder.setOutput(port);
717 nextObjBuilder.addTreatment(tBuilder.build());
718 });
719 return nextObjBuilder;
720 }
721
722 /**
Charles Chan3e56d9f2018-09-21 11:29:12 -0700723 * Creates a next objective builder for XConnect.
724 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530725 * @param key XConnect key
Charles Chan3e56d9f2018-09-21 11:29:12 -0700726 * @param ports set of XConnect ports
727 * @return next objective builder
728 */
729 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports) {
730 int nextId = flowObjectiveService.allocateNextId();
731 return nextObjBuilder(key, ports, nextId);
732 }
733
734
735 /**
Charles Chanc7b3c452018-06-19 20:31:57 -0700736 * Creates a bridging forwarding objective builder for XConnect.
737 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530738 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700739 * @param nextId next ID of the broadcast group for this XConnect key
740 * @return forwarding objective builder
741 */
742 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
743 /*
744 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
745 * as the VLAN cross-connect broadcast rules
746 */
747 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
748 sbuilder.matchVlanId(key.vlanId());
749 sbuilder.matchEthDst(MacAddress.NONE);
750
751 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
752 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
753 .withSelector(sbuilder.build())
754 .nextStep(nextId)
755 .withPriority(XCONNECT_PRIORITY)
756 .fromApp(appId)
757 .makePermanent();
758 return fob;
759 }
760
761 /**
762 * Creates an ACL forwarding objective builder for XConnect.
763 *
764 * @param vlanId cross connect VLAN id
765 * @return forwarding objective builder
766 */
767 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
768 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
769 sbuilder.matchVlanId(vlanId);
770
771 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
772
773 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
774 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
775 .withSelector(sbuilder.build())
776 .withTreatment(tbuilder.build())
777 .withPriority(XCONNECT_ACL_PRIORITY)
778 .fromApp(appId)
779 .makePermanent();
780 return fob;
781 }
782
783 /**
784 * Creates a filtering objective builder for XConnect.
785 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530786 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700787 * @param port XConnect ports
788 * @return next objective builder
789 */
790 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) {
791 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
792 fob.withKey(Criteria.matchInPort(port))
793 .addCondition(Criteria.matchVlanId(key.vlanId()))
794 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
795 .withPriority(XCONNECT_PRIORITY);
796 return fob.permit().fromApp(appId);
797 }
798
799 /**
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530800 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chanc7b3c452018-06-19 20:31:57 -0700801 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530802 * @param deviceId Device ID
803 * @param port Port details
804 * @param vlanId VLAN ID
805 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chanc7b3c452018-06-19 20:31:57 -0700806 */
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530807 private void updateL2Flooding(DeviceId deviceId, final PortNumber port, VlanId vlanId, boolean install) {
808
809 // Ensure mastership on device
810 if (!mastershipService.isLocalMaster(deviceId)) {
811 return;
Charles Chanc7b3c452018-06-19 20:31:57 -0700812 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530813
814 // Locate L2 flooding group details for given xconnect vlan
815 int nextId = getNextId(deviceId, vlanId);
816 if (nextId == -1) {
817 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
818 "Aborting pair group linking.", vlanId, deviceId);
819 return;
820 }
821
822 // Add pairing-port group to flooding group
823 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
824 // treatment.popVlan();
825 treatment.setOutput(port);
826 ObjectiveContext context = new DefaultObjectiveContext(
827 (objective) ->
828 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
829 vlanId, nextId, deviceId),
830 (objective, error) ->
831 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
832 "Error : {}", vlanId, nextId, deviceId, error)
833 );
834 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
835 .withId(nextId)
836 .withType(NextObjective.Type.BROADCAST)
837 .fromApp(srService.appId())
838 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
839 .addTreatment(treatment.build());
840 if (install) {
841 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
842 } else {
843 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
844 }
845 log.debug("Submitted next objective {} for vlan: {} in device {}",
846 nextId, vlanId, deviceId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700847 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530848
849 /**
850 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
851 * output to given port's L2 mulitcast group.
852 *
853 * @param deviceId Device ID
854 * @param pairPort Pair port number
855 * @param vlanId VLAN ID
856 * @param accessPorts List of access ports to be added into L2 multicast group
857 */
858 private void populateL2Multicast(DeviceId deviceId, final PortNumber pairPort,
859 VlanId vlanId, List<PortNumber> accessPorts) {
860
861 boolean multicastGroupExists = true;
862 int vlanMulticastNextId;
863
864 // Ensure enough rights to program pair device
865 if (!srService.shouldProgram(deviceId)) {
866 return;
867 }
868
869 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
870 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
871 .builder()
872 .withType(NextObjective.Type.BROADCAST)
873 .fromApp(srService.appId())
874 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
875 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
876 vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
877 if (vlanMulticastNextId == -1) {
878 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
879 multicastGroupExists = false;
880 vlanMulticastNextId = flowObjectiveService.allocateNextId();
881 addMulticastGroupNextObjectiveId(deviceId, vlanId, vlanMulticastNextId);
882 vlanMulticastNextObjBuilder.addTreatment(
883 DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
884 );
885 }
886 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
887 final int nextId = vlanMulticastNextId;
888 accessPorts.forEach(p -> {
889 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
890 // Do vlan popup action based on interface configuration
891 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
892 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
893 egressAction.popVlan();
894 }
895 egressAction.setOutput(p);
896 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
897 addMulticastGroupPort(deviceId, vlanId, p);
898 });
899 ObjectiveContext context = new DefaultObjectiveContext(
900 (objective) ->
901 log.debug("L2 multicast group installed/updated. "
902 + "NextObject Id {} on {} for subnet {} ",
903 nextId, deviceId, vlanId),
904 (objective, error) ->
905 log.warn("L2 multicast group failed to install/update. "
906 + " NextObject Id {} on {} for subnet {} : {}",
907 nextId, deviceId, vlanId, error)
908 );
909 if (!multicastGroupExists) {
910 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
911
912 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
913 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
914 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
915 multicastSelector.matchInPort(pairPort);
916 multicastSelector.matchVlanId(vlanId);
917 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
918 .withFlag(ForwardingObjective.Flag.VERSATILE)
919 .nextStep(vlanMulticastNextId)
920 .withSelector(multicastSelector.build())
921 .withPriority(100)
922 .fromApp(srService.appId())
923 .makePermanent();
924 context = new DefaultObjectiveContext(
925 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
926 deviceId,
927 pairPort,
928 vlanId),
929 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
930 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
931 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
932 } else {
933 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
934 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
935 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
936 }
937 }
938
939 /**
940 * Removes access ports from VLAN L2 multicast group on given deviceId.
941 *
942 * @param deviceId Device ID
943 * @param pairPort Pair port number
944 * @param vlanId VLAN ID
945 * @param accessPorts List of access ports to be added into L2 multicast group
946 */
947 private void revokeL2Multicast(DeviceId deviceId, final PortNumber pairPort,
948 VlanId vlanId, List<PortNumber> accessPorts) {
949
950 // Ensure enough rights to program pair device
951 if (!srService.shouldProgram(deviceId)) {
952 return;
953 }
954
955 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
956 if (vlanMulticastNextId == -1) {
957 return;
958 }
959 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
960 .builder()
961 .withType(NextObjective.Type.BROADCAST)
962 .fromApp(srService.appId())
963 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
964 .withId(vlanMulticastNextId);
965 accessPorts.forEach(p -> {
966 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
967 // Do vlan popup action based on interface configuration
968 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
969 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
970 egressAction.popVlan();
971 }
972 egressAction.setOutput(p);
973 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
974 removeMulticastGroupPort(deviceId, vlanId, p);
975 });
976 ObjectiveContext context = new DefaultObjectiveContext(
977 (objective) ->
978 log.debug("L2 multicast group installed/updated. "
979 + "NextObject Id {} on {} for subnet {} ",
980 vlanMulticastNextId, deviceId, vlanId),
981 (objective, error) ->
982 log.warn("L2 multicast group failed to install/update. "
983 + " NextObject Id {} on {} for subnet {} : {}",
984 vlanMulticastNextId, deviceId, vlanId, error)
985 );
986 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
987 }
988
989 /**
990 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
991 * Normally multicast group is not removed if it contains access ports; which can be forced
992 * by "force" flag
993 *
994 * @param deviceId Device ID
995 * @param pairPort Pair port number
996 * @param vlanId VLAN ID
997 * @param force Forceful removal
998 */
999 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1000
1001 // Ensure enough rights to program pair device
1002 if (!srService.shouldProgram(deviceId)) {
1003 return;
1004 }
1005
1006 // Ensure L2 multicast group doesn't contain access ports
1007 if (hasAccessPortInMulticastGroup(deviceId, vlanId, pairPort) && !force) {
1008 return;
1009 }
1010
1011 // Load L2 multicast group details
1012 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
1013 if (vlanMulticastNextId == -1) {
1014 return;
1015 }
1016
1017 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1018 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1019 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1020 l2MulticastSelector.matchInPort(pairPort);
1021 l2MulticastSelector.matchVlanId(vlanId);
1022 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1023 .withFlag(ForwardingObjective.Flag.VERSATILE)
1024 .nextStep(vlanMulticastNextId)
1025 .withSelector(l2MulticastSelector.build())
1026 .withPriority(100)
1027 .fromApp(srService.appId())
1028 .makePermanent();
1029 ObjectiveContext context = new DefaultObjectiveContext(
1030 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1031 pairPort, vlanId),
1032 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1033 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1034 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1035
1036 // Step 2 : Clear L2 multicast group associated with vlan
1037 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1038 .builder()
1039 .withId(vlanMulticastNextId)
1040 .withType(NextObjective.Type.BROADCAST)
1041 .fromApp(srService.appId())
1042 .withMeta(DefaultTrafficSelector.builder()
1043 .matchVlanId(vlanId)
1044 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1045 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1046 context = new DefaultObjectiveContext(
1047 (objective) ->
1048 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1049 vlanMulticastNextId, deviceId, vlanId),
1050 (objective, error) ->
1051 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1052 vlanMulticastNextId, deviceId, vlanId, error)
1053 );
1054 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1055
1056 // Finally clear store.
1057 removeMulticastGroup(deviceId, vlanId);
1058 }
1059
1060 private boolean isMulticastGroupExists(DeviceId deviceId, VlanId vlanId) {
1061 return xconnectMulticastNextStore.asJavaMap().entrySet().stream()
1062 .anyMatch(e -> e.getKey().deviceId().equals(deviceId) &&
1063 e.getKey().vlanId().equals(vlanId));
1064 }
1065
1066 private int getMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
1067 Optional<Integer> nextId
1068 = xconnectMulticastNextStore.asJavaMap().entrySet().stream()
1069 .filter(e -> e.getKey().deviceId().equals(deviceId) &&
1070 e.getKey().vlanId().equals(vlanId))
1071 .findFirst()
1072 .map(Map.Entry::getValue);
1073 return nextId.orElse(-1);
1074 }
1075
1076 private void addMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId, int nextId) {
1077 if (nextId == -1) {
1078 return;
1079 }
1080 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1081 xconnectMulticastNextStore.put(key, nextId);
1082
1083 // Update port store with empty entry.
1084 xconnectMulticastPortsStore.put(key, new ArrayList<PortNumber>());
1085 }
1086
1087 private void addMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
1088 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1089 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1090 ports.add(port);
1091 xconnectMulticastPortsStore.put(key, ports);
1092 }
1093
1094 private void removeMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
1095 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1096 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1097 ports.remove(port);
1098 xconnectMulticastPortsStore.put(key, ports);
1099 }
1100
1101 private void removeMulticastGroup(DeviceId deviceId, VlanId vlanId) {
1102 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1103 xconnectMulticastPortsStore.remove(key);
1104 xconnectMulticastNextStore.remove(key);
1105 }
1106
1107 private boolean hasAccessPortInMulticastGroup(DeviceId deviceId, VlanId vlanId, PortNumber pairPort) {
1108 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1109 if (!xconnectMulticastPortsStore.containsKey(key)) {
1110 return false;
1111 }
1112 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1113 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1114 }
1115
Charles Chanc7b3c452018-06-19 20:31:57 -07001116}