blob: df02417bc9f5d4ffe3295d41c2b18d382d71a56d [file] [log] [blame]
Andreas Pantelopoulosb21547d2018-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
396 // We establish the tunnel.
397 // result.nextId will be used in fwd
398 result = deployPseudoWireInit(pw.l2Tunnel(),
399 pw.l2TunnelPolicy().cP1(),
400 pw.l2TunnelPolicy().cP2(),
401 FWD,
402 fwdNextHop,
403 spinePw);
404 if (result != SUCCESS) {
405 log.info("Deploying process : Error in deploying pseudowire initiation for CP1");
406 return Result.ADDITION_ERROR;
407 }
408
409 VlanId egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP1OuterTag(),
410 pw.l2TunnelPolicy().cP1InnerTag(),
411 pw.l2TunnelPolicy().cP2OuterTag(),
412 pw.l2TunnelPolicy().cP2InnerTag());
413
414 // 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(),
429 VlanId.NONE,
430 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
441 // We establish the reverse tunnel.
442 result = deployPseudoWireInit(pw.l2Tunnel(),
443 pw.l2TunnelPolicy().cP2(),
444 pw.l2TunnelPolicy().cP1(),
445 REV,
446 revNextHop,
447 spinePw);
448 if (result != SUCCESS) {
449 log.info("Deploying process : Error in deploying pseudowire initiation for CP2");
450 return Result.ADDITION_ERROR;
451 }
452
453 egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP2OuterTag(),
454 pw.l2TunnelPolicy().cP2InnerTag(),
455 pw.l2TunnelPolicy().cP1OuterTag(),
456 pw.l2TunnelPolicy().cP1InnerTag());
457 result = deployPolicy(l2TunnelId,
458 pw.l2TunnelPolicy().cP2(),
459 pw.l2TunnelPolicy().cP2InnerTag(),
460 pw.l2TunnelPolicy().cP2OuterTag(),
461 egressVlan,
462 result.nextId);
463 if (result != SUCCESS) {
464 log.info("Deploying process : Error in deploying policy for CP2");
465 return Result.ADDITION_ERROR;
466 }
467
468 result = deployPseudoWireTerm(pw.l2Tunnel(),
469 pw.l2TunnelPolicy().cP1(),
470 VlanId.NONE,
471 REV,
472 spinePw);
473
474 if (result != SUCCESS) {
475 log.info("Deploying process : Error in deploying pseudowire termination for CP2");
476 return Result.ADDITION_ERROR;
477 }
478
479 log.info("Deploying process : Updating relevant information for pseudowire {}", l2TunnelId);
480
481 // Populate stores
482 l2TunnelStore.put(Long.toString(l2TunnelId), pw.l2Tunnel());
483 l2PolicyStore.put(Long.toString(l2TunnelId), pw.l2TunnelPolicy());
484
485 return Result.SUCCESS;
486 }
487
488 /**
489 * To deploy a number of pseudo wires.
490 * <p>
491 * Called ONLY when configuration changes, thus the check
492 * for the mastership of the device.
493 * <p>
494 * Only the master of CP1 will deploy this pseudowire.
495 *
496 * @param pwToAdd the set of pseudo wires to add
497 */
498 private void deploy(Set<L2TunnelDescription> pwToAdd) {
499
500 Result result;
501
502 for (L2TunnelDescription currentL2Tunnel : pwToAdd) {
503 ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
504 ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
505 long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
506
507 // only the master of CP1 will program this pseudowire
508 if (!srManager.isMasterOf(cp1)) {
509 log.debug("Not the master of {}. Ignore pseudo wire deployment id={}", cp1, tunnelId);
510 continue;
511 }
512
513 try {
514 // differentiate between leaf-leaf pseudowires and leaf-spine
515 // and pass the appropriate flag in them.
516 if (!srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
517 !srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
518 log.warn("Can not deploy pseudowire from spine to spine!");
519 result = Result.INTERNAL_ERROR;
520 } else if (srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
521 srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
522 log.info("Deploying a leaf-leaf pseudowire {}", tunnelId);
523 result = deployPseudowire(currentL2Tunnel, false);
524 } else {
525 log.info("Deploying a leaf-spine pseudowire {}", tunnelId);
526 result = deployPseudowire(currentL2Tunnel, true);
527 }
528 } catch (DeviceConfigNotFoundException e) {
529 log.error("Exception caught when deploying pseudowire", e.toString());
530 result = Result.INTERNAL_ERROR;
531 }
532
533 switch (result) {
534 case INTERNAL_ERROR:
535 log.warn("Could not deploy pseudowire {}, internal error!", tunnelId);
536 break;
537 case WRONG_PARAMETERS:
538 log.warn("Could not deploy pseudowire {}, wrong parameters!", tunnelId);
539 break;
540 case ADDITION_ERROR:
541 log.warn("Could not deploy pseudowire {}, error in populating rules!", tunnelId);
542 break;
543 default:
544 log.info("Pseudowire with {} succesfully deployed!", tunnelId);
545 break;
546 }
547 }
548 }
549
550
551 @Override
552 public void processPwaasConfigUpdated(NetworkConfigEvent event) {
553 checkArgument(event.config().isPresent(),
554 "Config is not presented in PwaasConfigUpdated event {}", event);
555 checkArgument(event.prevConfig().isPresent(),
556 "PrevConfig is not presented in PwaasConfigUpdated event {}", event);
557
558 log.info("Pseudowire configuration updated.");
559
560 // We retrieve the old pseudo wires.
561 PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get();
562 Set<Long> prevPws = prevConfig.getPwIds();
563
564 // We retrieve the new pseudo wires.
565 PwaasConfig config = (PwaasConfig) event.config().get();
566 Set<Long> newPws = config.getPwIds();
567
568 // We compute the pseudo wires to update.
569 Set<Long> updPws = newPws.stream()
570 .filter(tunnelId -> prevPws.contains(tunnelId)
571 && !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
572 .collect(Collectors.toSet());
573
574 // The pseudo wires to remove.
575 Set<Long> rmvPWs = prevPws.stream()
576 .filter(tunnelId -> !newPws.contains(tunnelId)).collect(Collectors.toSet());
577
578 Set<L2TunnelDescription> pwToRemove = rmvPWs.stream()
579 .map(prevConfig::getPwDescription)
580 .collect(Collectors.toSet());
581 tearDown(pwToRemove);
582
583 // The pseudo wires to add.
584 Set<Long> addedPWs = newPws.stream()
585 .filter(tunnelId -> !prevPws.contains(tunnelId))
586 .collect(Collectors.toSet());
587 Set<L2TunnelDescription> pwToAdd = addedPWs.stream()
588 .map(config::getPwDescription)
589 .collect(Collectors.toSet());
590 deploy(pwToAdd);
591
592
593 // The pseudo wires to update.
594 updPws.forEach(tunnelId -> updatePw(prevConfig.getPwDescription(tunnelId),
595 config.getPwDescription(tunnelId)));
596
597 log.info("Pseudowires removed : {}, Pseudowires updated : {}, Pseudowires added : {}", rmvPWs,
598 updPws, addedPWs);
599 }
600
601 /**
602 * Helper function to update a pw.
603 * <p>
604 * Called upon configuration changes that update existing pseudowires and
605 * when links fail. Checking of mastership for CP1 is mandatory because it is
606 * called in multiple instances for both cases.
607 * <p>
608 * Meant to call asynchronously for various events, thus this call can not block and need
609 * to perform asynchronous operations.
610 * <p>
611 * For this reason error checking is omitted.
612 *
613 * @param oldPw the pseudo wire to remove
614 * @param newPw the pseudo wire to add
615 */
616 private void updatePw(L2TunnelDescription oldPw,
617 L2TunnelDescription newPw) {
618 ConnectPoint oldCp1 = oldPw.l2TunnelPolicy().cP1();
619 long tunnelId = oldPw.l2Tunnel().tunnelId();
620
621 // only the master of CP1 will update this pseudowire
622 if (!srManager.isMasterOf(oldPw.l2TunnelPolicy().cP1())) {
623 log.debug("Not the master of {}. Ignore pseudo wire update id={}", oldCp1, tunnelId);
624 return;
625 }
626 // only determine if the new pseudowire is leaf-spine, because
627 // removal process is the same for both leaf-leaf and leaf-spine pws
628 boolean newPwSpine;
629 try {
630 newPwSpine = !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP1().deviceId()) ||
631 !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP2().deviceId());
632 } catch (DeviceConfigNotFoundException e) {
633 // if exception is caught treat the new pw as leaf-leaf
634 newPwSpine = false;
635 }
636
637 // copy the variable here because we need to use it in lambda thus it needs to be final
638 boolean finalNewPwSpine = newPwSpine;
639
640 log.info("Updating pseudowire {}", oldPw.l2Tunnel().tunnelId());
641
642 // The async tasks to orchestrate the next and forwarding update
643 CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
644 CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
645 CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
646 CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
647 CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>();
648 CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>();
649
650 // first delete all information from our stores, we can not do it asynchronously
651 l2PolicyStore.remove(Long.toString(tunnelId));
652
653 // grab the old l2 tunnel from the store, since it carries information which is not exposed
654 // to the user configuration and set it to oldPw.
655 oldPw.setL2Tunnel(l2TunnelStore.get(Long.toString(tunnelId)).value());
656 VlanId transportVlan = l2TunnelStore.get(Long.toString(tunnelId)).value().transportVlan();
657 l2TunnelStore.remove(Long.toString(tunnelId));
658
659 // remove the reserved transport vlan, if one is used
660 if (!transportVlan.equals(UNTAGGED_TRANSPORT_VLAN)) {
661 vlanStore.remove(transportVlan);
662 }
663
664 // First we remove both policy.
665 log.debug("Start deleting fwd policy for {}", tunnelId);
666 VlanId egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP1OuterTag(),
667 oldPw.l2TunnelPolicy().cP1InnerTag(),
668 oldPw.l2TunnelPolicy().cP2OuterTag(),
669 oldPw.l2TunnelPolicy().cP2InnerTag());
670 deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP1(),
671 oldPw.l2TunnelPolicy().cP1InnerTag(),
672 oldPw.l2TunnelPolicy().cP1OuterTag(),
673 egressVlan,
674 fwdInitNextFuture,
675 FWD);
676
677 log.debug("Update process : Start deleting rev policy for {}", tunnelId);
678 egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP2OuterTag(),
679 oldPw.l2TunnelPolicy().cP2InnerTag(),
680 oldPw.l2TunnelPolicy().cP1OuterTag(),
681 oldPw.l2TunnelPolicy().cP1InnerTag());
682 deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP2(),
683 oldPw.l2TunnelPolicy().cP2InnerTag(),
684 oldPw.l2TunnelPolicy().cP2OuterTag(),
685 egressVlan, revInitNextFuture,
686 REV);
687
688 // Finally we remove both the tunnels.
689 fwdInitNextFuture.thenAcceptAsync(status -> {
690 if (status == null) {
691 log.debug("Update process : Fwd policy removed. " +
692 "Now remove fwd {} for {}", INITIATION, tunnelId);
693 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP1(), fwdTermNextFuture, FWD);
694 }
695 });
696 revInitNextFuture.thenAcceptAsync(status -> {
697 if (status == null) {
698 log.debug("Update process : Rev policy removed. " +
699 "Now remove rev {} for {}", INITIATION, tunnelId);
700 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP2(), revTermNextFuture, REV);
701 }
702 });
703 fwdTermNextFuture.thenAcceptAsync(status -> {
704 if (status == null) {
705 log.debug("Update process : Fwd {} removed. " +
706 "Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId);
707 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP2(), fwdPwFuture, FWD);
708 }
709 });
710 revTermNextFuture.thenAcceptAsync(status -> {
711 if (status == null) {
712 log.debug("Update process : Rev {} removed. " +
713 "Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId);
714 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP1(), revPwFuture, REV);
715 }
716 });
717
718 // get path here, need to use the same for fwd and rev direction
719 List<Link> path = getPath(newPw.l2TunnelPolicy().cP1(),
720 newPw.l2TunnelPolicy().cP2());
721 if (path == null) {
722 log.error("Update process : " +
723 "No path between the connection points for pseudowire {}", newPw.l2Tunnel().tunnelId());
724 return;
725 }
726
727 Link fwdNextHop, revNextHop;
728 if (!finalNewPwSpine) {
729 if (path.size() != 2) {
730 log.error("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
731 newPw.l2Tunnel().tunnelId());
732 return;
733 }
734 fwdNextHop = path.get(0);
735 revNextHop = reverseLink(path.get(1));
736 } else {
737 if (path.size() != 1) {
738 log.error("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
739 newPw.l2Tunnel().tunnelId());
740 return;
741 }
742 fwdNextHop = path.get(0);
743 revNextHop = reverseLink(path.get(0));
744 }
745
746 // set new path and transport vlan.
747 newPw.l2Tunnel().setPath(path);
748 newPw.l2Tunnel().setTransportVlan(determineTransportVlan(newPwSpine));
749
750 // At the end we install the updated PW.
751 fwdPwFuture.thenAcceptAsync(status -> {
752 if (status == null) {
753
754 // Upgrade stores and book keeping information, need to move this here
755 // cause this call is asynchronous.
756 l2PolicyStore.put(Long.toString(tunnelId), newPw.l2TunnelPolicy());
757 l2TunnelStore.put(Long.toString(tunnelId), newPw.l2Tunnel());
758
759 log.debug("Update process : Deploying new fwd pw for {}", tunnelId);
760 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(),
761 newPw.l2TunnelPolicy().cP2(), FWD,
762 fwdNextHop, finalNewPwSpine);
763 if (lamdaResult != SUCCESS) {
764 return;
765 }
766
767 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP1OuterTag(),
768 newPw.l2TunnelPolicy().cP1InnerTag(),
769 newPw.l2TunnelPolicy().cP2OuterTag(),
770 newPw.l2TunnelPolicy().cP2InnerTag());
771
772 lamdaResult = deployPolicy(tunnelId, newPw.l2TunnelPolicy().cP1(),
773 newPw.l2TunnelPolicy().cP1InnerTag(),
774 newPw.l2TunnelPolicy().cP1OuterTag(),
775 egressVlanId, lamdaResult.nextId);
776 if (lamdaResult != SUCCESS) {
777 return;
778 }
779 deployPseudoWireTerm(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(),
780 VlanId.NONE, FWD, finalNewPwSpine);
781
782 }
783 });
784 revPwFuture.thenAcceptAsync(status -> {
785 if (status == null) {
786
787 log.debug("Update process : Deploying new rev pw for {}", tunnelId);
788 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
789 newPw.l2TunnelPolicy().cP2(),
790 newPw.l2TunnelPolicy().cP1(),
791 REV,
792 revNextHop, finalNewPwSpine);
793 if (lamdaResult != SUCCESS) {
794 return;
795 }
796
797 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP2OuterTag(),
798 newPw.l2TunnelPolicy().cP2InnerTag(),
799 newPw.l2TunnelPolicy().cP1OuterTag(),
800 newPw.l2TunnelPolicy().cP1InnerTag());
801 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(),
812 VlanId.NONE,
813 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,
1058 ConnectPoint egress, Direction direction, Link nextHop, boolean spinePw) {
1059
1060 if (nextHop == null) {
1061 log.warn("No path between ingress and egress cps for tunnel {}", l2Tunnel.tunnelId());
1062 return WRONG_PARAMETERS;
1063 }
1064
1065 // We create the next objective without the metadata
1066 // context and id. We check if it already exists in the
1067 // store. If not we store as it is in the store.
1068 NextObjective.Builder nextObjectiveBuilder = createNextObjective(INITIATION,
1069 nextHop.src(),
1070 nextHop.dst(),
1071 l2Tunnel,
1072 egress.deviceId(),
1073 spinePw);
1074
1075 if (nextObjectiveBuilder == null) {
1076 return INTERNAL_ERROR;
1077 }
1078 // We set the metadata. We will use this metadata
1079 // to inform the driver we are doing a l2 tunnel.
1080 TrafficSelector metadata = DefaultTrafficSelector
1081 .builder()
1082 .matchTunnelId(l2Tunnel.tunnelId())
1083 .build();
1084 nextObjectiveBuilder.withMeta(metadata);
1085 int nextId = srManager.flowObjectiveService.allocateNextId();
1086 if (nextId < 0) {
1087 log.warn("Not able to allocate a next id for initiation");
1088 return INTERNAL_ERROR;
1089 }
1090 nextObjectiveBuilder.withId(nextId);
1091 String key = generateKey(l2Tunnel.tunnelId(), direction);
1092 l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add());
1093 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1094 log.debug("Initiation l2 tunnel rule " +
1095 "for {} populated",
1096 l2Tunnel.tunnelId()),
1097 (objective, error) ->
1098 log.warn("Failed to populate Initiation " +
1099 "l2 tunnel rule for {}: {}",
1100 l2Tunnel.tunnelId(), error));
1101 NextObjective nextObjective = nextObjectiveBuilder.add(context);
1102 srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective);
1103 log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}",
1104 l2Tunnel.tunnelId(), nextObjective.id());
1105 Result result = SUCCESS;
1106 result.nextId = nextObjective.id();
1107 return result;
1108 }
1109
1110 /**
1111 * Handles the tunnel termination, which consists in the creation
1112 * of a forwarding objective and a next objective.
1113 *
1114 * @param l2Tunnel the tunnel to terminate
1115 * @param egress the egress point
1116 * @param egressVlan the expected vlan at egress
1117 * @param direction the direction
1118 * @param spinePw if the pseudowire involves a spine switch
1119 * @return the result of the operation
1120 */
1121 private Result deployPseudoWireTerm(L2Tunnel l2Tunnel, ConnectPoint egress,
1122 VlanId egressVlan, Direction direction, boolean spinePw) {
1123
1124 // We create the group relative to the termination.
1125 NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
1126 l2Tunnel, egress.deviceId(),
1127 spinePw);
1128 if (nextObjectiveBuilder == null) {
1129 return INTERNAL_ERROR;
1130 }
1131 TrafficSelector metadata = DefaultTrafficSelector
1132 .builder()
1133 .matchVlanId(egressVlan)
1134 .build();
1135 nextObjectiveBuilder.withMeta(metadata);
1136 int nextId = srManager.flowObjectiveService.allocateNextId();
1137 if (nextId < 0) {
1138 log.warn("Not able to allocate a next id for initiation");
1139 return INTERNAL_ERROR;
1140 }
1141 nextObjectiveBuilder.withId(nextId);
1142 String key = generateKey(l2Tunnel.tunnelId(), direction);
1143 l2TerminationNextObjStore.put(key, nextObjectiveBuilder.add());
1144 ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("Termination l2 tunnel rule " +
1145 "for {} populated",
1146 l2Tunnel.tunnelId()),
1147 (objective, error) -> log.warn("Failed to populate " +
1148 "termination l2 tunnel " +
1149 "rule for {}: {}",
1150 l2Tunnel.tunnelId(),
1151 error));
1152 NextObjective nextObjective = nextObjectiveBuilder.add(context);
1153 srManager.flowObjectiveService.next(egress.deviceId(), nextObjective);
1154 log.debug("Termination next objective for {} not found. Creating new NextObj with id={}",
1155 l2Tunnel.tunnelId(), nextObjective.id());
1156
1157 // We create the flow relative to the termination.
1158 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(), l2Tunnel.tunnelId(),
1159 egress.port(), nextObjective.id());
1160 context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for tunnel termination {} populated",
1161 l2Tunnel.tunnelId()),
1162 (objective, error) -> log.warn("Failed to populate fwdrObj" +
1163 " for tunnel termination {}",
1164 l2Tunnel.tunnelId(), error));
1165 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context));
1166 log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}",
1167 nextId, l2Tunnel.tunnelId());
1168
1169 if (spinePw) {
1170
1171 // determine the input port at the
1172 PortNumber inPort;
1173
1174 if (egress.deviceId().
1175 equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
1176 inPort = l2Tunnel.pathUsed().get(0).dst().port();
1177 } else {
1178 inPort = l2Tunnel.pathUsed().get(0).src().port();
1179 }
1180
1181 MacAddress dstMac;
1182 try {
1183 dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
1184 } catch (Exception e) {
1185 log.info("Device not found in configuration, no programming of MAC address");
1186 dstMac = null;
1187 }
1188
1189 log.info("Populating filtering objective for pseudowire transport" +
1190 " with vlan = {}, port = {}, mac = {}",
1191 l2Tunnel.transportVlan(),
1192 inPort,
1193 dstMac);
1194 FilteringObjective.Builder filteringObjectiveBuilder =
1195 createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
1196 context = new DefaultObjectiveContext(( objective ) ->
1197 log.debug("Special filtObj for " + "for {} populated",
1198 l2Tunnel.tunnelId()),
1199 ( objective, error ) ->
1200 log.warn("Failed to populate " +
1201 "special filtObj " +
1202 "rule for {}: {}",
1203 l2Tunnel.tunnelId(), error));
1204 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
1205 filteringObjectiveBuilder.withMeta(treatment.build());
1206 srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.add(context));
1207 log.debug("Creating new special FiltObj for termination point with tunnel {} for port {}",
1208 l2Tunnel.tunnelId(),
1209 inPort);
1210 }
1211
1212 return SUCCESS;
1213 }
1214
1215
1216 /**
1217 * Creates the filtering objective according to a given port and vlanid.
1218 *
1219 * @param inPort the in port
1220 * @param vlanId the inner vlan tag
1221 * @return the filtering objective
1222 */
1223 private FilteringObjective.Builder createNormalPipelineFiltObjective(PortNumber inPort,
1224 VlanId vlanId,
1225 MacAddress dstMac) {
1226
1227 log.info("Creating filtering objective for pseudowire transport with vlan={}, port={}, mac={}",
1228 vlanId,
1229 inPort,
1230 dstMac);
1231 FilteringObjective.Builder fwdBuilder = DefaultFilteringObjective
1232 .builder()
1233 .withKey(Criteria.matchInPort(inPort))
1234 .addCondition(Criteria.matchVlanId(vlanId))
1235 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1236 .permit()
1237 .fromApp(srManager.appId());
1238
1239 if (dstMac != null) {
1240 fwdBuilder.addCondition(Criteria.matchEthDst(dstMac));
1241 }
1242
1243 return fwdBuilder;
1244 }
1245
1246 /**
1247 * Creates the filtering objective according to a given policy.
1248 *
1249 * @param inPort the in port
1250 * @param innerTag the inner vlan tag
1251 * @param outerTag the outer vlan tag
1252 * @return the filtering objective
1253 */
1254 private FilteringObjective.Builder createFiltObjective(PortNumber inPort, VlanId innerTag, VlanId outerTag) {
1255
1256 log.info("Creating filtering objective for vlans {} / {}", outerTag, innerTag);
1257 return DefaultFilteringObjective
1258 .builder()
1259 .withKey(Criteria.matchInPort(inPort))
1260 .addCondition(Criteria.matchInnerVlanId(innerTag))
1261 .addCondition(Criteria.matchVlanId(outerTag))
1262 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1263 .permit()
1264 .fromApp(srManager.appId());
1265 }
1266
1267 /**
1268 * Creates the forwarding objective for the termination.
1269 *
1270 * @param pwLabel the pseudo wire label
1271 * @param tunnelId the tunnel id
1272 * @param egressPort the egress port
1273 * @param nextId the next step
1274 * @return the forwarding objective to support the termination
1275 */
1276 private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel, long tunnelId,
1277 PortNumber egressPort, int nextId) {
1278
1279 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1280 TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment.builder();
1281 // The flow has to match on the pw label and bos
1282 trafficSelector.matchEthType(Ethernet.MPLS_UNICAST);
1283 trafficSelector.matchMplsLabel(pwLabel);
1284 trafficSelector.matchMplsBos(true);
1285 // The flow has to decrement ttl, restore ttl in
1286 // pop mpls, set tunnel id and port.
1287 trafficTreatment.decMplsTtl();
1288 trafficTreatment.copyTtlIn();
1289 trafficTreatment.popMpls();
1290 trafficTreatment.setTunnelId(tunnelId);
1291 trafficTreatment.setOutput(egressPort);
1292
1293 return DefaultForwardingObjective
1294 .builder()
1295 .fromApp(srManager.appId())
1296 .makePermanent()
1297 .nextStep(nextId)
1298 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1299 .withSelector(trafficSelector.build())
1300 .withTreatment(trafficTreatment.build())
1301 .withFlag(VERSATILE);
1302 }
1303
1304 /**
1305 * Creates the forwarding objective for the initiation.
1306 *
1307 * @param tunnelId the tunnel id
1308 * @param inPort the input port
1309 * @param nextId the next step
1310 * @return the forwarding objective to support the initiation.
1311 */
1312 private ForwardingObjective.Builder createInitFwdObjective(long tunnelId, PortNumber inPort, int nextId) {
1313
1314 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1315
1316 // The flow has to match on the mpls logical
1317 // port and the tunnel id.
1318 trafficSelector.matchTunnelId(tunnelId);
1319 trafficSelector.matchInPort(inPort);
1320
1321 return DefaultForwardingObjective
1322 .builder()
1323 .fromApp(srManager.appId())
1324 .makePermanent()
1325 .nextStep(nextId)
1326 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1327 .withSelector(trafficSelector.build())
1328 .withFlag(VERSATILE);
1329
1330 }
1331
1332 /**
1333 * Creates the next objective according to a given
1334 * pipeline. We don't set the next id and we don't
1335 * create the final meta to check if we are re-using
1336 * the same next objective for different tunnels.
1337 *
1338 * @param pipeline the pipeline to support
1339 * @param srcCp the source port
1340 * @param dstCp the destination port
1341 * @param l2Tunnel the tunnel to support
1342 * @param egressId the egress device id
1343 * @param spinePw if the pw involves a spine switch
1344 * @return the next objective to support the pipeline
1345 */
1346 private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp,
1347 ConnectPoint dstCp, L2Tunnel l2Tunnel,
1348 DeviceId egressId, boolean spinePw) {
1349 NextObjective.Builder nextObjBuilder;
1350 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
1351 if (pipeline == INITIATION) {
1352 nextObjBuilder = DefaultNextObjective
1353 .builder()
1354 .withType(NextObjective.Type.SIMPLE)
1355 .fromApp(srManager.appId());
1356 // The pw label is the bottom of stack. It has to
1357 // be different -1.
1358 if (l2Tunnel.pwLabel().toInt() == MplsLabel.MAX_MPLS) {
1359 log.warn("Pw label not configured");
1360 return null;
1361 }
1362 treatmentBuilder.pushMpls();
1363 treatmentBuilder.setMpls(l2Tunnel.pwLabel());
1364 treatmentBuilder.setMplsBos(true);
1365 treatmentBuilder.copyTtlOut();
1366
1367 // If the inter-co label is present we have to set the label.
1368 if (l2Tunnel.interCoLabel().toInt() != MplsLabel.MAX_MPLS) {
1369 treatmentBuilder.pushMpls();
1370 treatmentBuilder.setMpls(l2Tunnel.interCoLabel());
1371 treatmentBuilder.setMplsBos(false);
1372 treatmentBuilder.copyTtlOut();
1373 }
1374
1375 // if pw is leaf-to-leaf we need to
1376 // add the routing label also
1377 if (!spinePw) {
1378 // We retrieve the sr label from the config
1379 // specific for pseudowire traffic
1380 // using the egress leaf device id.
1381 MplsLabel srLabel;
1382 try {
1383 srLabel = MplsLabel.mplsLabel(srManager.deviceConfiguration().getPWRoutingLabel(egressId));
1384
1385 } catch (DeviceConfigNotFoundException e) {
1386 log.warn("Sr label for pw traffic not configured");
1387 return null;
1388 }
1389
1390 treatmentBuilder.pushMpls();
1391 treatmentBuilder.setMpls(srLabel);
1392 treatmentBuilder.setMplsBos(false);
1393 treatmentBuilder.copyTtlOut();
1394 }
1395
1396 // We have to rewrite the src and dst mac address.
1397 MacAddress ingressMac;
1398 try {
1399 ingressMac = srManager.deviceConfiguration().getDeviceMac(srcCp.deviceId());
1400 } catch (DeviceConfigNotFoundException e) {
1401 log.warn("Was not able to find the ingress mac");
1402 return null;
1403 }
1404 treatmentBuilder.setEthSrc(ingressMac);
1405 MacAddress neighborMac;
1406 try {
1407 neighborMac = srManager.deviceConfiguration().getDeviceMac(dstCp.deviceId());
1408 } catch (DeviceConfigNotFoundException e) {
1409 log.warn("Was not able to find the neighbor mac");
1410 return null;
1411 }
1412 treatmentBuilder.setEthDst(neighborMac);
1413
1414 // if not a leaf-spine pw we need to POP the vlan at the output
1415 // since we carry this traffic untagged.
1416 if (!spinePw) {
1417 treatmentBuilder.popVlan();
1418 }
1419 } else {
1420 // We create the next objective which
1421 // will be a simple l2 group.
1422 nextObjBuilder = DefaultNextObjective
1423 .builder()
1424 .withType(NextObjective.Type.SIMPLE)
1425 .fromApp(srManager.appId());
1426 }
1427
1428 // set the appropriate transport vlan
1429 treatmentBuilder.setVlanId(l2Tunnel.transportVlan());
1430 treatmentBuilder.setOutput(srcCp.port());
1431 nextObjBuilder.addTreatment(treatmentBuilder.build());
1432 return nextObjBuilder;
1433 }
1434
1435 /**
1436 * Reverses a link.
1437 *
1438 * @param link link to be reversed
1439 * @return the reversed link
1440 */
1441 private Link reverseLink(Link link) {
1442
1443 DefaultLink.Builder linkBuilder = DefaultLink.builder();
1444
1445 linkBuilder.src(link.dst());
1446 linkBuilder.dst(link.src());
1447 linkBuilder.type(link.type());
1448 linkBuilder.providerId(link.providerId());
1449
1450 return linkBuilder.build();
1451 }
1452
1453 /**
1454 * Returns the path betwwen two connect points.
1455 *
1456 * @param srcCp source connect point
1457 * @param dstCp destination connect point
1458 * @return the path
1459 */
1460 private List<Link> getPath(ConnectPoint srcCp, ConnectPoint dstCp) {
1461 /* TODO We retrieve a set of paths in case of a link failure, what happens
1462 * if the TopologyService gets the link notification AFTER us and has not updated the paths?
1463 *
1464 * TODO This has the potential to act on old topology.
1465 * Maybe we should make SRManager be a listener on topology events instead raw link events.
1466 */
1467 Set<Path> paths = srManager.topologyService.getPaths(
1468 srManager.topologyService.currentTopology(),
1469 srcCp.deviceId(), dstCp.deviceId());
1470
1471 log.debug("Paths obtained from topology service {}", paths);
1472
1473 // We randomly pick a path.
1474 if (paths.isEmpty()) {
1475 return null;
1476 }
1477 int size = paths.size();
1478 int index = RandomUtils.nextInt(0, size);
1479
1480 List<Link> result = Iterables.get(paths, index).links();
1481 log.debug("Randomly picked a path {}", result);
1482
1483 return result;
1484 }
1485
1486 /**
1487 * Deletes a given policy using the parameter supplied.
1488 *
1489 * @param tunnelId the tunnel id
1490 * @param ingress the ingress point
1491 * @param ingressInner the ingress inner vlan id
1492 * @param ingressOuter the ingress outer vlan id
1493 * @param future to perform the async operation
1494 * @param direction the direction: forward or reverse
1495 */
1496 private void deletePolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter,
1497 VlanId egressVlan, CompletableFuture<ObjectiveError> future, Direction direction) {
1498
1499 String key = generateKey(tunnelId, direction);
1500 if (!l2InitiationNextObjStore.containsKey(key)) {
1501 log.warn("Abort delete of policy for tunnel {}: next does not exist in the store", tunnelId);
1502 if (future != null) {
1503 future.complete(null);
1504 }
1505 return;
1506 }
1507 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
1508 int nextId = nextObjective.id();
1509 List<Objective> objectives = Lists.newArrayList();
1510 // We create the forwarding objective.
1511 ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
1512 ObjectiveContext context = new ObjectiveContext() {
1513 @Override
1514 public void onSuccess(Objective objective) {
1515 log.debug("Previous fwdObj for policy {} removed", tunnelId);
1516 if (future != null) {
1517 future.complete(null);
1518 }
1519 }
1520
1521 @Override
1522 public void onError(Objective objective, ObjectiveError error) {
1523 log.warn("Failed to remove previous fwdObj for policy {}: {}", tunnelId, error);
1524 if (future != null) {
1525 future.complete(error);
1526 }
1527 }
1528 };
1529 objectives.add(fwdBuilder.remove(context));
1530 // We create the filtering objective to define the
1531 // permit traffic in the switch
1532 FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
1533 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
1534 .builder()
1535 .setTunnelId(tunnelId)
1536 .setVlanId(egressVlan);
1537 filtBuilder.withMeta(treatment.build());
1538 context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for policy {} revoked", tunnelId),
1539 (objective, error) ->
1540 log.warn("Failed to revoke filterObj for policy {}",
1541 tunnelId, error));
1542 objectives.add(filtBuilder.remove(context));
1543
1544 for (Objective objective : objectives) {
1545 if (objective instanceof ForwardingObjective) {
1546 srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
1547 } else {
1548 srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
1549 }
1550 }
1551 }
1552
1553 /**
1554 * Deletes the pseudo wire initiation.
1555 *
1556 * @param l2TunnelId the tunnel id
1557 * @param ingress the ingress connect point
1558 * @param future to perform an async operation
1559 * @param direction the direction: reverse of forward
1560 */
1561 private void tearDownPseudoWireInit(long l2TunnelId, ConnectPoint ingress,
1562 CompletableFuture<ObjectiveError> future, Direction direction) {
1563
1564 String key = generateKey(l2TunnelId, direction);
1565 if (!l2InitiationNextObjStore.containsKey(key)) {
1566 log.info("Abort delete of {} for {}: next does not exist in the store", INITIATION, key);
1567 if (future != null) {
1568 future.complete(null);
1569 }
1570 return;
1571 }
1572 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
1573
1574 // un-comment in case you want to delete groups used by the pw
1575 // however, this will break the update of pseudowires cause the L2 interface group can
1576 // not be deleted (it is referenced by other groups)
1577 /*
1578 ObjectiveContext context = new ObjectiveContext() {
1579 @Override
1580 public void onSuccess(Objective objective) {
1581 log.debug("Previous {} next for {} removed", INITIATION, key);
1582 if (future != null) {
1583 future.complete(null);
1584 }
1585 }
1586
1587 @Override
1588 public void onError(Objective objective, ObjectiveError error) {
1589 log.warn("Failed to remove previous {} next for {}: {}", INITIATION, key, error);
1590 if (future != null) {
1591 future.complete(error);
1592 }
1593 }
1594 };
1595 srManager.flowObjectiveService.next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1596 */
1597
1598 future.complete(null);
1599 l2InitiationNextObjStore.remove(key);
1600 }
1601
1602 /**
1603 * Deletes the pseudo wire termination.
1604 *
1605 * @param l2Tunnel the tunnel
1606 * @param egress the egress connect point
1607 * @param future the async task
1608 * @param direction the direction of the tunnel
1609 */
1610 private void tearDownPseudoWireTerm(L2Tunnel l2Tunnel,
1611 ConnectPoint egress,
1612 CompletableFuture<ObjectiveError> future,
1613 Direction direction) {
1614
1615 String key = generateKey(l2Tunnel.tunnelId(), direction);
1616 if (!l2TerminationNextObjStore.containsKey(key)) {
1617 log.info("Abort delete of {} for {}: next does not exist in the store", TERMINATION, key);
1618 if (future != null) {
1619 future.complete(null);
1620 }
1621 return;
1622 }
1623 NextObjective nextObjective = l2TerminationNextObjStore.get(key).value();
1624 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(),
1625 l2Tunnel.tunnelId(),
1626 egress.port(),
1627 nextObjective.id());
1628 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1629 log.debug("FwdObj for {} {}, " +
1630 "direction {} removed",
1631 TERMINATION,
1632 l2Tunnel.tunnelId(),
1633 direction),
1634 (objective, error) ->
1635 log.warn("Failed to remove fwdObj " +
1636 "for {} {}" +
1637 ", direction {}",
1638 TERMINATION,
1639 l2Tunnel.tunnelId(),
1640 error,
1641 direction));
1642 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context));
1643
1644 // un-comment in case you want to delete groups used by the pw
1645 // however, this will break the update of pseudowires cause the L2 interface group can
1646 // not be deleted (it is referenced by other groups)
1647 /*
1648 context = new ObjectiveContext() {
1649 @Override
1650 public void onSuccess(Objective objective) {
1651 log.debug("Previous {} next for {} removed", TERMINATION, key);
1652 if (future != null) {
1653 future.complete(null);
1654 }
1655 }
1656
1657 @Override
1658 public void onError(Objective objective, ObjectiveError error) {
1659 log.warn("Failed to remove previous {} next for {}: {}", TERMINATION, key, error);
1660 if (future != null) {
1661 future.complete(error);
1662 }
1663 }
1664 };
1665 srManager.flowObjectiveService.next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1666 */
1667
1668 // delete the extra filtering objective for terminating
1669 // spine-spine pws
1670 if (!l2Tunnel.transportVlan().equals(UNTAGGED_TRANSPORT_VLAN)) {
1671
1672 // determine the input port at the
1673 PortNumber inPort;
1674
1675 if (egress.deviceId().
1676 equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
1677 inPort = l2Tunnel.pathUsed().get(0).dst().port();
1678 } else {
1679 inPort = l2Tunnel.pathUsed().get(0).src().port();
1680 }
1681
1682 MacAddress dstMac;
1683 try {
1684 dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
1685 } catch (Exception e) {
1686 log.info("Device not found in configuration, no programming of MAC address");
1687 dstMac = null;
1688 }
1689
1690 log.info("Removing filtering objective for pseudowire transport" +
1691 " with vlan = {}, port = {}, mac = {}",
1692 l2Tunnel.transportVlan(),
1693 inPort,
1694 dstMac);
1695 FilteringObjective.Builder filteringObjectiveBuilder =
1696 createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
1697 context = new DefaultObjectiveContext(( objective ) ->
1698 log.debug("Special filtObj for " + "for {} removed",
1699 l2Tunnel.tunnelId()), ( objective, error ) ->
1700 log.warn("Failed to populate " + "special filtObj " +
1701 "rule for {}: {}", l2Tunnel.tunnelId(), error));
1702 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
1703 filteringObjectiveBuilder.withMeta(treatment.build());
1704 srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.remove(context));
1705 log.debug("Removing special FiltObj for termination point with tunnel {} for port {}",
1706 l2Tunnel.tunnelId(),
1707 inPort);
1708 }
1709
1710 l2TerminationNextObjStore.remove(key);
1711 future.complete(null);
1712 }
1713
1714 /**
1715 * Utilities to generate pw key.
1716 *
1717 * @param tunnelId the tunnel id
1718 * @param direction the direction of the pw
1719 * @return the key of the store
1720 */
1721 private String generateKey(long tunnelId, Direction direction) {
1722 return String.format("%s-%s", tunnelId, direction);
1723 }
1724
1725}