blob: 696b811c622d805d255a1ff6199074e8a593c023 [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
18import com.google.common.collect.ImmutableSet;
19import com.google.common.collect.Sets;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onlab.util.KryoNamespace;
29import org.onosproject.codec.CodecService;
30import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
32import org.onosproject.mastership.MastershipService;
33import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.PortNumber;
36import org.onosproject.net.config.NetworkConfigService;
37import org.onosproject.net.device.DeviceEvent;
38import org.onosproject.net.device.DeviceListener;
39import org.onosproject.net.device.DeviceService;
40import org.onosproject.net.flow.DefaultTrafficSelector;
41import org.onosproject.net.flow.DefaultTrafficTreatment;
42import org.onosproject.net.flow.TrafficSelector;
43import org.onosproject.net.flow.TrafficTreatment;
44import org.onosproject.net.flow.criteria.Criteria;
45import org.onosproject.net.flowobjective.DefaultFilteringObjective;
46import org.onosproject.net.flowobjective.DefaultForwardingObjective;
47import org.onosproject.net.flowobjective.DefaultNextObjective;
48import org.onosproject.net.flowobjective.DefaultObjectiveContext;
49import org.onosproject.net.flowobjective.FilteringObjective;
50import org.onosproject.net.flowobjective.FlowObjectiveService;
51import org.onosproject.net.flowobjective.ForwardingObjective;
52import org.onosproject.net.flowobjective.NextObjective;
53import org.onosproject.net.flowobjective.Objective;
54import org.onosproject.net.flowobjective.ObjectiveContext;
55import org.onosproject.net.flowobjective.ObjectiveError;
56import org.onosproject.segmentrouting.SegmentRoutingService;
57import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
58import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
59import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
60import org.onosproject.segmentrouting.xconnect.api.XconnectService;
61import org.onosproject.store.serializers.KryoNamespaces;
62import org.onosproject.store.service.ConsistentMap;
63import org.onosproject.store.service.MapEvent;
64import org.onosproject.store.service.MapEventListener;
65import org.onosproject.store.service.Serializer;
66import org.onosproject.store.service.StorageService;
67import org.onosproject.store.service.Versioned;
68import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
70
71import java.io.Serializable;
72import java.util.Set;
73import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -070074import java.util.concurrent.ExecutorService;
75import java.util.concurrent.Executors;
Charles Chan8d316332018-06-19 20:31:57 -070076import java.util.function.BiConsumer;
77import java.util.function.Consumer;
78import java.util.stream.Collectors;
79
Charles Chan56542b62018-08-07 12:48:36 -070080import static org.onlab.util.Tools.groupedThreads;
81
Charles Chan8d316332018-06-19 20:31:57 -070082@Service
83@Component(immediate = true)
84public class XconnectManager implements XconnectService {
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 private CoreService coreService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 private CodecService codecService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 private StorageService storageService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 public NetworkConfigService netCfgService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 public DeviceService deviceService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 public FlowObjectiveService flowObjectiveService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 public MastershipService mastershipService;
105
106 @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY)
107 public SegmentRoutingService srService;
108
109 private static final String APP_NAME = "org.onosproject.xconnect";
110 private static final String ERROR_NOT_MASTER = "Not master controller";
111
112 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
113
114 private ApplicationId appId;
115 private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
116 private ConsistentMap<XconnectKey, NextObjective> xconnectNextObjStore;
117
118 private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
119 private final DeviceListener deviceListener = new InternalDeviceListener();
120
Charles Chan56542b62018-08-07 12:48:36 -0700121 private ExecutorService deviceEventExecutor;
122
Charles Chan8d316332018-06-19 20:31:57 -0700123 @Activate
124 void activate() {
125 appId = coreService.registerApplication(APP_NAME);
126 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
127
128 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
129 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700130 .register(XconnectManager.class)
Charles Chan8d316332018-06-19 20:31:57 -0700131 .register(XconnectKey.class);
132
133 xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
134 .withName("onos-sr-xconnect")
135 .withRelaxedReadConsistency()
136 .withSerializer(Serializer.using(serializer.build()))
137 .build();
138 xconnectStore.addListener(xconnectListener);
139
140 xconnectNextObjStore = storageService.<XconnectKey, NextObjective>consistentMapBuilder()
141 .withName("onos-sr-xconnect-next")
142 .withRelaxedReadConsistency()
143 .withSerializer(Serializer.using(serializer.build()))
144 .build();
145
Charles Chan56542b62018-08-07 12:48:36 -0700146 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
147 groupedThreads("sr-xconnect-device-event", "%d", log));
148
Charles Chan8d316332018-06-19 20:31:57 -0700149 deviceService.addListener(deviceListener);
150
151 log.info("Started");
152 }
153
154 @Deactivate
155 void deactivate() {
156 xconnectStore.removeListener(xconnectListener);
157 deviceService.removeListener(deviceListener);
158 codecService.unregisterCodec(XconnectDesc.class);
159
Charles Chan56542b62018-08-07 12:48:36 -0700160 deviceEventExecutor.shutdown();
161
Charles Chan8d316332018-06-19 20:31:57 -0700162 log.info("Stopped");
163 }
164
165 @Override
166 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
167 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
168 deviceId, vlanId, ports);
169 final XconnectKey key = new XconnectKey(deviceId, vlanId);
170 xconnectStore.put(key, ports);
171 }
172
173 @Override
174 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
175 log.info("Removing xconnect. deviceId={}, vlanId={}",
176 deviceId, vlanId);
177 final XconnectKey key = new XconnectKey(deviceId, vlanId);
178 xconnectStore.remove(key);
179 }
180
181 @Override
182 public Set<XconnectDesc> getXconnects() {
183 return xconnectStore.asJavaMap().entrySet().stream()
184 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
185 .collect(Collectors.toSet());
186 }
187
188 @Override
189 public boolean hasXconnect(ConnectPoint cp) {
190 return getXconnects().stream().anyMatch(desc ->
191 desc.key().deviceId().equals(cp.deviceId()) && desc.ports().contains(cp.port())
192 );
193 }
194
195 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<PortNumber>> {
196 @Override
197 public void event(MapEvent<XconnectKey, Set<PortNumber>> event) {
198 XconnectKey key = event.key();
199 Versioned<Set<PortNumber>> ports = event.newValue();
200 Versioned<Set<PortNumber>> oldPorts = event.oldValue();
201
202 switch (event.type()) {
203 case INSERT:
204 populateXConnect(key, ports.value());
205 break;
206 case UPDATE:
207 updateXConnect(key, oldPorts.value(), ports.value());
208 break;
209 case REMOVE:
210 revokeXConnect(key, oldPorts.value());
211 break;
212 default:
213 break;
214 }
215 }
216 }
217
218 private class InternalDeviceListener implements DeviceListener {
219 @Override
220 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700221 deviceEventExecutor.execute(() -> {
222 DeviceId deviceId = event.subject().id();
223 if (!mastershipService.isLocalMaster(deviceId)) {
224 return;
225 }
Charles Chan8d316332018-06-19 20:31:57 -0700226
Charles Chan56542b62018-08-07 12:48:36 -0700227 switch (event.type()) {
228 case DEVICE_ADDED:
229 case DEVICE_AVAILABILITY_CHANGED:
230 case DEVICE_UPDATED:
231 if (deviceService.isAvailable(deviceId)) {
232 init(deviceId);
233 } else {
234 cleanup(deviceId);
235 }
236 break;
237 default:
238 break;
239 }
240 });
Charles Chan8d316332018-06-19 20:31:57 -0700241 }
242 }
243
244 void init(DeviceId deviceId) {
245 getXconnects().stream()
246 .filter(desc -> desc.key().deviceId().equals(deviceId))
247 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
248 }
249
250 void cleanup(DeviceId deviceId) {
251 xconnectNextObjStore.entrySet().stream()
252 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
253 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
254 log.debug("{} is removed from xConnectNextObjStore", deviceId);
255 }
256
257 /**
258 * Populates XConnect groups and flows for given key.
259 *
260 * @param key XConnect key
261 * @param ports a set of ports to be cross-connected
262 */
263 private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
264 if (!mastershipService.isLocalMaster(key.deviceId())) {
265 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
266 return;
267 }
268
269 ports = addPairPort(key.deviceId(), ports);
270 populateFilter(key, ports);
271 populateFwd(key, populateNext(key, ports));
272 populateAcl(key);
273 }
274
275 /**
276 * Populates filtering objectives for given XConnect.
277 *
278 * @param key XConnect store key
279 * @param ports XConnect ports
280 */
281 private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
282 ports.forEach(port -> {
283 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
284 ObjectiveContext context = new DefaultObjectiveContext(
285 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
286 key, port),
287 (objective, error) ->
288 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
289 key, port, error));
290 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
291 });
292 }
293
294 /**
295 * Populates next objectives for given XConnect.
296 *
297 * @param key XConnect store key
298 * @param ports XConnect ports
299 */
300 private NextObjective populateNext(XconnectKey key, Set<PortNumber> ports) {
301 NextObjective nextObj;
302 if (xconnectNextObjStore.containsKey(key)) {
303 nextObj = xconnectNextObjStore.get(key).value();
304 log.debug("NextObj for {} found, id={}", key, nextObj.id());
305 } else {
306 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
307 ObjectiveContext nextContext = new DefaultObjectiveContext(
308 // To serialize this with kryo
309 (Serializable & Consumer<Objective>) (objective) ->
310 log.debug("XConnect NextObj for {} added", key),
311 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) ->
312 log.warn("Failed to add XConnect NextObj for {}: {}", key, error)
313 );
314 nextObj = nextObjBuilder.add(nextContext);
315 flowObjectiveService.next(key.deviceId(), nextObj);
316 xconnectNextObjStore.put(key, nextObj);
317 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
318 }
319 return nextObj;
320 }
321
322 /**
323 * Populates bridging forwarding objectives for given XConnect.
324 *
325 * @param key XConnect store key
326 * @param nextObj next objective
327 */
328 private void populateFwd(XconnectKey key, NextObjective nextObj) {
329 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id());
330 ObjectiveContext fwdContext = new DefaultObjectiveContext(
331 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
332 (objective, error) ->
333 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
334 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
335 }
336
337 /**
338 * Populates ACL forwarding objectives for given XConnect.
339 *
340 * @param key XConnect store key
341 */
342 private void populateAcl(XconnectKey key) {
343 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
344 ObjectiveContext aclContext = new DefaultObjectiveContext(
345 (objective) -> log.debug("XConnect AclObj for {} populated", key),
346 (objective, error) ->
347 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
348 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
349 }
350
351 /**
352 * Revokes XConnect groups and flows for given key.
353 *
354 * @param key XConnect key
355 * @param ports XConnect ports
356 */
357 private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
358 if (!mastershipService.isLocalMaster(key.deviceId())) {
359 log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER);
360 return;
361 }
362
363 ports = addPairPort(key.deviceId(), ports);
364 revokeFilter(key, ports);
365 if (xconnectNextObjStore.containsKey(key)) {
366 NextObjective nextObj = xconnectNextObjStore.get(key).value();
367 revokeFwd(key, nextObj, null);
368 revokeNext(key, nextObj, null);
369 } else {
370 log.warn("NextObj for {} does not exist in the store.", key);
371 }
372 revokeAcl(key);
373 }
374
375 /**
376 * Revokes filtering objectives for given XConnect.
377 *
378 * @param key XConnect store key
379 * @param ports XConnect ports
380 */
381 private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
382 ports.forEach(port -> {
383 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
384 ObjectiveContext context = new DefaultObjectiveContext(
385 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
386 key, port),
387 (objective, error) ->
388 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
389 key, port, error));
390 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
391 });
392 }
393
394 /**
395 * Revokes next objectives for given XConnect.
396 *
397 * @param key XConnect store key
398 * @param nextObj next objective
399 * @param nextFuture completable future for this next objective operation
400 */
401 private void revokeNext(XconnectKey key, NextObjective nextObj,
402 CompletableFuture<ObjectiveError> nextFuture) {
403 ObjectiveContext context = new ObjectiveContext() {
404 @Override
405 public void onSuccess(Objective objective) {
406 log.debug("Previous NextObj for {} removed", key);
407 if (nextFuture != null) {
408 nextFuture.complete(null);
409 }
410 }
411
412 @Override
413 public void onError(Objective objective, ObjectiveError error) {
414 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
415 if (nextFuture != null) {
416 nextFuture.complete(error);
417 }
418 }
419 };
420 flowObjectiveService.next(key.deviceId(),
421 (NextObjective) nextObj.copy().remove(context));
422 xconnectNextObjStore.remove(key);
423 }
424
425 /**
426 * Revokes bridging forwarding objectives for given XConnect.
427 *
428 * @param key XConnect store key
429 * @param nextObj next objective
430 * @param fwdFuture completable future for this forwarding objective operation
431 */
432 private void revokeFwd(XconnectKey key, NextObjective nextObj,
433 CompletableFuture<ObjectiveError> fwdFuture) {
434 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id());
435 ObjectiveContext context = new ObjectiveContext() {
436 @Override
437 public void onSuccess(Objective objective) {
438 log.debug("Previous FwdObj for {} removed", key);
439 if (fwdFuture != null) {
440 fwdFuture.complete(null);
441 }
442 }
443
444 @Override
445 public void onError(Objective objective, ObjectiveError error) {
446 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
447 if (fwdFuture != null) {
448 fwdFuture.complete(error);
449 }
450 }
451 };
452 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
453 }
454
455 /**
456 * Revokes ACL forwarding objectives for given XConnect.
457 *
458 * @param key XConnect store key
459 */
460 private void revokeAcl(XconnectKey key) {
461 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
462 ObjectiveContext aclContext = new DefaultObjectiveContext(
463 (objective) -> log.debug("XConnect AclObj for {} populated", key),
464 (objective, error) ->
465 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
466 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
467 }
468
469 /**
470 * Updates XConnect groups and flows for given key.
471 *
472 * @param key XConnect key
473 * @param prevPorts previous XConnect ports
474 * @param ports new XConnect ports
475 */
476 private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
477 Set<PortNumber> ports) {
478 // NOTE: ACL flow doesn't include port information. No need to update it.
479 // Pair port is built-in and thus not going to change. No need to update it.
480
481 // remove old filter
482 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
483 revokeFilter(key, ImmutableSet.of(port)));
484 // install new filter
485 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
486 populateFilter(key, ImmutableSet.of(port)));
487
488 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
489 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
490
491 if (xconnectNextObjStore.containsKey(key)) {
492 NextObjective nextObj = xconnectNextObjStore.get(key).value();
493 revokeFwd(key, nextObj, fwdFuture);
494
495 fwdFuture.thenAcceptAsync(fwdStatus -> {
496 if (fwdStatus == null) {
497 log.debug("Fwd removed. Now remove group {}", key);
498 revokeNext(key, nextObj, nextFuture);
499 }
500 });
501
502 nextFuture.thenAcceptAsync(nextStatus -> {
503 if (nextStatus == null) {
504 log.debug("Installing new group and flow for {}", key);
505 populateFwd(key, populateNext(key, ports));
506 }
507 });
508 } else {
509 log.warn("NextObj for {} does not exist in the store.", key);
510 }
511 }
512
513 /**
514 * Creates a next objective builder for XConnect.
515 *
516 * @param key XConnect key
517 * @param ports set of XConnect ports
518 * @return next objective builder
519 */
520 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports) {
521 int nextId = flowObjectiveService.allocateNextId();
522 TrafficSelector metadata =
523 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
524 NextObjective.Builder nextObjBuilder = DefaultNextObjective
525 .builder().withId(nextId)
526 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
527 .withMeta(metadata);
528 ports.forEach(port -> {
529 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
530 tBuilder.setOutput(port);
531 nextObjBuilder.addTreatment(tBuilder.build());
532 });
533 return nextObjBuilder;
534 }
535
536 /**
537 * Creates a bridging forwarding objective builder for XConnect.
538 *
539 * @param key XConnect key
540 * @param nextId next ID of the broadcast group for this XConnect key
541 * @return forwarding objective builder
542 */
543 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
544 /*
545 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
546 * as the VLAN cross-connect broadcast rules
547 */
548 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
549 sbuilder.matchVlanId(key.vlanId());
550 sbuilder.matchEthDst(MacAddress.NONE);
551
552 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
553 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
554 .withSelector(sbuilder.build())
555 .nextStep(nextId)
556 .withPriority(XCONNECT_PRIORITY)
557 .fromApp(appId)
558 .makePermanent();
559 return fob;
560 }
561
562 /**
563 * Creates an ACL forwarding objective builder for XConnect.
564 *
565 * @param vlanId cross connect VLAN id
566 * @return forwarding objective builder
567 */
568 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
569 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
570 sbuilder.matchVlanId(vlanId);
571
572 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
573
574 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
575 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
576 .withSelector(sbuilder.build())
577 .withTreatment(tbuilder.build())
578 .withPriority(XCONNECT_ACL_PRIORITY)
579 .fromApp(appId)
580 .makePermanent();
581 return fob;
582 }
583
584 /**
585 * Creates a filtering objective builder for XConnect.
586 *
587 * @param key XConnect key
588 * @param port XConnect ports
589 * @return next objective builder
590 */
591 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) {
592 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
593 fob.withKey(Criteria.matchInPort(port))
594 .addCondition(Criteria.matchVlanId(key.vlanId()))
595 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
596 .withPriority(XCONNECT_PRIORITY);
597 return fob.permit().fromApp(appId);
598 }
599
600 /**
601 * Add pair port to the given set of port.
602 *
603 * @param deviceId device Id
604 * @param ports ports specified in the xconnect config
605 * @return port specified in the xconnect config plus the pair port (if configured)
606 */
607 private Set<PortNumber> addPairPort(DeviceId deviceId, Set<PortNumber> ports) {
608 if (srService == null) {
609 return ports;
610 }
611 Set<PortNumber> newPorts = Sets.newHashSet(ports);
612 srService.getPairLocalPort(deviceId).ifPresent(newPorts::add);
613 return newPorts;
614 }
Charles Chan8d316332018-06-19 20:31:57 -0700615}