blob: 2dc9de25044525f75fb4738fe5434b22089e92a5 [file] [log] [blame]
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001/*
2 * Copyright 2016-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 */
16
17package org.onosproject.segmentrouting.pwaas;
18
19import com.google.common.collect.Iterables;
20import com.google.common.collect.Lists;
21import org.apache.commons.lang3.RandomUtils;
22import org.onlab.packet.Ethernet;
23import org.onlab.packet.MacAddress;
24import org.onlab.packet.MplsLabel;
25import org.onlab.packet.VlanId;
26import org.onlab.util.KryoNamespace;
27import org.onosproject.net.ConnectPoint;
28import org.onosproject.net.DefaultLink;
29import org.onosproject.net.DeviceId;
30import org.onosproject.net.Link;
31import org.onosproject.net.Path;
32import org.onosproject.net.PortNumber;
33import org.onosproject.net.config.NetworkConfigEvent;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.TrafficSelector;
37import org.onosproject.net.flow.TrafficTreatment;
38import org.onosproject.net.flow.criteria.Criteria;
39import org.onosproject.net.flowobjective.DefaultFilteringObjective;
40import org.onosproject.net.flowobjective.DefaultForwardingObjective;
41import org.onosproject.net.flowobjective.DefaultNextObjective;
42import org.onosproject.net.flowobjective.DefaultObjectiveContext;
43import org.onosproject.net.flowobjective.FilteringObjective;
44import org.onosproject.net.flowobjective.ForwardingObjective;
45import org.onosproject.net.flowobjective.NextObjective;
46import org.onosproject.net.flowobjective.Objective;
47import org.onosproject.net.flowobjective.ObjectiveContext;
48import org.onosproject.net.flowobjective.ObjectiveError;
49import org.onosproject.segmentrouting.SegmentRoutingManager;
50import org.onosproject.segmentrouting.SegmentRoutingService;
51import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
52import org.onosproject.segmentrouting.config.PwaasConfig;
53import org.onosproject.store.serializers.KryoNamespaces;
54import org.onosproject.store.service.ConsistentMap;
55import org.onosproject.store.service.DistributedSet;
56import org.onosproject.store.service.Serializer;
57import org.onosproject.store.service.Versioned;
58import org.slf4j.Logger;
59import org.slf4j.LoggerFactory;
60
61import java.util.ArrayList;
62import java.util.List;
63import java.util.Set;
64import java.util.concurrent.CompletableFuture;
65import java.util.stream.Collectors;
66
67import static com.google.common.base.Preconditions.checkArgument;
68import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.VERSATILE;
69import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.INITIATION;
70import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.TERMINATION;
71import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Result.*;
72import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.FWD;
73import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.REV;
74
75/**
76 * Handles pwaas related events.
77 */
78public class DefaultL2TunnelHandler implements L2TunnelHandler {
79
80 private static final Logger log = LoggerFactory.getLogger(DefaultL2TunnelHandler.class);
81
82 private final SegmentRoutingManager srManager;
83 /**
84 * To store the next objectives related to the initiation.
85 */
86 private final ConsistentMap<String, NextObjective> l2InitiationNextObjStore;
87 /**
88 * To store the next objectives related to the termination.
89 */
90 private final ConsistentMap<String, NextObjective> l2TerminationNextObjStore;
91
92 /**
93 * To store policies.
94 */
95 private final ConsistentMap<String, L2TunnelPolicy> l2PolicyStore;
96
97 /**
98 * To store tunnels.
99 */
100 private final ConsistentMap<String, L2Tunnel> l2TunnelStore;
101
102 private final KryoNamespace.Builder l2TunnelKryo;
103
104 /**
105 * Contains transport vlans used for spine-leaf pseudowires.
106 */
107 private final DistributedSet<VlanId> vlanStore;
108
109 /**
110 * Used for determining transport vlans for leaf-spine.
111 */
112 private short transportVlanUpper = 4093, transportVlanLower = 3500;
113
114 private static final VlanId UNTAGGED_TRANSPORT_VLAN = VlanId.vlanId((short) 4094);
115
116 /**
117 * Create a l2 tunnel handler for the deploy and
118 * for the tear down of pseudo wires.
119 *
120 * @param segmentRoutingManager the segment routing manager
121 */
122 public DefaultL2TunnelHandler(SegmentRoutingManager segmentRoutingManager) {
123 srManager = segmentRoutingManager;
124 l2TunnelKryo = new KryoNamespace.Builder()
125 .register(KryoNamespaces.API)
126 .register(L2Tunnel.class,
127 L2TunnelPolicy.class,
128 DefaultL2Tunnel.class,
129 DefaultL2TunnelPolicy.class,
130 L2Mode.class,
131 MplsLabel.class,
132 VlanId.class,
133 ConnectPoint.class);
134
135 l2InitiationNextObjStore = srManager.
136 storageService.
137 <String, NextObjective>consistentMapBuilder().
138 withName("onos-l2initiation-nextobj-store").
139 withSerializer(Serializer.using(l2TunnelKryo.build())).
140 build();
141
142 l2TerminationNextObjStore = srManager.storageService.
143 <String, NextObjective>consistentMapBuilder()
144 .withName("onos-l2termination-nextobj-store")
145 .withSerializer(Serializer.using(l2TunnelKryo.build()))
146 .build();
147
148 l2PolicyStore = srManager.storageService
149 .<String, L2TunnelPolicy>consistentMapBuilder()
150 .withName("onos-l2-policy-store")
151 .withSerializer(Serializer.using(l2TunnelKryo.build()))
152 .build();
153
154 l2TunnelStore = srManager.storageService
155 .<String, L2Tunnel>consistentMapBuilder()
156 .withName("onos-l2-tunnel-store")
157 .withSerializer(Serializer.using(l2TunnelKryo.build()))
158 .build();
159
160 vlanStore = srManager.storageService.<VlanId>setBuilder()
161 .withName("onos-transport-vlan-store")
162 .withSerializer(Serializer.using(
163 new KryoNamespace.Builder()
164 .register(KryoNamespaces.API)
165 .build()))
166 .build()
167 .asDistributedSet();
168 }
169
170 /**
171 * Deploys any pre-existing pseudowires in the configuration.
172 * Used by manager only in initialization.
173 */
174 @Override
175 public void init() {
176
177 PwaasConfig config = srManager.cfgService.getConfig(srManager.appId(), PwaasConfig.class);
178 if (config == null) {
179 return;
180 }
181
182 log.info("Deploying existing pseudowires");
183
184 // gather pseudowires
185 Set<L2TunnelDescription> pwToAdd = config
186 .getPwIds()
187 .stream()
188 .map(config::getPwDescription)
189 .collect(Collectors.toSet());
190
191 // deploy pseudowires
192 deploy(pwToAdd);
193 }
194
195 /**
196 * Returns all L2 Policies.
197 *
198 * @return List of policies
199 */
200 @Override
201 public List<L2TunnelPolicy> getL2Policies() {
202
203 return new ArrayList<>(l2PolicyStore
204 .values()
205 .stream()
206 .map(Versioned::value)
207 .collect(Collectors.toList()));
208
209 }
210
211 /**
212 * Returns all L2 Tunnels.
213 *
214 * @return List of tunnels.
215 */
216 @Override
217 public List<L2Tunnel> getL2Tunnels() {
218
219 return new ArrayList<>(l2TunnelStore
220 .values()
221 .stream()
222 .map(Versioned::value)
223 .collect(Collectors.toList()));
224
225 }
226
227 @Override
228 public void processLinkDown(Link link) {
229
230 List<L2Tunnel> tunnels = getL2Tunnels();
231 List<L2TunnelPolicy> policies = getL2Policies();
232
233 // determine affected pseudowires and update them at once
234 Set<L2TunnelDescription> pwToUpdate = tunnels
235 .stream()
236 .filter(tun -> tun.pathUsed().contains(link))
237 .map(l2Tunnel -> {
238 L2TunnelPolicy policy = null;
239 for (L2TunnelPolicy l2Policy : policies) {
240 if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
241 policy = l2Policy;
242 break;
243 }
244 }
245
246 return new DefaultL2TunnelDescription(l2Tunnel, policy);
247 })
248 .collect(Collectors.toSet());
249
250
251 log.info("Pseudowires affected by link failure : {}, rerouting them...", pwToUpdate);
252
253 // update all pseudowires
254 pwToUpdate.forEach(tun -> updatePw(tun, tun));
255 }
256
257 @Override
258 public void processPwaasConfigAdded(NetworkConfigEvent event) {
259 checkArgument(event.config().isPresent(),
260 "Config is not presented in PwaasConfigAdded event {}", event);
261
262 log.info("Network event : Pseudowire configuration added!");
263 PwaasConfig config = (PwaasConfig) event.config().get();
264
265 // gather pseudowires
266 Set<L2TunnelDescription> pwToAdd = config
267 .getPwIds()
268 .stream()
269 .map(config::getPwDescription)
270 .collect(Collectors.toSet());
271
272 // deploy pseudowires
273 deploy(pwToAdd);
274 }
275
276 /**
277 * Returns the new vlan id for an ingress point of a
278 * pseudowire. For double tagged, it is the outer,
279 * For single tagged it is the single tag, and for
280 * inner it is None.
281 *
282 * @param ingressOuter vlanid of ingress outer
283 * @param ingressInner vlanid of ingress inner
284 * @param egressOuter vlanid of egress outer
285 * @param egressInner vlanid of egress inner
286 * @return returns the vlan id which will be installed at vlan table 1.
287 */
288 private VlanId determineEgressVlan(VlanId ingressOuter, VlanId ingressInner,
289 VlanId egressOuter, VlanId egressInner) {
290
291 // validity of vlan combinations was checked at verifyPseudowire
292 if (!(ingressOuter.equals(VlanId.NONE))) {
293 return egressOuter;
294 } else if (!(ingressInner.equals(VlanId.NONE))) {
295 return egressInner;
296 } else {
297 return VlanId.vlanId("None");
298 }
299 }
300
301 /**
302 * Determines vlan used for transporting the pw traffic.
303 *
304 * Leaf-Leaf traffic is transferred untagged, thus we choose the UNTAGGED_TRANSPORT_VLAN
305 * and also make sure to add the popVlan instruction.
306 * For spine-leaf pws we choose the highest vlan value available from a certain range.
307 *
308 * @param spinePw if the pw is leaf-spine.
309 * @return The vlan id chossen to transport this pseudowire. If vlan is UNTAGGED_TRANSPORT_VLAN
310 * then the pw is transported untagged.
311 */
312 private VlanId determineTransportVlan(boolean spinePw) {
313
314 if (!spinePw) {
315
316 log.info("Untagged transport with internal vlan {} for pseudowire!", UNTAGGED_TRANSPORT_VLAN);
317 return UNTAGGED_TRANSPORT_VLAN;
318 } else {
319 for (short i = transportVlanUpper; i > transportVlanLower; i--) {
320
321 VlanId vlanToUse = VlanId.vlanId((short) i);
322 if (!vlanStore.contains(vlanToUse)) {
323
324 vlanStore.add(vlanToUse);
325 log.info("Transport vlan {} for pseudowire!", vlanToUse);
326 return vlanToUse;
327 }
328 }
329
330 log.info("No available transport vlan found, pseudowire traffic will be carried untagged " +
331 "with internal vlan {}!", UNTAGGED_TRANSPORT_VLAN);
332 return UNTAGGED_TRANSPORT_VLAN;
333 }
334 }
335
336 /**
337 * Adds a single pseudowire from leaf to a leaf.
338 * This method can be called from cli commands
339 * without configuration updates, thus it does not check for mastership
340 * of the ingress pseudowire device.
341 *
342 * @param pw The pseudowire
343 * @param spinePw True if pseudowire is from leaf to spine
344 * @return result of pseudowire deployment
345 */
346 private Result deployPseudowire(L2TunnelDescription pw, boolean spinePw) {
347
348 Result result;
349 long l2TunnelId;
350
351 l2TunnelId = pw.l2Tunnel().tunnelId();
352
353 // The tunnel id cannot be 0.
354 if (l2TunnelId == 0) {
355 log.warn("Tunnel id id must be > 0");
356 return Result.ADDITION_ERROR;
357 }
358
359 // get path here, need to use the same for fwd and rev direction
360 List<Link> path = getPath(pw.l2TunnelPolicy().cP1(),
361 pw.l2TunnelPolicy().cP2());
362 if (path == null) {
363 log.info("Deploying process : No path between the connection points for pseudowire {}", l2TunnelId);
364 return WRONG_PARAMETERS;
365 }
366
367 Link fwdNextHop;
368 Link revNextHop;
369 if (!spinePw) {
370 if (path.size() != 2) {
371 log.info("Deploying process : Path between two leafs should have size of 2, for pseudowire {}",
372 l2TunnelId);
373 return INTERNAL_ERROR;
374 }
375
376 fwdNextHop = path.get(0);
377 revNextHop = reverseLink(path.get(1));
378 } else {
379 if (path.size() != 1) {
380 log.info("Deploying process : Path between leaf spine should equal to 1, for pseudowire {}",
381 l2TunnelId);
382 return INTERNAL_ERROR;
383 }
384
385 fwdNextHop = path.get(0);
386 revNextHop = reverseLink(path.get(0));
387 }
388
389 pw.l2Tunnel().setPath(path);
390 pw.l2Tunnel().setTransportVlan(determineTransportVlan(spinePw));
391
392 // next hops for next objectives
393
394 log.info("Deploying process : Establishing forward direction for pseudowire {}", l2TunnelId);
395
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800396 VlanId egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP1OuterTag(),
397 pw.l2TunnelPolicy().cP1InnerTag(),
398 pw.l2TunnelPolicy().cP2OuterTag(),
399 pw.l2TunnelPolicy().cP2InnerTag());
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800400 // We establish the tunnel.
401 // result.nextId will be used in fwd
402 result = deployPseudoWireInit(pw.l2Tunnel(),
403 pw.l2TunnelPolicy().cP1(),
404 pw.l2TunnelPolicy().cP2(),
405 FWD,
406 fwdNextHop,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800407 spinePw,
408 egressVlan);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800409 if (result != SUCCESS) {
410 log.info("Deploying process : Error in deploying pseudowire initiation for CP1");
411 return Result.ADDITION_ERROR;
412 }
413
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800414 // We create the policy.
415 result = deployPolicy(l2TunnelId,
416 pw.l2TunnelPolicy().cP1(),
417 pw.l2TunnelPolicy().cP1InnerTag(),
418 pw.l2TunnelPolicy().cP1OuterTag(),
419 egressVlan,
420 result.nextId);
421 if (result != SUCCESS) {
422 log.info("Deploying process : Error in deploying pseudowire policy for CP1");
423 return Result.ADDITION_ERROR;
424 }
425
426 // We terminate the tunnel
427 result = deployPseudoWireTerm(pw.l2Tunnel(),
428 pw.l2TunnelPolicy().cP2(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800429 egressVlan,
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800430 FWD,
431 spinePw);
432
433 if (result != SUCCESS) {
434 log.info("Deploying process : Error in deploying pseudowire termination for CP1");
435 return Result.ADDITION_ERROR;
436
437 }
438
439 log.info("Deploying process : Establishing reverse direction for pseudowire {}", l2TunnelId);
440
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800441 egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP2OuterTag(),
442 pw.l2TunnelPolicy().cP2InnerTag(),
443 pw.l2TunnelPolicy().cP1OuterTag(),
444 pw.l2TunnelPolicy().cP1InnerTag());
445
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800446 // We establish the reverse tunnel.
447 result = deployPseudoWireInit(pw.l2Tunnel(),
448 pw.l2TunnelPolicy().cP2(),
449 pw.l2TunnelPolicy().cP1(),
450 REV,
451 revNextHop,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800452 spinePw,
453 egressVlan);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800454 if (result != SUCCESS) {
455 log.info("Deploying process : Error in deploying pseudowire initiation for CP2");
456 return Result.ADDITION_ERROR;
457 }
458
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800459
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800460 result = deployPolicy(l2TunnelId,
461 pw.l2TunnelPolicy().cP2(),
462 pw.l2TunnelPolicy().cP2InnerTag(),
463 pw.l2TunnelPolicy().cP2OuterTag(),
464 egressVlan,
465 result.nextId);
466 if (result != SUCCESS) {
467 log.info("Deploying process : Error in deploying policy for CP2");
468 return Result.ADDITION_ERROR;
469 }
470
471 result = deployPseudoWireTerm(pw.l2Tunnel(),
472 pw.l2TunnelPolicy().cP1(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800473 egressVlan,
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800474 REV,
475 spinePw);
476
477 if (result != SUCCESS) {
478 log.info("Deploying process : Error in deploying pseudowire termination for CP2");
479 return Result.ADDITION_ERROR;
480 }
481
482 log.info("Deploying process : Updating relevant information for pseudowire {}", l2TunnelId);
483
484 // Populate stores
485 l2TunnelStore.put(Long.toString(l2TunnelId), pw.l2Tunnel());
486 l2PolicyStore.put(Long.toString(l2TunnelId), pw.l2TunnelPolicy());
487
488 return Result.SUCCESS;
489 }
490
491 /**
492 * To deploy a number of pseudo wires.
493 * <p>
494 * Called ONLY when configuration changes, thus the check
495 * for the mastership of the device.
496 * <p>
497 * Only the master of CP1 will deploy this pseudowire.
498 *
499 * @param pwToAdd the set of pseudo wires to add
500 */
501 private void deploy(Set<L2TunnelDescription> pwToAdd) {
502
503 Result result;
504
505 for (L2TunnelDescription currentL2Tunnel : pwToAdd) {
506 ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
507 ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
508 long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
509
510 // only the master of CP1 will program this pseudowire
511 if (!srManager.isMasterOf(cp1)) {
512 log.debug("Not the master of {}. Ignore pseudo wire deployment id={}", cp1, tunnelId);
513 continue;
514 }
515
516 try {
517 // differentiate between leaf-leaf pseudowires and leaf-spine
518 // and pass the appropriate flag in them.
519 if (!srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
520 !srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
521 log.warn("Can not deploy pseudowire from spine to spine!");
522 result = Result.INTERNAL_ERROR;
523 } else if (srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
524 srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
525 log.info("Deploying a leaf-leaf pseudowire {}", tunnelId);
526 result = deployPseudowire(currentL2Tunnel, false);
527 } else {
528 log.info("Deploying a leaf-spine pseudowire {}", tunnelId);
529 result = deployPseudowire(currentL2Tunnel, true);
530 }
531 } catch (DeviceConfigNotFoundException e) {
532 log.error("Exception caught when deploying pseudowire", e.toString());
533 result = Result.INTERNAL_ERROR;
534 }
535
536 switch (result) {
537 case INTERNAL_ERROR:
538 log.warn("Could not deploy pseudowire {}, internal error!", tunnelId);
539 break;
540 case WRONG_PARAMETERS:
541 log.warn("Could not deploy pseudowire {}, wrong parameters!", tunnelId);
542 break;
543 case ADDITION_ERROR:
544 log.warn("Could not deploy pseudowire {}, error in populating rules!", tunnelId);
545 break;
546 default:
547 log.info("Pseudowire with {} succesfully deployed!", tunnelId);
548 break;
549 }
550 }
551 }
552
553
554 @Override
555 public void processPwaasConfigUpdated(NetworkConfigEvent event) {
556 checkArgument(event.config().isPresent(),
557 "Config is not presented in PwaasConfigUpdated event {}", event);
558 checkArgument(event.prevConfig().isPresent(),
559 "PrevConfig is not presented in PwaasConfigUpdated event {}", event);
560
561 log.info("Pseudowire configuration updated.");
562
563 // We retrieve the old pseudo wires.
564 PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get();
565 Set<Long> prevPws = prevConfig.getPwIds();
566
567 // We retrieve the new pseudo wires.
568 PwaasConfig config = (PwaasConfig) event.config().get();
569 Set<Long> newPws = config.getPwIds();
570
571 // We compute the pseudo wires to update.
572 Set<Long> updPws = newPws.stream()
573 .filter(tunnelId -> prevPws.contains(tunnelId)
574 && !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
575 .collect(Collectors.toSet());
576
577 // The pseudo wires to remove.
578 Set<Long> rmvPWs = prevPws.stream()
579 .filter(tunnelId -> !newPws.contains(tunnelId)).collect(Collectors.toSet());
580
581 Set<L2TunnelDescription> pwToRemove = rmvPWs.stream()
582 .map(prevConfig::getPwDescription)
583 .collect(Collectors.toSet());
584 tearDown(pwToRemove);
585
586 // The pseudo wires to add.
587 Set<Long> addedPWs = newPws.stream()
588 .filter(tunnelId -> !prevPws.contains(tunnelId))
589 .collect(Collectors.toSet());
590 Set<L2TunnelDescription> pwToAdd = addedPWs.stream()
591 .map(config::getPwDescription)
592 .collect(Collectors.toSet());
593 deploy(pwToAdd);
594
595
596 // The pseudo wires to update.
597 updPws.forEach(tunnelId -> updatePw(prevConfig.getPwDescription(tunnelId),
598 config.getPwDescription(tunnelId)));
599
600 log.info("Pseudowires removed : {}, Pseudowires updated : {}, Pseudowires added : {}", rmvPWs,
601 updPws, addedPWs);
602 }
603
604 /**
605 * Helper function to update a pw.
606 * <p>
607 * Called upon configuration changes that update existing pseudowires and
608 * when links fail. Checking of mastership for CP1 is mandatory because it is
609 * called in multiple instances for both cases.
610 * <p>
611 * Meant to call asynchronously for various events, thus this call can not block and need
612 * to perform asynchronous operations.
613 * <p>
614 * For this reason error checking is omitted.
615 *
616 * @param oldPw the pseudo wire to remove
617 * @param newPw the pseudo wire to add
618 */
619 private void updatePw(L2TunnelDescription oldPw,
620 L2TunnelDescription newPw) {
621 ConnectPoint oldCp1 = oldPw.l2TunnelPolicy().cP1();
622 long tunnelId = oldPw.l2Tunnel().tunnelId();
623
624 // only the master of CP1 will update this pseudowire
625 if (!srManager.isMasterOf(oldPw.l2TunnelPolicy().cP1())) {
626 log.debug("Not the master of {}. Ignore pseudo wire update id={}", oldCp1, tunnelId);
627 return;
628 }
629 // only determine if the new pseudowire is leaf-spine, because
630 // removal process is the same for both leaf-leaf and leaf-spine pws
631 boolean newPwSpine;
632 try {
633 newPwSpine = !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP1().deviceId()) ||
634 !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP2().deviceId());
635 } catch (DeviceConfigNotFoundException e) {
636 // if exception is caught treat the new pw as leaf-leaf
637 newPwSpine = false;
638 }
639
640 // copy the variable here because we need to use it in lambda thus it needs to be final
641 boolean finalNewPwSpine = newPwSpine;
642
643 log.info("Updating pseudowire {}", oldPw.l2Tunnel().tunnelId());
644
645 // The async tasks to orchestrate the next and forwarding update
646 CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
647 CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
648 CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
649 CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
650 CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>();
651 CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>();
652
653 // first delete all information from our stores, we can not do it asynchronously
654 l2PolicyStore.remove(Long.toString(tunnelId));
655
656 // grab the old l2 tunnel from the store, since it carries information which is not exposed
657 // to the user configuration and set it to oldPw.
658 oldPw.setL2Tunnel(l2TunnelStore.get(Long.toString(tunnelId)).value());
659 VlanId transportVlan = l2TunnelStore.get(Long.toString(tunnelId)).value().transportVlan();
660 l2TunnelStore.remove(Long.toString(tunnelId));
661
662 // remove the reserved transport vlan, if one is used
663 if (!transportVlan.equals(UNTAGGED_TRANSPORT_VLAN)) {
664 vlanStore.remove(transportVlan);
665 }
666
667 // First we remove both policy.
668 log.debug("Start deleting fwd policy for {}", tunnelId);
669 VlanId egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP1OuterTag(),
670 oldPw.l2TunnelPolicy().cP1InnerTag(),
671 oldPw.l2TunnelPolicy().cP2OuterTag(),
672 oldPw.l2TunnelPolicy().cP2InnerTag());
673 deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP1(),
674 oldPw.l2TunnelPolicy().cP1InnerTag(),
675 oldPw.l2TunnelPolicy().cP1OuterTag(),
676 egressVlan,
677 fwdInitNextFuture,
678 FWD);
679
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800680 deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP2(),
681 oldPw.l2TunnelPolicy().cP2InnerTag(),
682 oldPw.l2TunnelPolicy().cP2OuterTag(),
683 egressVlan, revInitNextFuture,
684 REV);
685
686 // Finally we remove both the tunnels.
687 fwdInitNextFuture.thenAcceptAsync(status -> {
688 if (status == null) {
689 log.debug("Update process : Fwd policy removed. " +
690 "Now remove fwd {} for {}", INITIATION, tunnelId);
691 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP1(), fwdTermNextFuture, FWD);
692 }
693 });
694 revInitNextFuture.thenAcceptAsync(status -> {
695 if (status == null) {
696 log.debug("Update process : Rev policy removed. " +
697 "Now remove rev {} for {}", INITIATION, tunnelId);
698 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP2(), revTermNextFuture, REV);
699 }
700 });
701 fwdTermNextFuture.thenAcceptAsync(status -> {
702 if (status == null) {
703 log.debug("Update process : Fwd {} removed. " +
704 "Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId);
705 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP2(), fwdPwFuture, FWD);
706 }
707 });
708 revTermNextFuture.thenAcceptAsync(status -> {
709 if (status == null) {
710 log.debug("Update process : Rev {} removed. " +
711 "Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId);
712 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP1(), revPwFuture, REV);
713 }
714 });
715
716 // get path here, need to use the same for fwd and rev direction
717 List<Link> path = getPath(newPw.l2TunnelPolicy().cP1(),
718 newPw.l2TunnelPolicy().cP2());
719 if (path == null) {
720 log.error("Update process : " +
721 "No path between the connection points for pseudowire {}", newPw.l2Tunnel().tunnelId());
722 return;
723 }
724
725 Link fwdNextHop, revNextHop;
726 if (!finalNewPwSpine) {
727 if (path.size() != 2) {
728 log.error("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
729 newPw.l2Tunnel().tunnelId());
730 return;
731 }
732 fwdNextHop = path.get(0);
733 revNextHop = reverseLink(path.get(1));
734 } else {
735 if (path.size() != 1) {
736 log.error("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
737 newPw.l2Tunnel().tunnelId());
738 return;
739 }
740 fwdNextHop = path.get(0);
741 revNextHop = reverseLink(path.get(0));
742 }
743
744 // set new path and transport vlan.
745 newPw.l2Tunnel().setPath(path);
746 newPw.l2Tunnel().setTransportVlan(determineTransportVlan(newPwSpine));
747
748 // At the end we install the updated PW.
749 fwdPwFuture.thenAcceptAsync(status -> {
750 if (status == null) {
751
752 // Upgrade stores and book keeping information, need to move this here
753 // cause this call is asynchronous.
754 l2PolicyStore.put(Long.toString(tunnelId), newPw.l2TunnelPolicy());
755 l2TunnelStore.put(Long.toString(tunnelId), newPw.l2Tunnel());
756
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800757 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP1OuterTag(),
758 newPw.l2TunnelPolicy().cP1InnerTag(),
759 newPw.l2TunnelPolicy().cP2OuterTag(),
760 newPw.l2TunnelPolicy().cP2InnerTag());
761
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800762 log.debug("Update process : Deploying new fwd pw for {}", tunnelId);
763 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(),
764 newPw.l2TunnelPolicy().cP2(), FWD,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800765 fwdNextHop, finalNewPwSpine, egressVlanId);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800766 if (lamdaResult != SUCCESS) {
767 return;
768 }
769
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800770 lamdaResult = deployPolicy(tunnelId, newPw.l2TunnelPolicy().cP1(),
771 newPw.l2TunnelPolicy().cP1InnerTag(),
772 newPw.l2TunnelPolicy().cP1OuterTag(),
773 egressVlanId, lamdaResult.nextId);
774 if (lamdaResult != SUCCESS) {
775 return;
776 }
777 deployPseudoWireTerm(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800778 egressVlanId, FWD, finalNewPwSpine);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800779
780 }
781 });
782 revPwFuture.thenAcceptAsync(status -> {
783 if (status == null) {
784
785 log.debug("Update process : Deploying new rev pw for {}", tunnelId);
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800786
787 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP2OuterTag(),
788 newPw.l2TunnelPolicy().cP2InnerTag(),
789 newPw.l2TunnelPolicy().cP1OuterTag(),
790 newPw.l2TunnelPolicy().cP1InnerTag());
791
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800792 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
793 newPw.l2TunnelPolicy().cP2(),
794 newPw.l2TunnelPolicy().cP1(),
795 REV,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800796 revNextHop, finalNewPwSpine, egressVlanId);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800797 if (lamdaResult != SUCCESS) {
798 return;
799 }
800
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800801 lamdaResult = deployPolicy(tunnelId,
802 newPw.l2TunnelPolicy().cP2(),
803 newPw.l2TunnelPolicy().cP2InnerTag(),
804 newPw.l2TunnelPolicy().cP2OuterTag(),
805 egressVlanId,
806 lamdaResult.nextId);
807 if (lamdaResult != SUCCESS) {
808 return;
809 }
810 deployPseudoWireTerm(newPw.l2Tunnel(),
811 newPw.l2TunnelPolicy().cP1(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -0800812 egressVlanId,
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -0800813 REV, finalNewPwSpine);
814 }
815 });
816 }
817
818 @Override
819 public void processPwaasConfigRemoved(NetworkConfigEvent event) {
820 checkArgument(event.prevConfig().isPresent(),
821 "PrevConfig is not presented in PwaasConfigRemoved event {}", event);
822
823 log.info("Network event : Pseudowire configuration removed!");
824 PwaasConfig config = (PwaasConfig) event.prevConfig().get();
825
826 Set<L2TunnelDescription> pwToRemove = config
827 .getPwIds()
828 .stream()
829 .map(config::getPwDescription)
830 .collect(Collectors.toSet());
831
832 // We teardown all the pseudo wire deployed
833 tearDown(pwToRemove);
834 }
835
836 /**
837 * Helper function for removing a single pseudowire.
838 * <p>
839 * No mastership of CP1 is checked, because it can be called from
840 * the CLI for removal of pseudowires.
841 *
842 * @param l2TunnelId the id of the pseudowire to tear down
843 * @return Returns SUCCESS if no error is obeserved or an appropriate
844 * error on a failure
845 */
846 private Result tearDownPseudowire(long l2TunnelId) {
847
848 CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
849 CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
850
851 CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
852 CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
853
854 if (l2TunnelId == 0) {
855 log.warn("Removal process : Tunnel id cannot be 0");
856 return Result.WRONG_PARAMETERS;
857 }
858
859 // check existence of tunnels/policy in the store, if one is missing abort!
860 Versioned<L2Tunnel> l2TunnelVersioned = l2TunnelStore.get(Long.toString(l2TunnelId));
861 Versioned<L2TunnelPolicy> l2TunnelPolicyVersioned = l2PolicyStore.get(Long.toString(l2TunnelId));
862 if ((l2TunnelVersioned == null) || (l2TunnelPolicyVersioned == null)) {
863 log.warn("Removal process : Policy and/or tunnel missing for tunnel id {}", l2TunnelId);
864 return Result.REMOVAL_ERROR;
865 }
866
867 L2TunnelDescription pwToRemove = new DefaultL2TunnelDescription(l2TunnelVersioned.value(),
868 l2TunnelPolicyVersioned.value());
869
870 // remove the tunnels and the policies from the store
871 l2PolicyStore.remove(Long.toString(l2TunnelId));
872 l2TunnelStore.remove(Long.toString(l2TunnelId));
873
874 // remove the reserved transport vlan
875 if (!pwToRemove.l2Tunnel().transportVlan().equals(UNTAGGED_TRANSPORT_VLAN)) {
876 vlanStore.remove(pwToRemove.l2Tunnel().transportVlan());
877 }
878
879 log.info("Removal process : Tearing down forward direction of pseudowire {}", l2TunnelId);
880
881 VlanId egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP1OuterTag(),
882 pwToRemove.l2TunnelPolicy().cP1InnerTag(),
883 pwToRemove.l2TunnelPolicy().cP2OuterTag(),
884 pwToRemove.l2TunnelPolicy().cP2InnerTag());
885 deletePolicy(l2TunnelId,
886 pwToRemove.l2TunnelPolicy().cP1(),
887 pwToRemove.l2TunnelPolicy().cP1InnerTag(),
888 pwToRemove.l2TunnelPolicy().cP1OuterTag(),
889 egressVlan,
890 fwdInitNextFuture,
891 FWD);
892
893 fwdInitNextFuture.thenAcceptAsync(status -> {
894 if (status == null) {
895 // Finally we will tear down the pseudo wire.
896 tearDownPseudoWireInit(l2TunnelId,
897 pwToRemove.l2TunnelPolicy().cP1(),
898 fwdTermNextFuture,
899 FWD);
900 }
901 });
902
903 fwdTermNextFuture.thenAcceptAsync(status -> {
904 if (status == null) {
905 tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
906 pwToRemove.l2TunnelPolicy().cP2(),
907 null,
908 FWD);
909 }
910 });
911
912 log.info("Removal process : Tearing down reverse direction of pseudowire {}", l2TunnelId);
913
914 egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP2OuterTag(),
915 pwToRemove.l2TunnelPolicy().cP2InnerTag(),
916 pwToRemove.l2TunnelPolicy().cP1OuterTag(),
917 pwToRemove.l2TunnelPolicy().cP1InnerTag());
918
919 // We do the same operations on the reverse side.
920 deletePolicy(l2TunnelId,
921 pwToRemove.l2TunnelPolicy().cP2(),
922 pwToRemove.l2TunnelPolicy().cP2InnerTag(),
923 pwToRemove.l2TunnelPolicy().cP2OuterTag(),
924 egressVlan,
925 revInitNextFuture,
926 REV);
927
928 revInitNextFuture.thenAcceptAsync(status -> {
929 if (status == null) {
930 tearDownPseudoWireInit(l2TunnelId,
931 pwToRemove.l2TunnelPolicy().cP2(),
932 revTermNextFuture,
933 REV);
934 }
935 });
936
937 revTermNextFuture.thenAcceptAsync(status -> {
938 if (status == null) {
939 tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
940 pwToRemove.l2TunnelPolicy().cP1(),
941 null,
942 REV);
943 }
944 });
945
946 return Result.SUCCESS;
947 }
948
949 @Override
950 public void tearDown(Set<L2TunnelDescription> pwToRemove) {
951
952 Result result;
953
954 // We remove all the pw in the configuration file.
955 for (L2TunnelDescription currentL2Tunnel : pwToRemove) {
956 ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
957 ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
958 long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
959
960 // only the master of CP1 will program this pseudowire
961 if (!srManager.isMasterOf(cp1)) {
962 log.debug("Not the master of {}. Ignore pseudo wire removal id={}", cp1, tunnelId);
963 continue;
964 }
965
966 // no need to differentiate here between leaf-leaf and leaf-spine, because
967 // the only change is in the groups, which we do not remove either way
968 log.info("Removing pseudowire {}", tunnelId);
969
970 result = tearDownPseudowire(tunnelId);
971 switch (result) {
972 case WRONG_PARAMETERS:
973 log.warn("Error in supplied parameters for the pseudowire removal with tunnel id {}!",
974 tunnelId);
975 break;
976 case REMOVAL_ERROR:
977 log.warn("Error in pseudowire removal with tunnel id {}!", tunnelId);
978 break;
979 default:
980 log.warn("Pseudowire with tunnel id {} was removed successfully", tunnelId);
981 }
982 }
983 }
984
985 /**
986 * Handles the policy establishment which consists in
987 * create the filtering and forwarding objectives related
988 * to the initiation and termination.
989 *
990 * @param tunnelId the tunnel id
991 * @param ingress the ingress point
992 * @param ingressInner the ingress inner tag
993 * @param ingressOuter the ingress outer tag
994 * @param nextId the next objective id
995 * @param egressVlan Vlan-id to set, depends on ingress vlan
996 * combinations. For example, if pw is double tagged
997 * then this is the value of the outer vlan, if single
998 * tagged then it is the new value of the single tag.
999 * Should be None for untagged traffic.
1000 * @return the result of the operation
1001 */
1002 private Result deployPolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner,
1003 VlanId ingressOuter, VlanId egressVlan, int nextId) {
1004
1005 List<Objective> objectives = Lists.newArrayList();
1006 // We create the forwarding objective for supporting
1007 // the l2 tunnel.
1008 ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
1009 // We create and add objective context.
1010 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1011 log.debug("FwdObj for tunnel {} populated", tunnelId),
1012 (objective, error) ->
1013 log.warn("Failed to populate fwdrObj " +
1014 "for tunnel {}", tunnelId, error));
1015 objectives.add(fwdBuilder.add(context));
1016
1017 // We create the filtering objective to define the
1018 // permit traffic in the switch
1019 FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
1020
1021 // We add the metadata.
1022 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
1023 .builder()
1024 .setTunnelId(tunnelId)
1025 .setVlanId(egressVlan);
1026 filtBuilder.withMeta(treatment.build());
1027
1028 // We create and add objective context.
1029 context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for tunnel {} populated", tunnelId),
1030 (objective, error) -> log.warn("Failed to populate filterObj for " +
1031 "tunnel {}", tunnelId, error));
1032 objectives.add(filtBuilder.add(context));
1033
1034 for (Objective objective : objectives) {
1035 if (objective instanceof ForwardingObjective) {
1036 srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
1037 log.debug("Creating new FwdObj for initiation NextObj with id={} for tunnel {}", nextId, tunnelId);
1038 } else {
1039 srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
1040 log.debug("Creating new FiltObj for tunnel {}", tunnelId);
1041 }
1042 }
1043 return SUCCESS;
1044 }
1045
1046 /**
1047 * Handles the tunnel establishment which consists in
1048 * create the next objectives related to the initiation.
1049 *
1050 * @param l2Tunnel the tunnel to deploy
1051 * @param ingress the ingress connect point
1052 * @param egress the egress connect point
1053 * @param direction the direction of the pw
1054 * @param spinePw if the pseudowire involves a spine switch
1055 * @return the result of the operation
1056 */
1057 private Result deployPseudoWireInit(L2Tunnel l2Tunnel, ConnectPoint ingress,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001058 ConnectPoint egress, Direction direction,
1059 Link nextHop, boolean spinePw, VlanId termVlanId) {
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001060
1061 if (nextHop == null) {
1062 log.warn("No path between ingress and egress cps for tunnel {}", l2Tunnel.tunnelId());
1063 return WRONG_PARAMETERS;
1064 }
1065
1066 // We create the next objective without the metadata
1067 // context and id. We check if it already exists in the
1068 // store. If not we store as it is in the store.
1069 NextObjective.Builder nextObjectiveBuilder = createNextObjective(INITIATION,
1070 nextHop.src(),
1071 nextHop.dst(),
1072 l2Tunnel,
1073 egress.deviceId(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001074 spinePw,
1075 termVlanId);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001076
1077 if (nextObjectiveBuilder == null) {
1078 return INTERNAL_ERROR;
1079 }
1080 // We set the metadata. We will use this metadata
1081 // to inform the driver we are doing a l2 tunnel.
1082 TrafficSelector metadata = DefaultTrafficSelector
1083 .builder()
1084 .matchTunnelId(l2Tunnel.tunnelId())
1085 .build();
1086 nextObjectiveBuilder.withMeta(metadata);
1087 int nextId = srManager.flowObjectiveService.allocateNextId();
1088 if (nextId < 0) {
1089 log.warn("Not able to allocate a next id for initiation");
1090 return INTERNAL_ERROR;
1091 }
1092 nextObjectiveBuilder.withId(nextId);
1093 String key = generateKey(l2Tunnel.tunnelId(), direction);
1094 l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add());
1095 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1096 log.debug("Initiation l2 tunnel rule " +
1097 "for {} populated",
1098 l2Tunnel.tunnelId()),
1099 (objective, error) ->
1100 log.warn("Failed to populate Initiation " +
1101 "l2 tunnel rule for {}: {}",
1102 l2Tunnel.tunnelId(), error));
1103 NextObjective nextObjective = nextObjectiveBuilder.add(context);
1104 srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective);
1105 log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}",
1106 l2Tunnel.tunnelId(), nextObjective.id());
1107 Result result = SUCCESS;
1108 result.nextId = nextObjective.id();
1109 return result;
1110 }
1111
1112 /**
1113 * Handles the tunnel termination, which consists in the creation
1114 * of a forwarding objective and a next objective.
1115 *
1116 * @param l2Tunnel the tunnel to terminate
1117 * @param egress the egress point
1118 * @param egressVlan the expected vlan at egress
1119 * @param direction the direction
1120 * @param spinePw if the pseudowire involves a spine switch
1121 * @return the result of the operation
1122 */
1123 private Result deployPseudoWireTerm(L2Tunnel l2Tunnel, ConnectPoint egress,
1124 VlanId egressVlan, Direction direction, boolean spinePw) {
1125
1126 // We create the group relative to the termination.
1127 NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
1128 l2Tunnel, egress.deviceId(),
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001129 spinePw,
1130 egressVlan);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001131 if (nextObjectiveBuilder == null) {
1132 return INTERNAL_ERROR;
1133 }
1134 TrafficSelector metadata = DefaultTrafficSelector
1135 .builder()
1136 .matchVlanId(egressVlan)
1137 .build();
1138 nextObjectiveBuilder.withMeta(metadata);
1139 int nextId = srManager.flowObjectiveService.allocateNextId();
1140 if (nextId < 0) {
1141 log.warn("Not able to allocate a next id for initiation");
1142 return INTERNAL_ERROR;
1143 }
1144 nextObjectiveBuilder.withId(nextId);
1145 String key = generateKey(l2Tunnel.tunnelId(), direction);
1146 l2TerminationNextObjStore.put(key, nextObjectiveBuilder.add());
1147 ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("Termination l2 tunnel rule " +
1148 "for {} populated",
1149 l2Tunnel.tunnelId()),
1150 (objective, error) -> log.warn("Failed to populate " +
1151 "termination l2 tunnel " +
1152 "rule for {}: {}",
1153 l2Tunnel.tunnelId(),
1154 error));
1155 NextObjective nextObjective = nextObjectiveBuilder.add(context);
1156 srManager.flowObjectiveService.next(egress.deviceId(), nextObjective);
1157 log.debug("Termination next objective for {} not found. Creating new NextObj with id={}",
1158 l2Tunnel.tunnelId(), nextObjective.id());
1159
1160 // We create the flow relative to the termination.
1161 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(), l2Tunnel.tunnelId(),
1162 egress.port(), nextObjective.id());
1163 context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for tunnel termination {} populated",
1164 l2Tunnel.tunnelId()),
1165 (objective, error) -> log.warn("Failed to populate fwdrObj" +
1166 " for tunnel termination {}",
1167 l2Tunnel.tunnelId(), error));
1168 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context));
1169 log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}",
1170 nextId, l2Tunnel.tunnelId());
1171
1172 if (spinePw) {
1173
1174 // determine the input port at the
1175 PortNumber inPort;
1176
1177 if (egress.deviceId().
1178 equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
1179 inPort = l2Tunnel.pathUsed().get(0).dst().port();
1180 } else {
1181 inPort = l2Tunnel.pathUsed().get(0).src().port();
1182 }
1183
1184 MacAddress dstMac;
1185 try {
1186 dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
1187 } catch (Exception e) {
1188 log.info("Device not found in configuration, no programming of MAC address");
1189 dstMac = null;
1190 }
1191
1192 log.info("Populating filtering objective for pseudowire transport" +
1193 " with vlan = {}, port = {}, mac = {}",
1194 l2Tunnel.transportVlan(),
1195 inPort,
1196 dstMac);
1197 FilteringObjective.Builder filteringObjectiveBuilder =
1198 createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
1199 context = new DefaultObjectiveContext(( objective ) ->
1200 log.debug("Special filtObj for " + "for {} populated",
1201 l2Tunnel.tunnelId()),
1202 ( objective, error ) ->
1203 log.warn("Failed to populate " +
1204 "special filtObj " +
1205 "rule for {}: {}",
1206 l2Tunnel.tunnelId(), error));
1207 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
1208 filteringObjectiveBuilder.withMeta(treatment.build());
1209 srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.add(context));
1210 log.debug("Creating new special FiltObj for termination point with tunnel {} for port {}",
1211 l2Tunnel.tunnelId(),
1212 inPort);
1213 }
1214
1215 return SUCCESS;
1216 }
1217
1218
1219 /**
1220 * Creates the filtering objective according to a given port and vlanid.
1221 *
1222 * @param inPort the in port
1223 * @param vlanId the inner vlan tag
1224 * @return the filtering objective
1225 */
1226 private FilteringObjective.Builder createNormalPipelineFiltObjective(PortNumber inPort,
1227 VlanId vlanId,
1228 MacAddress dstMac) {
1229
1230 log.info("Creating filtering objective for pseudowire transport with vlan={}, port={}, mac={}",
1231 vlanId,
1232 inPort,
1233 dstMac);
1234 FilteringObjective.Builder fwdBuilder = DefaultFilteringObjective
1235 .builder()
1236 .withKey(Criteria.matchInPort(inPort))
1237 .addCondition(Criteria.matchVlanId(vlanId))
1238 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1239 .permit()
1240 .fromApp(srManager.appId());
1241
1242 if (dstMac != null) {
1243 fwdBuilder.addCondition(Criteria.matchEthDst(dstMac));
1244 }
1245
1246 return fwdBuilder;
1247 }
1248
1249 /**
1250 * Creates the filtering objective according to a given policy.
1251 *
1252 * @param inPort the in port
1253 * @param innerTag the inner vlan tag
1254 * @param outerTag the outer vlan tag
1255 * @return the filtering objective
1256 */
1257 private FilteringObjective.Builder createFiltObjective(PortNumber inPort, VlanId innerTag, VlanId outerTag) {
1258
1259 log.info("Creating filtering objective for vlans {} / {}", outerTag, innerTag);
1260 return DefaultFilteringObjective
1261 .builder()
1262 .withKey(Criteria.matchInPort(inPort))
1263 .addCondition(Criteria.matchInnerVlanId(innerTag))
1264 .addCondition(Criteria.matchVlanId(outerTag))
1265 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1266 .permit()
1267 .fromApp(srManager.appId());
1268 }
1269
1270 /**
1271 * Creates the forwarding objective for the termination.
1272 *
1273 * @param pwLabel the pseudo wire label
1274 * @param tunnelId the tunnel id
1275 * @param egressPort the egress port
1276 * @param nextId the next step
1277 * @return the forwarding objective to support the termination
1278 */
1279 private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel, long tunnelId,
1280 PortNumber egressPort, int nextId) {
1281
1282 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1283 TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment.builder();
1284 // The flow has to match on the pw label and bos
1285 trafficSelector.matchEthType(Ethernet.MPLS_UNICAST);
1286 trafficSelector.matchMplsLabel(pwLabel);
1287 trafficSelector.matchMplsBos(true);
1288 // The flow has to decrement ttl, restore ttl in
1289 // pop mpls, set tunnel id and port.
1290 trafficTreatment.decMplsTtl();
1291 trafficTreatment.copyTtlIn();
1292 trafficTreatment.popMpls();
1293 trafficTreatment.setTunnelId(tunnelId);
1294 trafficTreatment.setOutput(egressPort);
1295
1296 return DefaultForwardingObjective
1297 .builder()
1298 .fromApp(srManager.appId())
1299 .makePermanent()
1300 .nextStep(nextId)
1301 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1302 .withSelector(trafficSelector.build())
1303 .withTreatment(trafficTreatment.build())
1304 .withFlag(VERSATILE);
1305 }
1306
1307 /**
1308 * Creates the forwarding objective for the initiation.
1309 *
1310 * @param tunnelId the tunnel id
1311 * @param inPort the input port
1312 * @param nextId the next step
1313 * @return the forwarding objective to support the initiation.
1314 */
1315 private ForwardingObjective.Builder createInitFwdObjective(long tunnelId, PortNumber inPort, int nextId) {
1316
1317 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1318
1319 // The flow has to match on the mpls logical
1320 // port and the tunnel id.
1321 trafficSelector.matchTunnelId(tunnelId);
1322 trafficSelector.matchInPort(inPort);
1323
1324 return DefaultForwardingObjective
1325 .builder()
1326 .fromApp(srManager.appId())
1327 .makePermanent()
1328 .nextStep(nextId)
1329 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1330 .withSelector(trafficSelector.build())
1331 .withFlag(VERSATILE);
1332
1333 }
1334
1335 /**
1336 * Creates the next objective according to a given
1337 * pipeline. We don't set the next id and we don't
1338 * create the final meta to check if we are re-using
1339 * the same next objective for different tunnels.
1340 *
1341 * @param pipeline the pipeline to support
1342 * @param srcCp the source port
1343 * @param dstCp the destination port
1344 * @param l2Tunnel the tunnel to support
1345 * @param egressId the egress device id
1346 * @param spinePw if the pw involves a spine switch
1347 * @return the next objective to support the pipeline
1348 */
1349 private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp,
1350 ConnectPoint dstCp, L2Tunnel l2Tunnel,
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001351 DeviceId egressId, boolean spinePw, VlanId termVlanId) {
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001352 NextObjective.Builder nextObjBuilder;
1353 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
1354 if (pipeline == INITIATION) {
1355 nextObjBuilder = DefaultNextObjective
1356 .builder()
1357 .withType(NextObjective.Type.SIMPLE)
1358 .fromApp(srManager.appId());
1359 // The pw label is the bottom of stack. It has to
1360 // be different -1.
1361 if (l2Tunnel.pwLabel().toInt() == MplsLabel.MAX_MPLS) {
1362 log.warn("Pw label not configured");
1363 return null;
1364 }
1365 treatmentBuilder.pushMpls();
1366 treatmentBuilder.setMpls(l2Tunnel.pwLabel());
1367 treatmentBuilder.setMplsBos(true);
1368 treatmentBuilder.copyTtlOut();
1369
1370 // If the inter-co label is present we have to set the label.
1371 if (l2Tunnel.interCoLabel().toInt() != MplsLabel.MAX_MPLS) {
1372 treatmentBuilder.pushMpls();
1373 treatmentBuilder.setMpls(l2Tunnel.interCoLabel());
1374 treatmentBuilder.setMplsBos(false);
1375 treatmentBuilder.copyTtlOut();
1376 }
1377
1378 // if pw is leaf-to-leaf we need to
1379 // add the routing label also
1380 if (!spinePw) {
1381 // We retrieve the sr label from the config
1382 // specific for pseudowire traffic
1383 // using the egress leaf device id.
1384 MplsLabel srLabel;
1385 try {
1386 srLabel = MplsLabel.mplsLabel(srManager.deviceConfiguration().getPWRoutingLabel(egressId));
1387
1388 } catch (DeviceConfigNotFoundException e) {
1389 log.warn("Sr label for pw traffic not configured");
1390 return null;
1391 }
1392
1393 treatmentBuilder.pushMpls();
1394 treatmentBuilder.setMpls(srLabel);
1395 treatmentBuilder.setMplsBos(false);
1396 treatmentBuilder.copyTtlOut();
1397 }
1398
1399 // We have to rewrite the src and dst mac address.
1400 MacAddress ingressMac;
1401 try {
1402 ingressMac = srManager.deviceConfiguration().getDeviceMac(srcCp.deviceId());
1403 } catch (DeviceConfigNotFoundException e) {
1404 log.warn("Was not able to find the ingress mac");
1405 return null;
1406 }
1407 treatmentBuilder.setEthSrc(ingressMac);
1408 MacAddress neighborMac;
1409 try {
1410 neighborMac = srManager.deviceConfiguration().getDeviceMac(dstCp.deviceId());
1411 } catch (DeviceConfigNotFoundException e) {
1412 log.warn("Was not able to find the neighbor mac");
1413 return null;
1414 }
1415 treatmentBuilder.setEthDst(neighborMac);
1416
1417 // if not a leaf-spine pw we need to POP the vlan at the output
1418 // since we carry this traffic untagged.
1419 if (!spinePw) {
1420 treatmentBuilder.popVlan();
1421 }
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001422
1423 // set the appropriate transport vlan
1424 treatmentBuilder.setVlanId(l2Tunnel.transportVlan());
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001425 } else {
1426 // We create the next objective which
1427 // will be a simple l2 group.
1428 nextObjBuilder = DefaultNextObjective
1429 .builder()
1430 .withType(NextObjective.Type.SIMPLE)
1431 .fromApp(srManager.appId());
Andreas Pantelopoulose1b47072018-02-22 17:54:45 -08001432
1433 // for termination point we use the outer vlan of the
1434 // encapsulated packet
1435 treatmentBuilder.setVlanId(termVlanId);
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001436 }
1437
Andreas Pantelopoulos4c7de132018-02-22 12:32:42 -08001438 treatmentBuilder.setOutput(srcCp.port());
1439 nextObjBuilder.addTreatment(treatmentBuilder.build());
1440 return nextObjBuilder;
1441 }
1442
1443 /**
1444 * Reverses a link.
1445 *
1446 * @param link link to be reversed
1447 * @return the reversed link
1448 */
1449 private Link reverseLink(Link link) {
1450
1451 DefaultLink.Builder linkBuilder = DefaultLink.builder();
1452
1453 linkBuilder.src(link.dst());
1454 linkBuilder.dst(link.src());
1455 linkBuilder.type(link.type());
1456 linkBuilder.providerId(link.providerId());
1457
1458 return linkBuilder.build();
1459 }
1460
1461 /**
1462 * Returns the path betwwen two connect points.
1463 *
1464 * @param srcCp source connect point
1465 * @param dstCp destination connect point
1466 * @return the path
1467 */
1468 private List<Link> getPath(ConnectPoint srcCp, ConnectPoint dstCp) {
1469 /* TODO We retrieve a set of paths in case of a link failure, what happens
1470 * if the TopologyService gets the link notification AFTER us and has not updated the paths?
1471 *
1472 * TODO This has the potential to act on old topology.
1473 * Maybe we should make SRManager be a listener on topology events instead raw link events.
1474 */
1475 Set<Path> paths = srManager.topologyService.getPaths(
1476 srManager.topologyService.currentTopology(),
1477 srcCp.deviceId(), dstCp.deviceId());
1478
1479 log.debug("Paths obtained from topology service {}", paths);
1480
1481 // We randomly pick a path.
1482 if (paths.isEmpty()) {
1483 return null;
1484 }
1485 int size = paths.size();
1486 int index = RandomUtils.nextInt(0, size);
1487
1488 List<Link> result = Iterables.get(paths, index).links();
1489 log.debug("Randomly picked a path {}", result);
1490
1491 return result;
1492 }
1493
1494 /**
1495 * Deletes a given policy using the parameter supplied.
1496 *
1497 * @param tunnelId the tunnel id
1498 * @param ingress the ingress point
1499 * @param ingressInner the ingress inner vlan id
1500 * @param ingressOuter the ingress outer vlan id
1501 * @param future to perform the async operation
1502 * @param direction the direction: forward or reverse
1503 */
1504 private void deletePolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter,
1505 VlanId egressVlan, CompletableFuture<ObjectiveError> future, Direction direction) {
1506
1507 String key = generateKey(tunnelId, direction);
1508 if (!l2InitiationNextObjStore.containsKey(key)) {
1509 log.warn("Abort delete of policy for tunnel {}: next does not exist in the store", tunnelId);
1510 if (future != null) {
1511 future.complete(null);
1512 }
1513 return;
1514 }
1515 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
1516 int nextId = nextObjective.id();
1517 List<Objective> objectives = Lists.newArrayList();
1518 // We create the forwarding objective.
1519 ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
1520 ObjectiveContext context = new ObjectiveContext() {
1521 @Override
1522 public void onSuccess(Objective objective) {
1523 log.debug("Previous fwdObj for policy {} removed", tunnelId);
1524 if (future != null) {
1525 future.complete(null);
1526 }
1527 }
1528
1529 @Override
1530 public void onError(Objective objective, ObjectiveError error) {
1531 log.warn("Failed to remove previous fwdObj for policy {}: {}", tunnelId, error);
1532 if (future != null) {
1533 future.complete(error);
1534 }
1535 }
1536 };
1537 objectives.add(fwdBuilder.remove(context));
1538 // We create the filtering objective to define the
1539 // permit traffic in the switch
1540 FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
1541 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
1542 .builder()
1543 .setTunnelId(tunnelId)
1544 .setVlanId(egressVlan);
1545 filtBuilder.withMeta(treatment.build());
1546 context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for policy {} revoked", tunnelId),
1547 (objective, error) ->
1548 log.warn("Failed to revoke filterObj for policy {}",
1549 tunnelId, error));
1550 objectives.add(filtBuilder.remove(context));
1551
1552 for (Objective objective : objectives) {
1553 if (objective instanceof ForwardingObjective) {
1554 srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
1555 } else {
1556 srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
1557 }
1558 }
1559 }
1560
1561 /**
1562 * Deletes the pseudo wire initiation.
1563 *
1564 * @param l2TunnelId the tunnel id
1565 * @param ingress the ingress connect point
1566 * @param future to perform an async operation
1567 * @param direction the direction: reverse of forward
1568 */
1569 private void tearDownPseudoWireInit(long l2TunnelId, ConnectPoint ingress,
1570 CompletableFuture<ObjectiveError> future, Direction direction) {
1571
1572 String key = generateKey(l2TunnelId, direction);
1573 if (!l2InitiationNextObjStore.containsKey(key)) {
1574 log.info("Abort delete of {} for {}: next does not exist in the store", INITIATION, key);
1575 if (future != null) {
1576 future.complete(null);
1577 }
1578 return;
1579 }
1580 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
1581
1582 // un-comment in case you want to delete groups used by the pw
1583 // however, this will break the update of pseudowires cause the L2 interface group can
1584 // not be deleted (it is referenced by other groups)
1585 /*
1586 ObjectiveContext context = new ObjectiveContext() {
1587 @Override
1588 public void onSuccess(Objective objective) {
1589 log.debug("Previous {} next for {} removed", INITIATION, key);
1590 if (future != null) {
1591 future.complete(null);
1592 }
1593 }
1594
1595 @Override
1596 public void onError(Objective objective, ObjectiveError error) {
1597 log.warn("Failed to remove previous {} next for {}: {}", INITIATION, key, error);
1598 if (future != null) {
1599 future.complete(error);
1600 }
1601 }
1602 };
1603 srManager.flowObjectiveService.next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1604 */
1605
1606 future.complete(null);
1607 l2InitiationNextObjStore.remove(key);
1608 }
1609
1610 /**
1611 * Deletes the pseudo wire termination.
1612 *
1613 * @param l2Tunnel the tunnel
1614 * @param egress the egress connect point
1615 * @param future the async task
1616 * @param direction the direction of the tunnel
1617 */
1618 private void tearDownPseudoWireTerm(L2Tunnel l2Tunnel,
1619 ConnectPoint egress,
1620 CompletableFuture<ObjectiveError> future,
1621 Direction direction) {
1622
1623 String key = generateKey(l2Tunnel.tunnelId(), direction);
1624 if (!l2TerminationNextObjStore.containsKey(key)) {
1625 log.info("Abort delete of {} for {}: next does not exist in the store", TERMINATION, key);
1626 if (future != null) {
1627 future.complete(null);
1628 }
1629 return;
1630 }
1631 NextObjective nextObjective = l2TerminationNextObjStore.get(key).value();
1632 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(),
1633 l2Tunnel.tunnelId(),
1634 egress.port(),
1635 nextObjective.id());
1636 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1637 log.debug("FwdObj for {} {}, " +
1638 "direction {} removed",
1639 TERMINATION,
1640 l2Tunnel.tunnelId(),
1641 direction),
1642 (objective, error) ->
1643 log.warn("Failed to remove fwdObj " +
1644 "for {} {}" +
1645 ", direction {}",
1646 TERMINATION,
1647 l2Tunnel.tunnelId(),
1648 error,
1649 direction));
1650 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context));
1651
1652 // un-comment in case you want to delete groups used by the pw
1653 // however, this will break the update of pseudowires cause the L2 interface group can
1654 // not be deleted (it is referenced by other groups)
1655 /*
1656 context = new ObjectiveContext() {
1657 @Override
1658 public void onSuccess(Objective objective) {
1659 log.debug("Previous {} next for {} removed", TERMINATION, key);
1660 if (future != null) {
1661 future.complete(null);
1662 }
1663 }
1664
1665 @Override
1666 public void onError(Objective objective, ObjectiveError error) {
1667 log.warn("Failed to remove previous {} next for {}: {}", TERMINATION, key, error);
1668 if (future != null) {
1669 future.complete(error);
1670 }
1671 }
1672 };
1673 srManager.flowObjectiveService.next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1674 */
1675
1676 // delete the extra filtering objective for terminating
1677 // spine-spine pws
1678 if (!l2Tunnel.transportVlan().equals(UNTAGGED_TRANSPORT_VLAN)) {
1679
1680 // determine the input port at the
1681 PortNumber inPort;
1682
1683 if (egress.deviceId().
1684 equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
1685 inPort = l2Tunnel.pathUsed().get(0).dst().port();
1686 } else {
1687 inPort = l2Tunnel.pathUsed().get(0).src().port();
1688 }
1689
1690 MacAddress dstMac;
1691 try {
1692 dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
1693 } catch (Exception e) {
1694 log.info("Device not found in configuration, no programming of MAC address");
1695 dstMac = null;
1696 }
1697
1698 log.info("Removing filtering objective for pseudowire transport" +
1699 " with vlan = {}, port = {}, mac = {}",
1700 l2Tunnel.transportVlan(),
1701 inPort,
1702 dstMac);
1703 FilteringObjective.Builder filteringObjectiveBuilder =
1704 createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
1705 context = new DefaultObjectiveContext(( objective ) ->
1706 log.debug("Special filtObj for " + "for {} removed",
1707 l2Tunnel.tunnelId()), ( objective, error ) ->
1708 log.warn("Failed to populate " + "special filtObj " +
1709 "rule for {}: {}", l2Tunnel.tunnelId(), error));
1710 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
1711 filteringObjectiveBuilder.withMeta(treatment.build());
1712 srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.remove(context));
1713 log.debug("Removing special FiltObj for termination point with tunnel {} for port {}",
1714 l2Tunnel.tunnelId(),
1715 inPort);
1716 }
1717
1718 l2TerminationNextObjStore.remove(key);
1719 future.complete(null);
1720 }
1721
1722 /**
1723 * Utilities to generate pw key.
1724 *
1725 * @param tunnelId the tunnel id
1726 * @param direction the direction of the pw
1727 * @return the key of the store
1728 */
1729 private String generateKey(long tunnelId, Direction direction) {
1730 return String.format("%s-%s", tunnelId, direction);
1731 }
1732
1733}