blob: be1dafc1d0210842ad842f761593c7eda421b427 [file] [log] [blame]
Charles Chan8d316332018-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 Chan0b1dd7e2018-08-19 19:21:46 -070018import com.google.common.collect.ImmutableMap;
Charles Chan8d316332018-06-19 20:31:57 -070019import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Sets;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053021import org.onlab.packet.Ethernet;
Charles Chan8d316332018-06-19 20:31:57 -070022import org.onlab.packet.MacAddress;
23import org.onlab.packet.VlanId;
24import org.onlab.util.KryoNamespace;
25import org.onosproject.codec.CodecService;
26import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
28import org.onosproject.mastership.MastershipService;
29import org.onosproject.net.ConnectPoint;
30import org.onosproject.net.DeviceId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053031import org.onosproject.net.Host;
32import org.onosproject.net.HostLocation;
Charles Chan8d316332018-06-19 20:31:57 -070033import org.onosproject.net.PortNumber;
34import org.onosproject.net.config.NetworkConfigService;
35import org.onosproject.net.device.DeviceEvent;
36import org.onosproject.net.device.DeviceListener;
37import org.onosproject.net.device.DeviceService;
38import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.flow.DefaultTrafficTreatment;
40import org.onosproject.net.flow.TrafficSelector;
41import org.onosproject.net.flow.TrafficTreatment;
42import org.onosproject.net.flow.criteria.Criteria;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
46import org.onosproject.net.flowobjective.DefaultObjectiveContext;
47import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.FlowObjectiveService;
49import org.onosproject.net.flowobjective.ForwardingObjective;
50import org.onosproject.net.flowobjective.NextObjective;
51import org.onosproject.net.flowobjective.Objective;
52import org.onosproject.net.flowobjective.ObjectiveContext;
53import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053054import org.onosproject.net.host.HostEvent;
55import org.onosproject.net.host.HostListener;
56import org.onosproject.net.host.HostService;
57import org.onosproject.net.intf.InterfaceService;
Charles Chan8d316332018-06-19 20:31:57 -070058import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053059import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan8d316332018-06-19 20:31:57 -070060import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
61import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
62import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
63import org.onosproject.segmentrouting.xconnect.api.XconnectService;
64import org.onosproject.store.serializers.KryoNamespaces;
65import org.onosproject.store.service.ConsistentMap;
66import org.onosproject.store.service.MapEvent;
67import org.onosproject.store.service.MapEventListener;
68import org.onosproject.store.service.Serializer;
69import org.onosproject.store.service.StorageService;
70import org.onosproject.store.service.Versioned;
Ray Milkey2bd24a92018-08-17 14:54:17 -070071import org.osgi.service.component.annotations.Activate;
72import org.osgi.service.component.annotations.Component;
73import org.osgi.service.component.annotations.Deactivate;
74import org.osgi.service.component.annotations.Reference;
75import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chan8d316332018-06-19 20:31:57 -070076import org.slf4j.Logger;
77import org.slf4j.LoggerFactory;
78
79import java.io.Serializable;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053080import java.util.ArrayList;
81import java.util.Collections;
82import java.util.List;
83import java.util.Map;
84import java.util.Optional;
Charles Chan8d316332018-06-19 20:31:57 -070085import java.util.Set;
86import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -070087import java.util.concurrent.ExecutorService;
88import java.util.concurrent.Executors;
Charles Chan8d316332018-06-19 20:31:57 -070089import java.util.function.BiConsumer;
90import java.util.function.Consumer;
91import java.util.stream.Collectors;
92
Charles Chan56542b62018-08-07 12:48:36 -070093import static org.onlab.util.Tools.groupedThreads;
94
Ray Milkey2bd24a92018-08-17 14:54:17 -070095@Component(immediate = true, service = XconnectService.class)
Charles Chan8d316332018-06-19 20:31:57 -070096public class XconnectManager implements XconnectService {
Ray Milkey2bd24a92018-08-17 14:54:17 -070097 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -070098 private CoreService coreService;
99
Ray Milkey2bd24a92018-08-17 14:54:17 -0700100 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700101 private CodecService codecService;
102
Ray Milkey2bd24a92018-08-17 14:54:17 -0700103 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700104 private StorageService storageService;
105
Ray Milkey2bd24a92018-08-17 14:54:17 -0700106 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700107 public NetworkConfigService netCfgService;
108
Ray Milkey2bd24a92018-08-17 14:54:17 -0700109 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700110 public DeviceService deviceService;
111
Ray Milkey2bd24a92018-08-17 14:54:17 -0700112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700113 public FlowObjectiveService flowObjectiveService;
114
Ray Milkey2bd24a92018-08-17 14:54:17 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700116 public MastershipService mastershipService;
117
Ray Milkey2bd24a92018-08-17 14:54:17 -0700118 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chan8d316332018-06-19 20:31:57 -0700119 public SegmentRoutingService srService;
120
Ray Milkey3cad4db2018-10-04 15:13:33 -0700121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530122 public InterfaceService interfaceService;
123
Ray Milkey3cad4db2018-10-04 15:13:33 -0700124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530125 HostService hostService;
126
Charles Chan8d316332018-06-19 20:31:57 -0700127 private static final String APP_NAME = "org.onosproject.xconnect";
128 private static final String ERROR_NOT_MASTER = "Not master controller";
129
130 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
131
132 private ApplicationId appId;
133 private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
Charles Chan1fb65132018-09-21 11:29:12 -0700134 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan8d316332018-06-19 20:31:57 -0700135
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530136 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
137 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
138
Charles Chan8d316332018-06-19 20:31:57 -0700139 private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
140 private final DeviceListener deviceListener = new InternalDeviceListener();
141
Charles Chan56542b62018-08-07 12:48:36 -0700142 private ExecutorService deviceEventExecutor;
143
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530144 private final HostListener hostListener = new InternalHostListener();
145 private ExecutorService hostEventExecutor;
146
147
Charles Chan8d316332018-06-19 20:31:57 -0700148 @Activate
149 void activate() {
150 appId = coreService.registerApplication(APP_NAME);
151 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
152
153 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
154 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700155 .register(XconnectManager.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530156 .register(XconnectKey.class)
157 .register(VlanNextObjectiveStoreKey.class);
Charles Chan8d316332018-06-19 20:31:57 -0700158
159 xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
160 .withName("onos-sr-xconnect")
161 .withRelaxedReadConsistency()
162 .withSerializer(Serializer.using(serializer.build()))
163 .build();
164 xconnectStore.addListener(xconnectListener);
165
Charles Chan1fb65132018-09-21 11:29:12 -0700166 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700167 .withName("onos-sr-xconnect-next")
168 .withRelaxedReadConsistency()
169 .withSerializer(Serializer.using(serializer.build()))
170 .build();
171
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530172 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
173 .withName("onos-sr-xconnect-l2multicast-next")
174 .withSerializer(Serializer.using(serializer.build()))
175 .build();
176 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
177 .withName("onos-sr-xconnect-l2multicast-ports")
178 .withSerializer(Serializer.using(serializer.build()))
179 .build();
180
Charles Chan56542b62018-08-07 12:48:36 -0700181 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
182 groupedThreads("sr-xconnect-device-event", "%d", log));
183
Charles Chan8d316332018-06-19 20:31:57 -0700184 deviceService.addListener(deviceListener);
185
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530186 hostEventExecutor = Executors.newSingleThreadExecutor(
187 groupedThreads("sr-xconnect-host-event", "%d", log));
188
189 hostService.addListener(hostListener);
190
Charles Chan8d316332018-06-19 20:31:57 -0700191 log.info("Started");
192 }
193
194 @Deactivate
195 void deactivate() {
196 xconnectStore.removeListener(xconnectListener);
197 deviceService.removeListener(deviceListener);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530198 hostService.removeListener(hostListener);
Charles Chan8d316332018-06-19 20:31:57 -0700199 codecService.unregisterCodec(XconnectDesc.class);
200
Charles Chan56542b62018-08-07 12:48:36 -0700201 deviceEventExecutor.shutdown();
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530202 hostEventExecutor.shutdown();
Charles Chan56542b62018-08-07 12:48:36 -0700203
Charles Chan8d316332018-06-19 20:31:57 -0700204 log.info("Stopped");
205 }
206
207 @Override
208 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
209 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530210 deviceId, vlanId, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700211 final XconnectKey key = new XconnectKey(deviceId, vlanId);
212 xconnectStore.put(key, ports);
213 }
214
215 @Override
216 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
217 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530218 deviceId, vlanId);
Charles Chan8d316332018-06-19 20:31:57 -0700219 final XconnectKey key = new XconnectKey(deviceId, vlanId);
220 xconnectStore.remove(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530221
222 // Cleanup multicasting support, if any.
223 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
224 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
225 });
226
Charles Chan8d316332018-06-19 20:31:57 -0700227 }
228
229 @Override
230 public Set<XconnectDesc> getXconnects() {
231 return xconnectStore.asJavaMap().entrySet().stream()
232 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
233 .collect(Collectors.toSet());
234 }
235
236 @Override
237 public boolean hasXconnect(ConnectPoint cp) {
238 return getXconnects().stream().anyMatch(desc ->
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530239 desc.key().deviceId().equals(cp.deviceId())
240 && desc.ports().contains(cp.port())
Charles Chan8d316332018-06-19 20:31:57 -0700241 );
242 }
243
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700244 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530245 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
246 return getXconnects().stream()
247 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
248 .map(XconnectDesc::key)
249 .map(XconnectKey::vlanId)
250 .collect(Collectors.toList());
251 }
252
253 @Override
254 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
255 return getXconnects().stream()
256 .anyMatch(desc -> desc.key().deviceId().equals(deviceId) && desc.key().vlanId().equals(vlanId));
257 }
258
259 @Override
Charles Chan1fb65132018-09-21 11:29:12 -0700260 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700261 if (xconnectNextObjStore != null) {
262 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
263 } else {
264 return ImmutableMap.of();
265 }
266 }
267
268 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530269 public int getNextId(final DeviceId deviceId, final VlanId vlanId) {
270 Optional<Integer> nextObjective = getNext().entrySet().stream()
271 .filter(d -> d.getKey().deviceId().equals(deviceId) && d.getKey().vlanId().equals(vlanId))
272 .findFirst()
273 .map(Map.Entry::getValue);
274 return nextObjective.isPresent() ? nextObjective.get() : -1;
275 }
276
277 @Override
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700278 public void removeNextId(int nextId) {
279 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan1fb65132018-09-21 11:29:12 -0700280 if (e.getValue().value() == nextId) {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700281 xconnectNextObjStore.remove(e.getKey());
282 }
283 });
284 }
285
Charles Chan8d316332018-06-19 20:31:57 -0700286 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<PortNumber>> {
287 @Override
288 public void event(MapEvent<XconnectKey, Set<PortNumber>> event) {
289 XconnectKey key = event.key();
290 Versioned<Set<PortNumber>> ports = event.newValue();
291 Versioned<Set<PortNumber>> oldPorts = event.oldValue();
292
293 switch (event.type()) {
294 case INSERT:
295 populateXConnect(key, ports.value());
296 break;
297 case UPDATE:
298 updateXConnect(key, oldPorts.value(), ports.value());
299 break;
300 case REMOVE:
301 revokeXConnect(key, oldPorts.value());
302 break;
303 default:
304 break;
305 }
306 }
307 }
308
309 private class InternalDeviceListener implements DeviceListener {
310 @Override
311 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700312 deviceEventExecutor.execute(() -> {
313 DeviceId deviceId = event.subject().id();
314 if (!mastershipService.isLocalMaster(deviceId)) {
315 return;
316 }
Charles Chan8d316332018-06-19 20:31:57 -0700317
Charles Chan56542b62018-08-07 12:48:36 -0700318 switch (event.type()) {
319 case DEVICE_ADDED:
320 case DEVICE_AVAILABILITY_CHANGED:
321 case DEVICE_UPDATED:
322 if (deviceService.isAvailable(deviceId)) {
323 init(deviceId);
324 } else {
325 cleanup(deviceId);
326 }
327 break;
328 default:
329 break;
330 }
331 });
Charles Chan8d316332018-06-19 20:31:57 -0700332 }
333 }
334
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530335 private class InternalHostListener implements HostListener {
336 @Override
337 public void event(HostEvent event) {
338 hostEventExecutor.execute(() -> {
339
340 switch (event.type()) {
341 case HOST_MOVED:
342 log.trace("Processing host event {}", event);
343
344 Host host = event.subject();
345 Set<HostLocation> prevLocations = event.prevSubject().locations();
346 Set<HostLocation> newLocations = host.locations();
347
348 // Dual-home host port failure
349 // For each old location, in failed and paired devices update L2 vlan groups
350 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
351
352 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
353 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
354
355 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
356 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
357 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
358
359 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
360 prevLocation.port());
361 xconnectVlans.forEach(xconnectVlan -> {
362 // Add single-home host into L2 multicast group at paired device side.
363 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
364 newLocations.stream()
365 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
366 .forEach(location -> populateL2Multicast(location.deviceId(),
367 srService.getPairLocalPort(
368 location.deviceId()).get(),
369 xconnectVlan,
370 Collections.singletonList(
371 location.port())));
372 // Ensure pair-port attached to xconnect vlan flooding group at dual home failed device.
373 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
374 });
375 }
376 });
377
378 // Dual-home host port restoration
379 // For each new location, reverse xconnect loop prevention groups.
380 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
381 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
382 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
383 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
384 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
385
386 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
387 newLocation.port());
388 xconnectVlans.forEach(xconnectVlan -> {
389 // Remove recovered dual homed port from vlan L2 multicast group
390 prevLocations.stream()
391 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
392 .forEach(prevLocation -> revokeL2Multicast(prevLocation.deviceId(),
393 srService.getPairLocalPort(
394 prevLocation.deviceId()).get(),
395 xconnectVlan,
396 Collections.singletonList(newLocation.port()))
397 );
398
399 // Remove pair-port from vlan's flooding group at dual home restored device,if needed.
400 if (!hasAccessPortInMulticastGroup(newLocation.deviceId(),
401 xconnectVlan,
402 pairLocalPort.get())) {
403 updateL2Flooding(newLocation.deviceId(),
404 pairLocalPort.get(),
405 xconnectVlan,
406 false);
407
408 // Clean L2 multicast group at pair-device; also update store.
409 cleanupL2MulticastRule(pairDeviceId.get(),
410 srService.getPairLocalPort(pairDeviceId.get()).get(),
411 xconnectVlan,
412 false);
413 }
414 });
415 }
416 });
417 break;
418
419 default:
420 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
421 break;
422 }
423 });
424 }
425 }
426
Charles Chan1fb65132018-09-21 11:29:12 -0700427 private void init(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700428 getXconnects().stream()
429 .filter(desc -> desc.key().deviceId().equals(deviceId))
430 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
431 }
432
Charles Chan1fb65132018-09-21 11:29:12 -0700433 private void cleanup(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700434 xconnectNextObjStore.entrySet().stream()
435 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
436 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
437 log.debug("{} is removed from xConnectNextObjStore", deviceId);
438 }
439
440 /**
441 * Populates XConnect groups and flows for given key.
442 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530443 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700444 * @param ports a set of ports to be cross-connected
445 */
446 private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
447 if (!mastershipService.isLocalMaster(key.deviceId())) {
448 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
449 return;
450 }
451
Charles Chan8d316332018-06-19 20:31:57 -0700452 populateFilter(key, ports);
453 populateFwd(key, populateNext(key, ports));
454 populateAcl(key);
455 }
456
457 /**
458 * Populates filtering objectives for given XConnect.
459 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530460 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700461 * @param ports XConnect ports
462 */
463 private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
464 ports.forEach(port -> {
465 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
466 ObjectiveContext context = new DefaultObjectiveContext(
467 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530468 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700469 (objective, error) ->
470 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530471 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700472 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
473 });
474 }
475
476 /**
477 * Populates next objectives for given XConnect.
478 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530479 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700480 * @param ports XConnect ports
481 */
Charles Chan1fb65132018-09-21 11:29:12 -0700482 private int populateNext(XconnectKey key, Set<PortNumber> ports) {
Charles Chan8d316332018-06-19 20:31:57 -0700483 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan1fb65132018-09-21 11:29:12 -0700484 int nextId = xconnectNextObjStore.get(key).value();
485 log.debug("NextObj for {} found, id={}", key, nextId);
486 return nextId;
Charles Chan8d316332018-06-19 20:31:57 -0700487 } else {
488 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
489 ObjectiveContext nextContext = new DefaultObjectiveContext(
490 // To serialize this with kryo
491 (Serializable & Consumer<Objective>) (objective) ->
492 log.debug("XConnect NextObj for {} added", key),
Charles Chanfacfbef2018-08-23 14:30:33 -0700493 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
494 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
495 srService.invalidateNextObj(objective.id());
496 });
Charles Chan1fb65132018-09-21 11:29:12 -0700497 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan8d316332018-06-19 20:31:57 -0700498 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan1fb65132018-09-21 11:29:12 -0700499 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan8d316332018-06-19 20:31:57 -0700500 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan1fb65132018-09-21 11:29:12 -0700501 return nextObj.id();
Charles Chan8d316332018-06-19 20:31:57 -0700502 }
Charles Chan8d316332018-06-19 20:31:57 -0700503 }
504
505 /**
506 * Populates bridging forwarding objectives for given XConnect.
507 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530508 * @param key XConnect store key
Charles Chan1fb65132018-09-21 11:29:12 -0700509 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700510 */
Charles Chan1fb65132018-09-21 11:29:12 -0700511 private void populateFwd(XconnectKey key, int nextId) {
512 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700513 ObjectiveContext fwdContext = new DefaultObjectiveContext(
514 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
515 (objective, error) ->
516 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
517 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
518 }
519
520 /**
521 * Populates ACL forwarding objectives for given XConnect.
522 *
523 * @param key XConnect store key
524 */
525 private void populateAcl(XconnectKey key) {
526 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
527 ObjectiveContext aclContext = new DefaultObjectiveContext(
528 (objective) -> log.debug("XConnect AclObj for {} populated", key),
529 (objective, error) ->
530 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
531 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
532 }
533
534 /**
535 * Revokes XConnect groups and flows for given key.
536 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530537 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700538 * @param ports XConnect ports
539 */
540 private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
541 if (!mastershipService.isLocalMaster(key.deviceId())) {
542 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
543 return;
544 }
545
Charles Chan8d316332018-06-19 20:31:57 -0700546 revokeFilter(key, ports);
547 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan1fb65132018-09-21 11:29:12 -0700548 int nextId = xconnectNextObjStore.get(key).value();
549 revokeFwd(key, nextId, null);
550 revokeNext(key, ports, nextId, null);
Charles Chan8d316332018-06-19 20:31:57 -0700551 } else {
552 log.warn("NextObj for {} does not exist in the store.", key);
553 }
554 revokeAcl(key);
555 }
556
557 /**
558 * Revokes filtering objectives for given XConnect.
559 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530560 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700561 * @param ports XConnect ports
562 */
563 private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
564 ports.forEach(port -> {
565 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
566 ObjectiveContext context = new DefaultObjectiveContext(
567 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530568 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700569 (objective, error) ->
570 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530571 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700572 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
573 });
574 }
575
576 /**
577 * Revokes next objectives for given XConnect.
578 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530579 * @param key XConnect store key
580 * @param ports ports in the XConnect
581 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700582 * @param nextFuture completable future for this next objective operation
583 */
Charles Chan1fb65132018-09-21 11:29:12 -0700584 private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
Charles Chan8d316332018-06-19 20:31:57 -0700585 CompletableFuture<ObjectiveError> nextFuture) {
586 ObjectiveContext context = new ObjectiveContext() {
587 @Override
588 public void onSuccess(Objective objective) {
589 log.debug("Previous NextObj for {} removed", key);
590 if (nextFuture != null) {
591 nextFuture.complete(null);
592 }
593 }
594
595 @Override
596 public void onError(Objective objective, ObjectiveError error) {
597 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
598 if (nextFuture != null) {
599 nextFuture.complete(error);
600 }
Charles Chanfacfbef2018-08-23 14:30:33 -0700601 srService.invalidateNextObj(objective.id());
Charles Chan8d316332018-06-19 20:31:57 -0700602 }
603 };
Charles Chan1fb65132018-09-21 11:29:12 -0700604 flowObjectiveService.next(key.deviceId(), nextObjBuilder(key, ports, nextId).remove(context));
Charles Chan8d316332018-06-19 20:31:57 -0700605 xconnectNextObjStore.remove(key);
606 }
607
608 /**
609 * Revokes bridging forwarding objectives for given XConnect.
610 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530611 * @param key XConnect store key
612 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700613 * @param fwdFuture completable future for this forwarding objective operation
614 */
Charles Chan1fb65132018-09-21 11:29:12 -0700615 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
616 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700617 ObjectiveContext context = new ObjectiveContext() {
618 @Override
619 public void onSuccess(Objective objective) {
620 log.debug("Previous FwdObj for {} removed", key);
621 if (fwdFuture != null) {
622 fwdFuture.complete(null);
623 }
624 }
625
626 @Override
627 public void onError(Objective objective, ObjectiveError error) {
628 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
629 if (fwdFuture != null) {
630 fwdFuture.complete(error);
631 }
632 }
633 };
634 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
635 }
636
637 /**
638 * Revokes ACL forwarding objectives for given XConnect.
639 *
640 * @param key XConnect store key
641 */
642 private void revokeAcl(XconnectKey key) {
643 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
644 ObjectiveContext aclContext = new DefaultObjectiveContext(
645 (objective) -> log.debug("XConnect AclObj for {} populated", key),
646 (objective, error) ->
647 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
648 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
649 }
650
651 /**
652 * Updates XConnect groups and flows for given key.
653 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530654 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700655 * @param prevPorts previous XConnect ports
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530656 * @param ports new XConnect ports
Charles Chan8d316332018-06-19 20:31:57 -0700657 */
658 private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
659 Set<PortNumber> ports) {
660 // NOTE: ACL flow doesn't include port information. No need to update it.
661 // Pair port is built-in and thus not going to change. No need to update it.
662
663 // remove old filter
664 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530665 revokeFilter(key,
666 ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700667 // install new filter
668 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530669 populateFilter(key,
670 ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700671
672 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
673 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
674
675 if (xconnectNextObjStore.containsKey(key)) {
Charles Chan1fb65132018-09-21 11:29:12 -0700676 int nextId = xconnectNextObjStore.get(key).value();
677 revokeFwd(key, nextId, fwdFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700678
679 fwdFuture.thenAcceptAsync(fwdStatus -> {
680 if (fwdStatus == null) {
681 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan1fb65132018-09-21 11:29:12 -0700682 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700683 }
684 });
685
686 nextFuture.thenAcceptAsync(nextStatus -> {
687 if (nextStatus == null) {
688 log.debug("Installing new group and flow for {}", key);
689 populateFwd(key, populateNext(key, ports));
690 }
691 });
692 } else {
693 log.warn("NextObj for {} does not exist in the store.", key);
694 }
695 }
696
697 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700698 * Creates a next objective builder for XConnect with given nextId.
Charles Chan8d316332018-06-19 20:31:57 -0700699 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530700 * @param key XConnect key
701 * @param ports set of XConnect ports
Charles Chan1fb65132018-09-21 11:29:12 -0700702 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700703 * @return next objective builder
704 */
Charles Chan1fb65132018-09-21 11:29:12 -0700705 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports, int nextId) {
Charles Chan8d316332018-06-19 20:31:57 -0700706 TrafficSelector metadata =
707 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
708 NextObjective.Builder nextObjBuilder = DefaultNextObjective
709 .builder().withId(nextId)
710 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
711 .withMeta(metadata);
712 ports.forEach(port -> {
713 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
714 tBuilder.setOutput(port);
715 nextObjBuilder.addTreatment(tBuilder.build());
716 });
717 return nextObjBuilder;
718 }
719
720 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700721 * Creates a next objective builder for XConnect.
722 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530723 * @param key XConnect key
Charles Chan1fb65132018-09-21 11:29:12 -0700724 * @param ports set of XConnect ports
725 * @return next objective builder
726 */
727 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports) {
728 int nextId = flowObjectiveService.allocateNextId();
729 return nextObjBuilder(key, ports, nextId);
730 }
731
732
733 /**
Charles Chan8d316332018-06-19 20:31:57 -0700734 * Creates a bridging forwarding objective builder for XConnect.
735 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530736 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700737 * @param nextId next ID of the broadcast group for this XConnect key
738 * @return forwarding objective builder
739 */
740 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
741 /*
742 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
743 * as the VLAN cross-connect broadcast rules
744 */
745 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
746 sbuilder.matchVlanId(key.vlanId());
747 sbuilder.matchEthDst(MacAddress.NONE);
748
749 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
750 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
751 .withSelector(sbuilder.build())
752 .nextStep(nextId)
753 .withPriority(XCONNECT_PRIORITY)
754 .fromApp(appId)
755 .makePermanent();
756 return fob;
757 }
758
759 /**
760 * Creates an ACL forwarding objective builder for XConnect.
761 *
762 * @param vlanId cross connect VLAN id
763 * @return forwarding objective builder
764 */
765 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
766 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
767 sbuilder.matchVlanId(vlanId);
768
769 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
770
771 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
772 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
773 .withSelector(sbuilder.build())
774 .withTreatment(tbuilder.build())
775 .withPriority(XCONNECT_ACL_PRIORITY)
776 .fromApp(appId)
777 .makePermanent();
778 return fob;
779 }
780
781 /**
782 * Creates a filtering objective builder for XConnect.
783 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530784 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700785 * @param port XConnect ports
786 * @return next objective builder
787 */
788 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) {
789 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
790 fob.withKey(Criteria.matchInPort(port))
791 .addCondition(Criteria.matchVlanId(key.vlanId()))
792 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
793 .withPriority(XCONNECT_PRIORITY);
794 return fob.permit().fromApp(appId);
795 }
796
797 /**
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530798 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan8d316332018-06-19 20:31:57 -0700799 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530800 * @param deviceId Device ID
801 * @param port Port details
802 * @param vlanId VLAN ID
803 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan8d316332018-06-19 20:31:57 -0700804 */
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530805 private void updateL2Flooding(DeviceId deviceId, final PortNumber port, VlanId vlanId, boolean install) {
806
807 // Ensure mastership on device
808 if (!mastershipService.isLocalMaster(deviceId)) {
809 return;
Charles Chan8d316332018-06-19 20:31:57 -0700810 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530811
812 // Locate L2 flooding group details for given xconnect vlan
813 int nextId = getNextId(deviceId, vlanId);
814 if (nextId == -1) {
815 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
816 "Aborting pair group linking.", vlanId, deviceId);
817 return;
818 }
819
820 // Add pairing-port group to flooding group
821 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
822 // treatment.popVlan();
823 treatment.setOutput(port);
824 ObjectiveContext context = new DefaultObjectiveContext(
825 (objective) ->
826 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
827 vlanId, nextId, deviceId),
828 (objective, error) ->
829 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
830 "Error : {}", vlanId, nextId, deviceId, error)
831 );
832 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
833 .withId(nextId)
834 .withType(NextObjective.Type.BROADCAST)
835 .fromApp(srService.appId())
836 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
837 .addTreatment(treatment.build());
838 if (install) {
839 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
840 } else {
841 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
842 }
843 log.debug("Submitted next objective {} for vlan: {} in device {}",
844 nextId, vlanId, deviceId);
Charles Chan8d316332018-06-19 20:31:57 -0700845 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530846
847 /**
848 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
849 * output to given port's L2 mulitcast group.
850 *
851 * @param deviceId Device ID
852 * @param pairPort Pair port number
853 * @param vlanId VLAN ID
854 * @param accessPorts List of access ports to be added into L2 multicast group
855 */
856 private void populateL2Multicast(DeviceId deviceId, final PortNumber pairPort,
857 VlanId vlanId, List<PortNumber> accessPorts) {
858
859 boolean multicastGroupExists = true;
860 int vlanMulticastNextId;
861
862 // Ensure enough rights to program pair device
863 if (!srService.shouldProgram(deviceId)) {
864 return;
865 }
866
867 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
868 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
869 .builder()
870 .withType(NextObjective.Type.BROADCAST)
871 .fromApp(srService.appId())
872 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
873 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
874 vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
875 if (vlanMulticastNextId == -1) {
876 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
877 multicastGroupExists = false;
878 vlanMulticastNextId = flowObjectiveService.allocateNextId();
879 addMulticastGroupNextObjectiveId(deviceId, vlanId, vlanMulticastNextId);
880 vlanMulticastNextObjBuilder.addTreatment(
881 DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
882 );
883 }
884 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
885 final int nextId = vlanMulticastNextId;
886 accessPorts.forEach(p -> {
887 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
888 // Do vlan popup action based on interface configuration
889 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
890 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
891 egressAction.popVlan();
892 }
893 egressAction.setOutput(p);
894 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
895 addMulticastGroupPort(deviceId, vlanId, p);
896 });
897 ObjectiveContext context = new DefaultObjectiveContext(
898 (objective) ->
899 log.debug("L2 multicast group installed/updated. "
900 + "NextObject Id {} on {} for subnet {} ",
901 nextId, deviceId, vlanId),
902 (objective, error) ->
903 log.warn("L2 multicast group failed to install/update. "
904 + " NextObject Id {} on {} for subnet {} : {}",
905 nextId, deviceId, vlanId, error)
906 );
907 if (!multicastGroupExists) {
908 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
909
910 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
911 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
912 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
913 multicastSelector.matchInPort(pairPort);
914 multicastSelector.matchVlanId(vlanId);
915 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
916 .withFlag(ForwardingObjective.Flag.VERSATILE)
917 .nextStep(vlanMulticastNextId)
918 .withSelector(multicastSelector.build())
919 .withPriority(100)
920 .fromApp(srService.appId())
921 .makePermanent();
922 context = new DefaultObjectiveContext(
923 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
924 deviceId,
925 pairPort,
926 vlanId),
927 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
928 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
929 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
930 } else {
931 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
932 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
933 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
934 }
935 }
936
937 /**
938 * Removes access ports from VLAN L2 multicast group on given deviceId.
939 *
940 * @param deviceId Device ID
941 * @param pairPort Pair port number
942 * @param vlanId VLAN ID
943 * @param accessPorts List of access ports to be added into L2 multicast group
944 */
945 private void revokeL2Multicast(DeviceId deviceId, final PortNumber pairPort,
946 VlanId vlanId, List<PortNumber> accessPorts) {
947
948 // Ensure enough rights to program pair device
949 if (!srService.shouldProgram(deviceId)) {
950 return;
951 }
952
953 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
954 if (vlanMulticastNextId == -1) {
955 return;
956 }
957 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
958 .builder()
959 .withType(NextObjective.Type.BROADCAST)
960 .fromApp(srService.appId())
961 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
962 .withId(vlanMulticastNextId);
963 accessPorts.forEach(p -> {
964 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
965 // Do vlan popup action based on interface configuration
966 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
967 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
968 egressAction.popVlan();
969 }
970 egressAction.setOutput(p);
971 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
972 removeMulticastGroupPort(deviceId, vlanId, p);
973 });
974 ObjectiveContext context = new DefaultObjectiveContext(
975 (objective) ->
976 log.debug("L2 multicast group installed/updated. "
977 + "NextObject Id {} on {} for subnet {} ",
978 vlanMulticastNextId, deviceId, vlanId),
979 (objective, error) ->
980 log.warn("L2 multicast group failed to install/update. "
981 + " NextObject Id {} on {} for subnet {} : {}",
982 vlanMulticastNextId, deviceId, vlanId, error)
983 );
984 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
985 }
986
987 /**
988 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
989 * Normally multicast group is not removed if it contains access ports; which can be forced
990 * by "force" flag
991 *
992 * @param deviceId Device ID
993 * @param pairPort Pair port number
994 * @param vlanId VLAN ID
995 * @param force Forceful removal
996 */
997 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
998
999 // Ensure enough rights to program pair device
1000 if (!srService.shouldProgram(deviceId)) {
1001 return;
1002 }
1003
1004 // Ensure L2 multicast group doesn't contain access ports
1005 if (hasAccessPortInMulticastGroup(deviceId, vlanId, pairPort) && !force) {
1006 return;
1007 }
1008
1009 // Load L2 multicast group details
1010 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
1011 if (vlanMulticastNextId == -1) {
1012 return;
1013 }
1014
1015 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1016 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1017 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1018 l2MulticastSelector.matchInPort(pairPort);
1019 l2MulticastSelector.matchVlanId(vlanId);
1020 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1021 .withFlag(ForwardingObjective.Flag.VERSATILE)
1022 .nextStep(vlanMulticastNextId)
1023 .withSelector(l2MulticastSelector.build())
1024 .withPriority(100)
1025 .fromApp(srService.appId())
1026 .makePermanent();
1027 ObjectiveContext context = new DefaultObjectiveContext(
1028 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1029 pairPort, vlanId),
1030 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1031 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1032 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1033
1034 // Step 2 : Clear L2 multicast group associated with vlan
1035 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1036 .builder()
1037 .withId(vlanMulticastNextId)
1038 .withType(NextObjective.Type.BROADCAST)
1039 .fromApp(srService.appId())
1040 .withMeta(DefaultTrafficSelector.builder()
1041 .matchVlanId(vlanId)
1042 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1043 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1044 context = new DefaultObjectiveContext(
1045 (objective) ->
1046 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1047 vlanMulticastNextId, deviceId, vlanId),
1048 (objective, error) ->
1049 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1050 vlanMulticastNextId, deviceId, vlanId, error)
1051 );
1052 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1053
1054 // Finally clear store.
1055 removeMulticastGroup(deviceId, vlanId);
1056 }
1057
1058 private boolean isMulticastGroupExists(DeviceId deviceId, VlanId vlanId) {
1059 return xconnectMulticastNextStore.asJavaMap().entrySet().stream()
1060 .anyMatch(e -> e.getKey().deviceId().equals(deviceId) &&
1061 e.getKey().vlanId().equals(vlanId));
1062 }
1063
1064 private int getMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
1065 Optional<Integer> nextId
1066 = xconnectMulticastNextStore.asJavaMap().entrySet().stream()
1067 .filter(e -> e.getKey().deviceId().equals(deviceId) &&
1068 e.getKey().vlanId().equals(vlanId))
1069 .findFirst()
1070 .map(Map.Entry::getValue);
1071 return nextId.orElse(-1);
1072 }
1073
1074 private void addMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId, int nextId) {
1075 if (nextId == -1) {
1076 return;
1077 }
1078 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1079 xconnectMulticastNextStore.put(key, nextId);
1080
1081 // Update port store with empty entry.
1082 xconnectMulticastPortsStore.put(key, new ArrayList<PortNumber>());
1083 }
1084
1085 private void addMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
1086 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1087 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1088 ports.add(port);
1089 xconnectMulticastPortsStore.put(key, ports);
1090 }
1091
1092 private void removeMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
1093 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1094 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1095 ports.remove(port);
1096 xconnectMulticastPortsStore.put(key, ports);
1097 }
1098
1099 private void removeMulticastGroup(DeviceId deviceId, VlanId vlanId) {
1100 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1101 xconnectMulticastPortsStore.remove(key);
1102 xconnectMulticastNextStore.remove(key);
1103 }
1104
1105 private boolean hasAccessPortInMulticastGroup(DeviceId deviceId, VlanId vlanId, PortNumber pairPort) {
1106 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1107 if (!xconnectMulticastPortsStore.containsKey(key)) {
1108 return false;
1109 }
1110 List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
1111 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1112 }
1113
Charles Chan8d316332018-06-19 20:31:57 -07001114}