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