blob: a2048b0689f9ea3d8a71fb3856861dddfb7431e7 [file] [log] [blame]
Pier Ventre6b19e482016-11-07 16:21:04 -08001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Pier Ventre6b19e482016-11-07 16:21:04 -08003 *
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
Pier Ventref3cf5b92016-11-09 14:17:26 -080019import com.google.common.collect.Iterables;
20import com.google.common.collect.Lists;
21import org.apache.commons.lang3.RandomUtils;
Pier Ventreac3e1d92016-11-17 22:26:29 -080022import org.onlab.packet.Ethernet;
Pier Ventref3cf5b92016-11-09 14:17:26 -080023import org.onlab.packet.MacAddress;
24import org.onlab.packet.MplsLabel;
25import org.onlab.packet.VlanId;
26import org.onlab.util.KryoNamespace;
27import org.onosproject.net.ConnectPoint;
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -070028import org.onosproject.net.DefaultLink;
Pier Ventref3cf5b92016-11-09 14:17:26 -080029import org.onosproject.net.DeviceId;
Pier Ventref3cf5b92016-11-09 14:17:26 -080030import org.onosproject.net.Link;
Charles Chan83163812018-01-09 13:45:07 -080031import org.onosproject.net.Path;
Pier Ventref3cf5b92016-11-09 14:17:26 -080032import org.onosproject.net.PortNumber;
Pier Ventre6b19e482016-11-07 16:21:04 -080033import org.onosproject.net.config.NetworkConfigEvent;
Pier Ventref3cf5b92016-11-09 14:17:26 -080034import 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;
Pier Ventre6b19e482016-11-07 16:21:04 -080049import org.onosproject.segmentrouting.SegmentRoutingManager;
Pier Ventref3cf5b92016-11-09 14:17:26 -080050import org.onosproject.segmentrouting.SegmentRoutingService;
51import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
Pier Ventre6b19e482016-11-07 16:21:04 -080052import org.onosproject.segmentrouting.config.PwaasConfig;
Pier Ventref3cf5b92016-11-09 14:17:26 -080053import org.onosproject.store.serializers.KryoNamespaces;
54import org.onosproject.store.service.ConsistentMap;
55import org.onosproject.store.service.Serializer;
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -070056import org.onosproject.store.service.Versioned;
Pier Ventre6b19e482016-11-07 16:21:04 -080057import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
Pier Ventref3cf5b92016-11-09 14:17:26 -080060import java.util.List;
61import java.util.Set;
62import java.util.concurrent.CompletableFuture;
63import java.util.stream.Collectors;
64
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -080065import static com.google.common.base.Preconditions.checkArgument;
Pier Ventref3cf5b92016-11-09 14:17:26 -080066import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.VERSATILE;
Pier Ventref3cf5b92016-11-09 14:17:26 -080067import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.INITIATION;
Pier Ventreac3e1d92016-11-17 22:26:29 -080068import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.TERMINATION;
Pier Ventref3cf5b92016-11-09 14:17:26 -080069import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Result.*;
Pier Ventreac3e1d92016-11-17 22:26:29 -080070import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.FWD;
71import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.REV;
Pier Ventref3cf5b92016-11-09 14:17:26 -080072
Pier Ventre6b19e482016-11-07 16:21:04 -080073/**
74 * Handles pwaas related events.
75 */
76public class L2TunnelHandler {
Pier Ventref3cf5b92016-11-09 14:17:26 -080077
Pier Ventre6b19e482016-11-07 16:21:04 -080078 private static final Logger log = LoggerFactory.getLogger(L2TunnelHandler.class);
Pier Ventref3cf5b92016-11-09 14:17:26 -080079
Pier Ventre6b19e482016-11-07 16:21:04 -080080 private final SegmentRoutingManager srManager;
Pier Ventref3cf5b92016-11-09 14:17:26 -080081 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -080082 * To store the next objectives related to the initiation.
83 */
84 private final ConsistentMap<String, NextObjective> l2InitiationNextObjStore;
85 /**
86 * To store the next objectives related to the termination.
87 */
88 private final ConsistentMap<String, NextObjective> l2TerminationNextObjStore;
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -070089
Pier Ventreac3e1d92016-11-17 22:26:29 -080090 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -070091 * To store policies.
Pier Ventref3cf5b92016-11-09 14:17:26 -080092 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -070093 private final ConsistentMap<String, DefaultL2TunnelPolicy> l2PolicyStore;
94
95 /**
96 * To store tunnels.
97 */
98 private final ConsistentMap<String, DefaultL2Tunnel> l2TunnelStore;
99
Pier Ventref3cf5b92016-11-09 14:17:26 -0800100 private final KryoNamespace.Builder l2TunnelKryo;
101
102 /**
103 * Create a l2 tunnel handler for the deploy and
104 * for the tear down of pseudo wires.
105 *
106 * @param segmentRoutingManager the segment routing manager
107 */
108 public L2TunnelHandler(SegmentRoutingManager segmentRoutingManager) {
109 srManager = segmentRoutingManager;
110 l2TunnelKryo = new KryoNamespace.Builder()
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700111 .register(KryoNamespaces.API)
112 .register(DefaultL2Tunnel.class,
113 DefaultL2TunnelPolicy.class,
114 L2Mode.class,
115 MplsLabel.class,
116 VlanId.class,
117 ConnectPoint.class);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800118
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700119 l2InitiationNextObjStore = srManager.
120 storageService.
121 <String, NextObjective>consistentMapBuilder().
122 withName("onos-l2initiation-nextobj-store").
123 withSerializer(Serializer.using(l2TunnelKryo.build())).
124 build();
Pier Ventreac3e1d92016-11-17 22:26:29 -0800125
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700126 l2TerminationNextObjStore = srManager.storageService.
127 <String, NextObjective>consistentMapBuilder()
Pier Ventreac3e1d92016-11-17 22:26:29 -0800128 .withName("onos-l2termination-nextobj-store")
129 .withSerializer(Serializer.using(l2TunnelKryo.build()))
130 .build();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700131
132 l2PolicyStore = srManager.storageService
133 .<String, DefaultL2TunnelPolicy>consistentMapBuilder()
134 .withName("onos-l2-policy-store")
135 .withSerializer(Serializer.using(l2TunnelKryo.build()))
136 .build();
137
138 l2TunnelStore = srManager.storageService
139 .<String, DefaultL2Tunnel>consistentMapBuilder()
140 .withName("onos-l2-tunnel-store")
141 .withSerializer(Serializer.using(l2TunnelKryo.build()))
142 .build();
143 }
144
145 /**
Andreas Pantelopoulos75eef062018-01-11 07:53:48 -0800146 * Deploys any pre-existing pseudowires in the configuration.
147 * Used by manager only in initialization.
148 */
149 public void init() {
150
151 PwaasConfig config = srManager.cfgService.getConfig(srManager.appId(), PwaasConfig.class);
152 if (config == null) {
153 return;
154 }
155
156 log.info("Deploying existing pseudowires");
157
158 // gather pseudowires
159 Set<DefaultL2TunnelDescription> pwToAdd = config
160 .getPwIds()
161 .stream()
162 .map(config::getPwDescription)
163 .collect(Collectors.toSet());
164
165 // deploy pseudowires
166 deploy(pwToAdd);
167 }
168
169 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700170 * Returns all L2 Policies.
171 *
172 * @return List of policies
173 */
174 public List<DefaultL2TunnelPolicy> getL2Policies() {
175
176 return l2PolicyStore
177 .values()
178 .stream()
179 .map(Versioned::value)
180 .map(DefaultL2TunnelPolicy::new)
181 .collect(Collectors.toList());
182
183 }
184
185 /**
186 * Returns all L2 Tunnels.
187 *
188 * @return List of tunnels.
189 */
190 public List<DefaultL2Tunnel> getL2Tunnels() {
191
192 return l2TunnelStore
193 .values()
194 .stream()
195 .map(Versioned::value)
196 .map(DefaultL2Tunnel::new)
197 .collect(Collectors.toList());
198
199 }
200
201 /**
202 * Processes a link removal. Finds affected pseudowires and rewires them.
203 * TODO: Make it also take into account failures of links that are used for pw
204 * traffic in the spine.
205 * @param link The link that failed
206 */
207 public void processLinkDown(Link link) {
208
209 List<DefaultL2Tunnel> tunnels = getL2Tunnels();
210 List<DefaultL2TunnelPolicy> policies = getL2Policies();
211
212 // determine affected pseudowires and update them at once
213 Set<DefaultL2TunnelDescription> pwToUpdate = tunnels
214 .stream()
215 .filter(tun -> tun.pathUsed().contains(link))
216 .map(l2Tunnel -> {
217 DefaultL2TunnelPolicy policy = null;
218 for (DefaultL2TunnelPolicy l2Policy : policies) {
219 if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
220 policy = l2Policy;
221 break;
222 }
223 }
224
225 return new DefaultL2TunnelDescription(l2Tunnel, policy);
226 })
227 .collect(Collectors.toSet());
228
229
230 log.info("Pseudowires affected by link failure : {}, rerouting them...", pwToUpdate);
231
232 // update all pseudowires
233 pwToUpdate.forEach(tun -> updatePw(tun, tun));
Pier Ventre6b19e482016-11-07 16:21:04 -0800234 }
235
236 /**
237 * Processes Pwaas Config added event.
238 *
Pier Ventref3cf5b92016-11-09 14:17:26 -0800239 * @param event network config add event
Pier Ventre6b19e482016-11-07 16:21:04 -0800240 */
241 public void processPwaasConfigAdded(NetworkConfigEvent event) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800242 checkArgument(event.config().isPresent(),
243 "Config is not presented in PwaasConfigAdded event {}", event);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700244
245 log.info("Network event : Pseudowire configuration added!");
Pier Ventre6b19e482016-11-07 16:21:04 -0800246 PwaasConfig config = (PwaasConfig) event.config().get();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700247
248 // gather pseudowires
249 Set<DefaultL2TunnelDescription> pwToAdd = config
250 .getPwIds()
Pier Ventref3cf5b92016-11-09 14:17:26 -0800251 .stream()
252 .map(config::getPwDescription)
253 .collect(Collectors.toSet());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700254
255 // deploy pseudowires
Pier Ventref3cf5b92016-11-09 14:17:26 -0800256 deploy(pwToAdd);
257 }
258
Pier Ventreac3e1d92016-11-17 22:26:29 -0800259 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700260 * 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 */
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800271 private VlanId determineEgressVlan(VlanId ingressOuter, VlanId ingressInner,
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700272 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 /**
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800285 * Adds a single pseudowire from leaf to a leaf.
286 * This method can be called from cli commands
Charles Chane5a32552018-01-06 17:57:27 -0800287 * without configuration updates, thus it does not check for mastership
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700288 * of the ingress pseudowire device.
289 *
290 * @param pw The pseudowire
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800291 * @param spinePw True if pseudowire is from leaf to spine
292 * @return result of pseudowire deployment
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700293 */
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800294 private Result deployPseudowire(DefaultL2TunnelDescription pw, boolean spinePw) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700295
296 Result result;
297 long l2TunnelId;
298
299 l2TunnelId = pw.l2Tunnel().tunnelId();
300
301 // The tunnel id cannot be 0.
302 if (l2TunnelId == 0) {
303 log.warn("Tunnel id id must be > 0");
304 return Result.ADDITION_ERROR;
305 }
306
307 // get path here, need to use the same for fwd and rev direction
308 List<Link> path = getPath(pw.l2TunnelPolicy().cP1(),
309 pw.l2TunnelPolicy().cP2());
310 if (path == null) {
311 log.info("Deploying process : No path between the connection points for pseudowire {}", l2TunnelId);
312 return WRONG_PARAMETERS;
313 }
314
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800315 Link fwdNextHop;
316 Link revNextHop;
317 if (!spinePw) {
318 if (path.size() != 2) {
319 log.info("Deploying process : Path between two leafs should have size of 2, for pseudowire {}",
320 l2TunnelId);
321 return INTERNAL_ERROR;
322 }
323
324 fwdNextHop = path.get(0);
325 revNextHop = reverseLink(path.get(1));
326 } else {
327 if (path.size() != 1) {
Charles Chane5a32552018-01-06 17:57:27 -0800328 log.info("Deploying process : Path between leaf spine should equal to 1, for pseudowire {}",
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800329 l2TunnelId);
330 return INTERNAL_ERROR;
331 }
332
333 fwdNextHop = path.get(0);
334 revNextHop = reverseLink(path.get(0));
335 }
336
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700337 pw.l2Tunnel().setPath(path);
338
339 // next hops for next objectives
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700340
341 log.info("Deploying process : Establishing forward direction for pseudowire {}", l2TunnelId);
342
343 // We establish the tunnel.
344 // result.nextId will be used in fwd
345 result = deployPseudoWireInit(pw.l2Tunnel(),
346 pw.l2TunnelPolicy().cP1(),
347 pw.l2TunnelPolicy().cP2(),
348 FWD,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800349 fwdNextHop,
350 spinePw);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700351 if (result != SUCCESS) {
352 log.info("Deploying process : Error in deploying pseudowire initiation for CP1");
353 return Result.ADDITION_ERROR;
354 }
355
356 VlanId egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP1OuterTag(),
357 pw.l2TunnelPolicy().cP1InnerTag(),
358 pw.l2TunnelPolicy().cP2OuterTag(),
359 pw.l2TunnelPolicy().cP2InnerTag());
360
361 // We create the policy.
362 result = deployPolicy(l2TunnelId,
363 pw.l2TunnelPolicy().cP1(),
364 pw.l2TunnelPolicy().cP1InnerTag(),
365 pw.l2TunnelPolicy().cP1OuterTag(),
366 egressVlan,
367 result.nextId);
368 if (result != SUCCESS) {
369 log.info("Deploying process : Error in deploying pseudowire policy for CP1");
370 return Result.ADDITION_ERROR;
371 }
372
373 // We terminate the tunnel
374 result = deployPseudoWireTerm(pw.l2Tunnel(),
375 pw.l2TunnelPolicy().cP2(),
Andreas Pantelopoulos5a243d62018-01-17 10:03:00 -0800376 VlanId.NONE,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800377 FWD,
378 spinePw);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700379
380 if (result != SUCCESS) {
381 log.info("Deploying process : Error in deploying pseudowire termination for CP1");
382 return Result.ADDITION_ERROR;
383
384 }
385
386 log.info("Deploying process : Establishing reverse direction for pseudowire {}", l2TunnelId);
387
388 // We establish the reverse tunnel.
389 result = deployPseudoWireInit(pw.l2Tunnel(),
390 pw.l2TunnelPolicy().cP2(),
391 pw.l2TunnelPolicy().cP1(),
392 REV,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800393 revNextHop,
394 spinePw);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700395 if (result != SUCCESS) {
396 log.info("Deploying process : Error in deploying pseudowire initiation for CP2");
397 return Result.ADDITION_ERROR;
398 }
399
400 egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP2OuterTag(),
401 pw.l2TunnelPolicy().cP2InnerTag(),
402 pw.l2TunnelPolicy().cP1OuterTag(),
403 pw.l2TunnelPolicy().cP1InnerTag());
404 result = deployPolicy(l2TunnelId,
405 pw.l2TunnelPolicy().cP2(),
406 pw.l2TunnelPolicy().cP2InnerTag(),
407 pw.l2TunnelPolicy().cP2OuterTag(),
408 egressVlan,
409 result.nextId);
410 if (result != SUCCESS) {
411 log.info("Deploying process : Error in deploying policy for CP2");
412 return Result.ADDITION_ERROR;
413 }
414
415 result = deployPseudoWireTerm(pw.l2Tunnel(),
416 pw.l2TunnelPolicy().cP1(),
Andreas Pantelopoulos5a243d62018-01-17 10:03:00 -0800417 VlanId.NONE,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800418 REV,
419 spinePw);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700420
421 if (result != SUCCESS) {
422 log.info("Deploying process : Error in deploying pseudowire termination for CP2");
423 return Result.ADDITION_ERROR;
424 }
425
426 log.info("Deploying process : Updating relevant information for pseudowire {}", l2TunnelId);
427
428 // Populate stores
429 l2TunnelStore.put(Long.toString(l2TunnelId), pw.l2Tunnel());
430 l2PolicyStore.put(Long.toString(l2TunnelId), pw.l2TunnelPolicy());
431
432 return Result.SUCCESS;
433 }
434
435 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -0800436 * To deploy a number of pseudo wires.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700437 * <p>
438 * Called ONLY when configuration changes, thus the check
439 * for the mastership of the device.
440 * <p>
441 * Only the master of CP1 will deploy this pseudowire.
Pier Ventreac3e1d92016-11-17 22:26:29 -0800442 *
443 * @param pwToAdd the set of pseudo wires to add
444 */
Pier Ventref3cf5b92016-11-09 14:17:26 -0800445 private void deploy(Set<DefaultL2TunnelDescription> pwToAdd) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700446
Pier Ventref3cf5b92016-11-09 14:17:26 -0800447 Result result;
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700448
Pier Ventref3cf5b92016-11-09 14:17:26 -0800449 for (DefaultL2TunnelDescription currentL2Tunnel : pwToAdd) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800450 ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
451 ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
452 long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700453
454 // only the master of CP1 will program this pseudowire
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800455 if (!srManager.isMasterOf(cp1)) {
456 log.debug("Not the master of {}. Ignore pseudo wire deployment id={}", cp1, tunnelId);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800457 continue;
458 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700459
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800460 try {
461 // differentiate between leaf-leaf pseudowires and leaf-spine
462 // and pass the appropriate flag in them.
463 if (!srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
464 !srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
465 log.warn("Can not deploy pseudowire from spine to spine!");
466 result = Result.INTERNAL_ERROR;
467 } else if (srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
468 srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
469 log.info("Deploying a leaf-leaf pseudowire {}", tunnelId);
470 result = deployPseudowire(currentL2Tunnel, false);
471 } else {
472 log.info("Deploying a leaf-spine pseudowire {}", tunnelId);
473 result = deployPseudowire(currentL2Tunnel, true);
474 }
475 } catch (DeviceConfigNotFoundException e) {
476 log.error("Exception caught when deploying pseudowire", e.toString());
477 result = Result.INTERNAL_ERROR;
478 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700479
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700480 switch (result) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800481 case INTERNAL_ERROR:
482 log.warn("Could not deploy pseudowire {}, internal error!", tunnelId);
483 break;
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700484 case WRONG_PARAMETERS:
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800485 log.warn("Could not deploy pseudowire {}, wrong parameters!", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700486 break;
487 case ADDITION_ERROR:
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800488 log.warn("Could not deploy pseudowire {}, error in populating rules!", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700489 break;
490 default:
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800491 log.info("Pseudowire with {} succesfully deployed!", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700492 break;
Pier Ventref3cf5b92016-11-09 14:17:26 -0800493 }
Pier Ventref3cf5b92016-11-09 14:17:26 -0800494 }
Pier Ventre6b19e482016-11-07 16:21:04 -0800495 }
496
497 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -0800498 * Processes PWaaS Config updated event.
Pier Ventre6b19e482016-11-07 16:21:04 -0800499 *
500 * @param event network config updated event
501 */
502 public void processPwaasConfigUpdated(NetworkConfigEvent event) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800503 checkArgument(event.config().isPresent(),
504 "Config is not presented in PwaasConfigUpdated event {}", event);
505 checkArgument(event.prevConfig().isPresent(),
506 "PrevConfig is not presented in PwaasConfigUpdated event {}", event);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700507
508 log.info("Pseudowire configuration updated.");
509
Pier Ventref3cf5b92016-11-09 14:17:26 -0800510 // We retrieve the old pseudo wires.
511 PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get();
512 Set<Long> prevPws = prevConfig.getPwIds();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700513
Pier Ventref3cf5b92016-11-09 14:17:26 -0800514 // We retrieve the new pseudo wires.
Pier Ventre6b19e482016-11-07 16:21:04 -0800515 PwaasConfig config = (PwaasConfig) event.config().get();
Pier Ventref3cf5b92016-11-09 14:17:26 -0800516 Set<Long> newPws = config.getPwIds();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700517
Pier Ventref3cf5b92016-11-09 14:17:26 -0800518 // We compute the pseudo wires to update.
519 Set<Long> updPws = newPws.stream()
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700520 .filter(tunnelId -> prevPws.contains(tunnelId)
521 && !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
Pier Ventref3cf5b92016-11-09 14:17:26 -0800522 .collect(Collectors.toSet());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700523
Pier Ventref3cf5b92016-11-09 14:17:26 -0800524 // The pseudo wires to remove.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700525 Set<Long> rmvPWs = prevPws.stream()
526 .filter(tunnelId -> !newPws.contains(tunnelId)).collect(Collectors.toSet());
527
528 Set<DefaultL2TunnelDescription> pwToRemove = rmvPWs.stream()
Pier Ventref3cf5b92016-11-09 14:17:26 -0800529 .map(prevConfig::getPwDescription)
530 .collect(Collectors.toSet());
531 tearDown(pwToRemove);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700532
Pier Ventref3cf5b92016-11-09 14:17:26 -0800533 // The pseudo wires to add.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700534 Set<Long> addedPWs = newPws.stream()
Pier Ventref3cf5b92016-11-09 14:17:26 -0800535 .filter(tunnelId -> !prevPws.contains(tunnelId))
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700536 .collect(Collectors.toSet());
537 Set<DefaultL2TunnelDescription> pwToAdd = addedPWs.stream()
Pier Ventref3cf5b92016-11-09 14:17:26 -0800538 .map(config::getPwDescription)
539 .collect(Collectors.toSet());
540 deploy(pwToAdd);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700541
542
Pier Ventref3cf5b92016-11-09 14:17:26 -0800543 // The pseudo wires to update.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700544 updPws.forEach(tunnelId -> updatePw(prevConfig.getPwDescription(tunnelId),
545 config.getPwDescription(tunnelId)));
546
547 log.info("Pseudowires removed : {}, Pseudowires updated : {}, Pseudowires added : {}", rmvPWs,
548 updPws, addedPWs);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800549 }
550
551 /**
552 * Helper function to update a pw.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700553 * <p>
554 * Called upon configuration changes that update existing pseudowires and
555 * when links fail. Checking of mastership for CP1 is mandatory because it is
556 * called in multiple instances for both cases.
557 * <p>
558 * Meant to call asynchronously for various events, thus this call can not block and need
559 * to perform asynchronous operations.
560 * <p>
561 * For this reason error checking is omitted.
Pier Ventref3cf5b92016-11-09 14:17:26 -0800562 *
563 * @param oldPw the pseudo wire to remove
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700564 * @param newPw the pseudo wire to add
Pier Ventref3cf5b92016-11-09 14:17:26 -0800565 */
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800566 private void updatePw(DefaultL2TunnelDescription oldPw,
567 DefaultL2TunnelDescription newPw) {
568 ConnectPoint oldCp1 = oldPw.l2TunnelPolicy().cP1();
Pier Ventref3cf5b92016-11-09 14:17:26 -0800569 long tunnelId = oldPw.l2Tunnel().tunnelId();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700570
571 // only the master of CP1 will update this pseudowire
572 if (!srManager.isMasterOf(oldPw.l2TunnelPolicy().cP1())) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800573 log.debug("Not the master of {}. Ignore pseudo wire update id={}", oldCp1, tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700574 return;
575 }
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800576 // only determine if the new pseudowire is leaf-spine, because
577 // removal process is the same for both leaf-leaf and leaf-spine
578 // pws.
579 boolean newPwSpine;
580 try {
581 newPwSpine = !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP1().deviceId()) ||
582 !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP2().deviceId());
583 } catch (DeviceConfigNotFoundException e) {
584 // if exception is caught treat the newpw as leaf-leaf
585 newPwSpine = false;
586 }
587
588 // copy the variable here because we need to
589 // use it in lambda thus it needs to be final
590 boolean finalNewPwSpine = newPwSpine;
591
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700592
593 log.info("Updating pseudowire {}", oldPw.l2Tunnel().tunnelId());
594
Pier Ventref3cf5b92016-11-09 14:17:26 -0800595 // The async tasks to orchestrate the next and
596 // forwarding update.
Pier Ventref3cf5b92016-11-09 14:17:26 -0800597 CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
598 CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
Pier Ventreac3e1d92016-11-17 22:26:29 -0800599 CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
600 CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
601 CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>();
602 CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>();
Pier Ventref3cf5b92016-11-09 14:17:26 -0800603
Pier Ventref3cf5b92016-11-09 14:17:26 -0800604 // First we remove both policy.
Pier Ventref3cf5b92016-11-09 14:17:26 -0800605 log.debug("Start deleting fwd policy for {}", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700606
607 // first delete all information from our stores
608 // we can not do it asynchronously
609 l2PolicyStore.remove(Long.toString(tunnelId));
610 l2TunnelStore.remove(Long.toString(tunnelId));
611
612 VlanId egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP1OuterTag(),
613 oldPw.l2TunnelPolicy().cP1InnerTag(),
614 oldPw.l2TunnelPolicy().cP2OuterTag(),
615 oldPw.l2TunnelPolicy().cP2InnerTag());
616 deletePolicy(tunnelId,
617 oldPw.l2TunnelPolicy().cP1(),
618 oldPw.l2TunnelPolicy().cP1InnerTag(),
619 oldPw.l2TunnelPolicy().cP1OuterTag(),
620 egressVlan,
621 fwdInitNextFuture,
622 FWD);
623
624 log.debug("Update process : Start deleting rev policy for {}", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700625 egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP2OuterTag(),
626 oldPw.l2TunnelPolicy().cP2InnerTag(),
627 oldPw.l2TunnelPolicy().cP1OuterTag(),
628 oldPw.l2TunnelPolicy().cP1InnerTag());
629 deletePolicy(tunnelId,
630 oldPw.l2TunnelPolicy().cP2(),
631 oldPw.l2TunnelPolicy().cP2InnerTag(),
632 oldPw.l2TunnelPolicy().cP2OuterTag(),
633 egressVlan, revInitNextFuture,
634 REV);
635
Pier Ventref3cf5b92016-11-09 14:17:26 -0800636 // Finally we remove both the tunnels.
637 fwdInitNextFuture.thenAcceptAsync(status -> {
638 if (status == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700639 log.debug("Update process : Fwd policy removed. " +
640 "Now remove fwd {} for {}", INITIATION, tunnelId);
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800641 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP1(), fwdTermNextFuture, FWD);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800642 }
643 });
644 revInitNextFuture.thenAcceptAsync(status -> {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700645 if (status == null) {
646 log.debug("Update process : Rev policy removed. " +
647 "Now remove rev {} for {}", INITIATION, tunnelId);
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800648 tearDownPseudoWireInit(tunnelId, oldPw.l2TunnelPolicy().cP2(), revTermNextFuture, REV);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700649 }
Pier Ventref3cf5b92016-11-09 14:17:26 -0800650 });
Pier Ventreac3e1d92016-11-17 22:26:29 -0800651 fwdTermNextFuture.thenAcceptAsync(status -> {
652 if (status == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700653 log.debug("Update process : Fwd {} removed. " +
654 "Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId);
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800655 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP2(), fwdPwFuture, FWD);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800656 }
657 });
658 revTermNextFuture.thenAcceptAsync(status -> {
659 if (status == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700660 log.debug("Update process : Rev {} removed. " +
661 "Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId);
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800662 tearDownPseudoWireTerm(oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP1(), revPwFuture, REV);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800663 }
664 });
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700665
666 // get path here, need to use the same for fwd and rev direction
667 List<Link> path = getPath(newPw.l2TunnelPolicy().cP1(),
668 newPw.l2TunnelPolicy().cP2());
669 if (path == null) {
670 log.info("Deploying process : " +
671 "No path between the connection points for pseudowire {}", newPw.l2Tunnel().tunnelId());
672 return;
673 }
674
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800675 Link fwdNextHop, revNextHop;
676 if (!finalNewPwSpine) {
677 if (path.size() != 2) {
678 log.info("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
679 newPw.l2Tunnel().tunnelId());
680 return;
681 }
682
683 fwdNextHop = path.get(0);
684 revNextHop = reverseLink(path.get(1));
685 } else {
686 if (path.size() != 1) {
Charles Chane5a32552018-01-06 17:57:27 -0800687 log.info("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800688 newPw.l2Tunnel().tunnelId());
689 return;
690 }
691
692 fwdNextHop = path.get(0);
693 revNextHop = reverseLink(path.get(0));
694 }
695
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700696 newPw.l2Tunnel().setPath(path);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700697
698 // At the end we install the updated PW.
Pier Ventreac3e1d92016-11-17 22:26:29 -0800699 fwdPwFuture.thenAcceptAsync(status -> {
Pier Ventref3cf5b92016-11-09 14:17:26 -0800700 if (status == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700701
702 // Upgrade stores and book keeping information, need to move this here
703 // cause this call is asynchronous.
704 l2PolicyStore.put(Long.toString(tunnelId), newPw.l2TunnelPolicy());
705 l2TunnelStore.put(Long.toString(tunnelId), newPw.l2Tunnel());
706
707 log.debug("Update process : Deploying new fwd pw for {}", tunnelId);
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800708 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(),
709 newPw.l2TunnelPolicy().cP2(), FWD,
710 fwdNextHop, finalNewPwSpine);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800711 if (lamdaResult != SUCCESS) {
712 return;
713 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700714
715 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP1OuterTag(),
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800716 newPw.l2TunnelPolicy().cP1InnerTag(),
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700717 newPw.l2TunnelPolicy().cP2OuterTag(),
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800718 newPw.l2TunnelPolicy().cP2InnerTag());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700719
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800720 lamdaResult = deployPolicy(tunnelId, newPw.l2TunnelPolicy().cP1(),
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700721 newPw.l2TunnelPolicy().cP1InnerTag(),
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800722 newPw.l2TunnelPolicy().cP1OuterTag(),
723 egressVlanId, lamdaResult.nextId);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800724 if (lamdaResult != SUCCESS) {
725 return;
726 }
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800727 deployPseudoWireTerm(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(),
Andreas Pantelopoulos5a243d62018-01-17 10:03:00 -0800728 VlanId.NONE, FWD, finalNewPwSpine);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800729
730 }
731 });
732 revPwFuture.thenAcceptAsync(status -> {
733 if (status == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700734 log.debug("Update process : Deploying new rev pw for {}", tunnelId);
735 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
736 newPw.l2TunnelPolicy().cP2(),
737 newPw.l2TunnelPolicy().cP1(),
738 REV,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800739 revNextHop, finalNewPwSpine);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800740 if (lamdaResult != SUCCESS) {
741 return;
742 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700743
744 VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP2OuterTag(),
745 newPw.l2TunnelPolicy().cP2InnerTag(),
746 newPw.l2TunnelPolicy().cP1OuterTag(),
747 newPw.l2TunnelPolicy().cP1InnerTag());
748 lamdaResult = deployPolicy(tunnelId,
749 newPw.l2TunnelPolicy().cP2(),
750 newPw.l2TunnelPolicy().cP2InnerTag(),
751 newPw.l2TunnelPolicy().cP2OuterTag(),
752 egressVlanId,
753 lamdaResult.nextId);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800754 if (lamdaResult != SUCCESS) {
755 return;
756 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700757 deployPseudoWireTerm(newPw.l2Tunnel(),
758 newPw.l2TunnelPolicy().cP1(),
Andreas Pantelopoulos5a243d62018-01-17 10:03:00 -0800759 VlanId.NONE,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800760 REV, finalNewPwSpine);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800761 }
Pier Ventre6b19e482016-11-07 16:21:04 -0800762 });
763 }
764
765 /**
766 * Processes Pwaas Config removed event.
767 *
768 * @param event network config removed event
769 */
770 public void processPwaasConfigRemoved(NetworkConfigEvent event) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800771 checkArgument(event.prevConfig().isPresent(),
772 "PrevConfig is not presented in PwaasConfigRemoved event {}", event);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700773
774 log.info("Network event : Pseudowire configuration removed!");
Pier Ventref3cf5b92016-11-09 14:17:26 -0800775 PwaasConfig config = (PwaasConfig) event.prevConfig().get();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700776
777 Set<DefaultL2TunnelDescription> pwToRemove = config
778 .getPwIds()
Pier Ventref3cf5b92016-11-09 14:17:26 -0800779 .stream()
780 .map(config::getPwDescription)
781 .collect(Collectors.toSet());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700782
Pier Ventref3cf5b92016-11-09 14:17:26 -0800783 // We teardown all the pseudo wire deployed
784 tearDown(pwToRemove);
Pier Ventre6b19e482016-11-07 16:21:04 -0800785 }
Pier Ventref3cf5b92016-11-09 14:17:26 -0800786
787 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700788 * Helper function for removing a single pseudowire.
789 * <p>
790 * No mastership of CP1 is checked, because it can be called from
791 * the CLI for removal of pseudowires.
Pier Ventref3cf5b92016-11-09 14:17:26 -0800792 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700793 * @param l2TunnelId the id of the pseudowire to tear down
794 * @return Returns SUCCESS if no error is obeserved or an appropriate
795 * error on a failure
Pier Ventref3cf5b92016-11-09 14:17:26 -0800796 */
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800797 private Result tearDownPseudowire(long l2TunnelId) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700798
799 CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
800 CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
801
802 CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
803 CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
804
805 if (l2TunnelId == 0) {
806 log.warn("Removal process : Tunnel id cannot be 0");
807 return Result.WRONG_PARAMETERS;
Pier Ventref3cf5b92016-11-09 14:17:26 -0800808 }
809
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700810 // check existence of tunnels/policy in the store, if one is missing abort!
811 Versioned<DefaultL2Tunnel> l2TunnelVersioned = l2TunnelStore.get(Long.toString(l2TunnelId));
812 Versioned<DefaultL2TunnelPolicy> l2TunnelPolicyVersioned = l2PolicyStore.get(Long.toString(l2TunnelId));
813 if ((l2TunnelVersioned == null) || (l2TunnelPolicyVersioned == null)) {
814 log.warn("Removal process : Policy and/or tunnel missing for tunnel id {}", l2TunnelId);
815 return Result.REMOVAL_ERROR;
816 }
817
818 DefaultL2TunnelDescription pwToRemove = new DefaultL2TunnelDescription(l2TunnelVersioned.value(),
819 l2TunnelPolicyVersioned.value());
820
821 // remove the tunnels and the policies from the store
822 l2PolicyStore.remove(Long.toString(l2TunnelId));
823 l2TunnelStore.remove(Long.toString(l2TunnelId));
824
825 log.info("Removal process : Tearing down forward direction of pseudowire {}", l2TunnelId);
826
827 VlanId egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP1OuterTag(),
828 pwToRemove.l2TunnelPolicy().cP1InnerTag(),
829 pwToRemove.l2TunnelPolicy().cP2OuterTag(),
830 pwToRemove.l2TunnelPolicy().cP2InnerTag());
831 deletePolicy(l2TunnelId,
832 pwToRemove.l2TunnelPolicy().cP1(),
833 pwToRemove.l2TunnelPolicy().cP1InnerTag(),
834 pwToRemove.l2TunnelPolicy().cP1OuterTag(),
835 egressVlan,
836 fwdInitNextFuture,
837 FWD);
838
839 fwdInitNextFuture.thenAcceptAsync(status -> {
840 if (status == null) {
841 // Finally we will tear down the pseudo wire.
842 tearDownPseudoWireInit(l2TunnelId,
843 pwToRemove.l2TunnelPolicy().cP1(),
844 fwdTermNextFuture,
845 FWD);
846 }
847 });
848
849 fwdTermNextFuture.thenAcceptAsync(status -> {
850 if (status == null) {
851 tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
852 pwToRemove.l2TunnelPolicy().cP2(),
853 null,
854 FWD);
855 }
856 });
857
858 log.info("Removal process : Tearing down reverse direction of pseudowire {}", l2TunnelId);
859
860 egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP2OuterTag(),
861 pwToRemove.l2TunnelPolicy().cP2InnerTag(),
862 pwToRemove.l2TunnelPolicy().cP1OuterTag(),
863 pwToRemove.l2TunnelPolicy().cP1InnerTag());
864
865 // We do the same operations on the reverse side.
866 deletePolicy(l2TunnelId,
867 pwToRemove.l2TunnelPolicy().cP2(),
868 pwToRemove.l2TunnelPolicy().cP2InnerTag(),
869 pwToRemove.l2TunnelPolicy().cP2OuterTag(),
870 egressVlan,
871 revInitNextFuture,
872 REV);
873
874 revInitNextFuture.thenAcceptAsync(status -> {
875 if (status == null) {
876 tearDownPseudoWireInit(l2TunnelId,
877 pwToRemove.l2TunnelPolicy().cP2(),
878 revTermNextFuture,
879 REV);
880 }
881 });
882
883 revTermNextFuture.thenAcceptAsync(status -> {
884 if (status == null) {
885 tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
886 pwToRemove.l2TunnelPolicy().cP1(),
887 null,
888 REV);
889 }
890 });
891
892 return Result.SUCCESS;
Pier Ventref3cf5b92016-11-09 14:17:26 -0800893 }
894
895 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700896 * Helper function to handle the pw removal.
897 * <p>
898 * This method checks for the mastership of the device because it is
899 * used only from network configuration updates, thus we only want
900 * one instance only to program each pseudowire.
Pier Ventref3cf5b92016-11-09 14:17:26 -0800901 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700902 * @param pwToRemove the pseudo wires to remove
Pier Ventref3cf5b92016-11-09 14:17:26 -0800903 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700904 public void tearDown(Set<DefaultL2TunnelDescription> pwToRemove) {
Pier Ventref3cf5b92016-11-09 14:17:26 -0800905
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700906 Result result;
907
908 // We remove all the pw in the configuration file.
909 for (DefaultL2TunnelDescription currentL2Tunnel : pwToRemove) {
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800910 ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
911 ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
912 long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700913
914 // only the master of CP1 will program this pseudowire
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800915 if (!srManager.isMasterOf(cp1)) {
916 log.debug("Not the master of {}. Ignore pseudo wire removal id={}", cp1, tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700917 continue;
918 }
919
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800920 // no need to differentiate here between leaf-leaf and leaf-spine, because
921 // the only change is in the groups, which we do not remove either way
922 log.info("Removing pseudowire {}", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700923
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800924 result = tearDownPseudowire(tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700925 switch (result) {
926 case WRONG_PARAMETERS:
927 log.warn("Error in supplied parameters for the pseudowire removal with tunnel id {}!",
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800928 tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700929 break;
930 case REMOVAL_ERROR:
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800931 log.warn("Error in pseudowire removal with tunnel id {}!", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700932 break;
933 default:
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -0800934 log.warn("Pseudowire with tunnel id {} was removed successfully", tunnelId);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700935 }
936 }
Pier Ventref3cf5b92016-11-09 14:17:26 -0800937 }
938
939 /**
Pier Ventref3cf5b92016-11-09 14:17:26 -0800940 * Handles the policy establishment which consists in
941 * create the filtering and forwarding objectives related
942 * to the initiation and termination.
943 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700944 * @param tunnelId the tunnel id
945 * @param ingress the ingress point
Pier Ventref3cf5b92016-11-09 14:17:26 -0800946 * @param ingressInner the ingress inner tag
947 * @param ingressOuter the ingress outer tag
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700948 * @param nextId the next objective id
949 * @param egressVlan Vlan-id to set, depends on ingress vlan
950 * combinations. For example, if pw is double tagged
951 * then this is the value of the outer vlan, if single
952 * tagged then it is the new value of the single tag.
953 * Should be None for untagged traffic.
Pier Ventreac3e1d92016-11-17 22:26:29 -0800954 * @return the result of the operation
Pier Ventref3cf5b92016-11-09 14:17:26 -0800955 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700956 private Result deployPolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner,
957 VlanId ingressOuter, VlanId egressVlan, int nextId) {
958
Pier Ventreac3e1d92016-11-17 22:26:29 -0800959 List<Objective> objectives = Lists.newArrayList();
Pier Ventref3cf5b92016-11-09 14:17:26 -0800960 // We create the forwarding objective for supporting
961 // the l2 tunnel.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700962 ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800963 // We create and add objective context.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700964 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
965 log.debug("FwdObj for tunnel {} populated", tunnelId),
966 (objective, error) ->
967 log.warn("Failed to populate fwdrObj " +
968 "for tunnel {}", tunnelId, error));
Pier Ventref3cf5b92016-11-09 14:17:26 -0800969 objectives.add(fwdBuilder.add(context));
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700970
Pier Ventref3cf5b92016-11-09 14:17:26 -0800971 // We create the filtering objective to define the
972 // permit traffic in the switch
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700973 FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
974
Pier Ventref3cf5b92016-11-09 14:17:26 -0800975 // We add the metadata.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700976 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
977 .builder()
978 .setTunnelId(tunnelId)
979 .setVlanId(egressVlan);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800980 filtBuilder.withMeta(treatment.build());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700981
Pier Ventref3cf5b92016-11-09 14:17:26 -0800982 // We create and add objective context.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -0700983 context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for tunnel {} populated", tunnelId),
984 (objective, error) -> log.warn("Failed to populate filterObj for " +
985 "tunnel {}", tunnelId, error));
Pier Ventref3cf5b92016-11-09 14:17:26 -0800986 objectives.add(filtBuilder.add(context));
987
988 for (Objective objective : objectives) {
989 if (objective instanceof ForwardingObjective) {
990 srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
Pier Ventreac3e1d92016-11-17 22:26:29 -0800991 log.debug("Creating new FwdObj for initiation NextObj with id={} for tunnel {}", nextId, tunnelId);
Pier Ventref3cf5b92016-11-09 14:17:26 -0800992 } else {
993 srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
994 log.debug("Creating new FiltObj for tunnel {}", tunnelId);
995 }
996 }
997 return SUCCESS;
998 }
999
1000 /**
Pier Ventref3cf5b92016-11-09 14:17:26 -08001001 * Handles the tunnel establishment which consists in
Pier Ventreac3e1d92016-11-17 22:26:29 -08001002 * create the next objectives related to the initiation.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001003 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001004 * @param l2Tunnel the tunnel to deploy
1005 * @param ingress the ingress connect point
1006 * @param egress the egress connect point
Pier Ventref3cf5b92016-11-09 14:17:26 -08001007 * @param direction the direction of the pw
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001008 * @param spinePw if the pseudowire involves a spine switch
Pier Ventreac3e1d92016-11-17 22:26:29 -08001009 * @return the result of the operation
Pier Ventref3cf5b92016-11-09 14:17:26 -08001010 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001011 private Result deployPseudoWireInit(DefaultL2Tunnel l2Tunnel, ConnectPoint ingress,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001012 ConnectPoint egress, Direction direction, Link nextHop, boolean spinePw) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001013
Pier Ventref3cf5b92016-11-09 14:17:26 -08001014 if (nextHop == null) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001015 log.warn("No path between ingress and egress cps for tunnel {}", l2Tunnel.tunnelId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001016 return WRONG_PARAMETERS;
1017 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001018
Pier Ventref3cf5b92016-11-09 14:17:26 -08001019 // We create the next objective without the metadata
1020 // context and id. We check if it already exists in the
Pier Ventreac3e1d92016-11-17 22:26:29 -08001021 // store. If not we store as it is in the store.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001022 NextObjective.Builder nextObjectiveBuilder = createNextObjective(INITIATION,
1023 nextHop.src(),
1024 nextHop.dst(),
1025 l2Tunnel,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001026 egress.deviceId(),
1027 spinePw);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001028
Pier Ventref3cf5b92016-11-09 14:17:26 -08001029 if (nextObjectiveBuilder == null) {
1030 return INTERNAL_ERROR;
1031 }
1032 // We set the metadata. We will use this metadata
1033 // to inform the driver we are doing a l2 tunnel.
1034 TrafficSelector metadata = DefaultTrafficSelector
1035 .builder()
1036 .matchTunnelId(l2Tunnel.tunnelId())
1037 .build();
1038 nextObjectiveBuilder.withMeta(metadata);
Pier Ventreac3e1d92016-11-17 22:26:29 -08001039 int nextId = srManager.flowObjectiveService.allocateNextId();
Pier Ventref3cf5b92016-11-09 14:17:26 -08001040 if (nextId < 0) {
1041 log.warn("Not able to allocate a next id for initiation");
1042 return INTERNAL_ERROR;
1043 }
1044 nextObjectiveBuilder.withId(nextId);
1045 String key = generateKey(l2Tunnel.tunnelId(), direction);
1046 l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001047 ObjectiveContext context = new DefaultObjectiveContext((objective) ->
1048 log.debug("Initiation l2 tunnel rule " +
1049 "for {} populated",
1050 l2Tunnel.tunnelId()),
1051 (objective, error) ->
1052 log.warn("Failed to populate Initiation " +
1053 "l2 tunnel rule for {}: {}",
1054 l2Tunnel.tunnelId(), error));
Pier Ventreac3e1d92016-11-17 22:26:29 -08001055 NextObjective nextObjective = nextObjectiveBuilder.add(context);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001056 srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective);
1057 log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}",
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001058 l2Tunnel.tunnelId(), nextObjective.id());
Pier Ventreac3e1d92016-11-17 22:26:29 -08001059 Result result = SUCCESS;
Pier Ventref3cf5b92016-11-09 14:17:26 -08001060 result.nextId = nextObjective.id();
1061 return result;
1062 }
1063
1064 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001065 * Handles the tunnel termination, which consists in the creation
1066 * of a forwarding objective and a next objective.
1067 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001068 * @param l2Tunnel the tunnel to terminate
1069 * @param egress the egress point
Pier Ventreac3e1d92016-11-17 22:26:29 -08001070 * @param egressVlan the expected vlan at egress
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001071 * @param direction the direction
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001072 * @param spinePw if the pseudowire involves a spine switch
Pier Ventreac3e1d92016-11-17 22:26:29 -08001073 * @return the result of the operation
1074 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001075 private Result deployPseudoWireTerm(DefaultL2Tunnel l2Tunnel, ConnectPoint egress,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001076 VlanId egressVlan, Direction direction, boolean spinePw) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001077
Pier Ventreac3e1d92016-11-17 22:26:29 -08001078 // We create the group relative to the termination.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001079 NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001080 null, egress.deviceId(),
1081 spinePw);
Pier Ventreac3e1d92016-11-17 22:26:29 -08001082 if (nextObjectiveBuilder == null) {
1083 return INTERNAL_ERROR;
1084 }
1085 TrafficSelector metadata = DefaultTrafficSelector
1086 .builder()
1087 .matchVlanId(egressVlan)
1088 .build();
1089 nextObjectiveBuilder.withMeta(metadata);
1090 int nextId = srManager.flowObjectiveService.allocateNextId();
1091 if (nextId < 0) {
1092 log.warn("Not able to allocate a next id for initiation");
1093 return INTERNAL_ERROR;
1094 }
1095 nextObjectiveBuilder.withId(nextId);
1096 String key = generateKey(l2Tunnel.tunnelId(), direction);
1097 l2TerminationNextObjStore.put(key, nextObjectiveBuilder.add());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001098 ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("Termination l2 tunnel rule " +
1099 "for {} populated",
1100 l2Tunnel.tunnelId()),
1101 (objective, error) -> log.warn("Failed to populate " +
1102 "termination l2 tunnel " +
1103 "rule for {}: {}",
1104 l2Tunnel.tunnelId(),
1105 error));
Pier Ventreac3e1d92016-11-17 22:26:29 -08001106 NextObjective nextObjective = nextObjectiveBuilder.add(context);
1107 srManager.flowObjectiveService.next(egress.deviceId(), nextObjective);
1108 log.debug("Termination next objective for {} not found. Creating new NextObj with id={}",
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001109 l2Tunnel.tunnelId(), nextObjective.id());
1110
Pier Ventreac3e1d92016-11-17 22:26:29 -08001111 // We create the flow relative to the termination.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001112 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(), l2Tunnel.tunnelId(),
1113 egress.port(), nextObjective.id());
1114 context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for tunnel termination {} populated",
1115 l2Tunnel.tunnelId()),
1116 (objective, error) -> log.warn("Failed to populate fwdrObj" +
1117 " for tunnel termination {}",
1118 l2Tunnel.tunnelId(), error));
Pier Ventreac3e1d92016-11-17 22:26:29 -08001119 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context));
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001120 log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}",
1121 nextId, l2Tunnel.tunnelId());
Pier Ventreac3e1d92016-11-17 22:26:29 -08001122 return SUCCESS;
1123
1124 }
1125
1126 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001127 * Creates the filtering objective according to a given policy.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001128 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001129 * @param inPort the in port
Pier Ventref3cf5b92016-11-09 14:17:26 -08001130 * @param innerTag the inner vlan tag
1131 * @param outerTag the outer vlan tag
1132 * @return the filtering objective
1133 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001134 private FilteringObjective.Builder createFiltObjective(PortNumber inPort, VlanId innerTag, VlanId outerTag) {
1135
1136 log.info("Creating filtering objective for vlans {} / {}", outerTag, innerTag);
1137 return DefaultFilteringObjective
1138 .builder()
Pier Ventref3cf5b92016-11-09 14:17:26 -08001139 .withKey(Criteria.matchInPort(inPort))
1140 .addCondition(Criteria.matchInnerVlanId(innerTag))
1141 .addCondition(Criteria.matchVlanId(outerTag))
1142 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1143 .permit()
Ray Milkeyb85de082017-04-05 09:42:04 -07001144 .fromApp(srManager.appId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001145 }
1146
1147 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001148 * Creates the forwarding objective for the termination.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001149 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001150 * @param pwLabel the pseudo wire label
1151 * @param tunnelId the tunnel id
Pier Ventreac3e1d92016-11-17 22:26:29 -08001152 * @param egressPort the egress port
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001153 * @param nextId the next step
Pier Ventreac3e1d92016-11-17 22:26:29 -08001154 * @return the forwarding objective to support the termination
Pier Ventref3cf5b92016-11-09 14:17:26 -08001155 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001156 private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel, long tunnelId,
1157 PortNumber egressPort, int nextId) {
1158
1159 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1160 TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment.builder();
Pier Ventreac3e1d92016-11-17 22:26:29 -08001161 // The flow has to match on the pw label and bos
1162 trafficSelector.matchEthType(Ethernet.MPLS_UNICAST);
1163 trafficSelector.matchMplsLabel(pwLabel);
1164 trafficSelector.matchMplsBos(true);
1165 // The flow has to decrement ttl, restore ttl in
1166 // pop mpls, set tunnel id and port.
1167 trafficTreatment.decMplsTtl();
1168 trafficTreatment.copyTtlIn();
1169 trafficTreatment.popMpls();
1170 trafficTreatment.setTunnelId(tunnelId);
1171 trafficTreatment.setOutput(egressPort);
1172
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001173 return DefaultForwardingObjective
1174 .builder()
Ray Milkeyb85de082017-04-05 09:42:04 -07001175 .fromApp(srManager.appId())
Pier Ventreac3e1d92016-11-17 22:26:29 -08001176 .makePermanent()
1177 .nextStep(nextId)
1178 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1179 .withSelector(trafficSelector.build())
1180 .withTreatment(trafficTreatment.build())
1181 .withFlag(VERSATILE);
1182 }
1183
1184 /**
1185 * Creates the forwarding objective for the initiation.
1186 *
1187 * @param tunnelId the tunnel id
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001188 * @param inPort the input port
1189 * @param nextId the next step
Pier Ventreac3e1d92016-11-17 22:26:29 -08001190 * @return the forwarding objective to support the initiation.
1191 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001192 private ForwardingObjective.Builder createInitFwdObjective(long tunnelId, PortNumber inPort, int nextId) {
1193
1194 TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
1195
Pier Ventreac3e1d92016-11-17 22:26:29 -08001196 // The flow has to match on the mpls logical
1197 // port and the tunnel id.
1198 trafficSelector.matchTunnelId(tunnelId);
1199 trafficSelector.matchInPort(inPort);
1200
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001201 return DefaultForwardingObjective
1202 .builder()
Ray Milkeyb85de082017-04-05 09:42:04 -07001203 .fromApp(srManager.appId())
Pier Ventreac3e1d92016-11-17 22:26:29 -08001204 .makePermanent()
1205 .nextStep(nextId)
1206 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
1207 .withSelector(trafficSelector.build())
1208 .withFlag(VERSATILE);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001209
1210 }
1211
1212 /**
1213 * Creates the next objective according to a given
1214 * pipeline. We don't set the next id and we don't
1215 * create the final meta to check if we are re-using
1216 * the same next objective for different tunnels.
1217 *
1218 * @param pipeline the pipeline to support
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001219 * @param srcCp the source port
1220 * @param dstCp the destination port
Pier Ventref3cf5b92016-11-09 14:17:26 -08001221 * @param l2Tunnel the tunnel to support
1222 * @param egressId the egress device id
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001223 * @param spinePw if the pw involves a spine switch
Pier Ventref3cf5b92016-11-09 14:17:26 -08001224 * @return the next objective to support the pipeline
1225 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001226 private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp,
1227 ConnectPoint dstCp, DefaultL2Tunnel l2Tunnel,
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001228 DeviceId egressId, boolean spinePw) {
Pier Ventref3cf5b92016-11-09 14:17:26 -08001229 NextObjective.Builder nextObjBuilder;
1230 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
1231 if (pipeline == INITIATION) {
1232 nextObjBuilder = DefaultNextObjective
1233 .builder()
1234 .withType(NextObjective.Type.SIMPLE)
Ray Milkeyb85de082017-04-05 09:42:04 -07001235 .fromApp(srManager.appId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001236 // The pw label is the bottom of stack. It has to
1237 // be different -1.
1238 if (l2Tunnel.pwLabel().toInt() == MplsLabel.MAX_MPLS) {
1239 log.warn("Pw label not configured");
1240 return null;
1241 }
1242 treatmentBuilder.pushMpls();
1243 treatmentBuilder.setMpls(l2Tunnel.pwLabel());
1244 treatmentBuilder.setMplsBos(true);
1245 treatmentBuilder.copyTtlOut();
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001246
Pier Ventref3cf5b92016-11-09 14:17:26 -08001247 // If the inter-co label is present we have to set the label.
1248 if (l2Tunnel.interCoLabel().toInt() != MplsLabel.MAX_MPLS) {
1249 treatmentBuilder.pushMpls();
1250 treatmentBuilder.setMpls(l2Tunnel.interCoLabel());
1251 treatmentBuilder.setMplsBos(false);
1252 treatmentBuilder.copyTtlOut();
1253 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001254
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001255 // if pw is leaf-to-leaf we need to
1256 // add the routing label also
1257 if (!spinePw) {
1258 // We retrieve the sr label from the config
1259 // specific for pseudowire traffic
1260 // using the egress leaf device id.
1261 MplsLabel srLabel;
1262 try {
1263 srLabel = MplsLabel.mplsLabel(srManager.deviceConfiguration().getPWRoutingLabel(egressId));
1264
1265 } catch (DeviceConfigNotFoundException e) {
1266 log.warn("Sr label for pw traffic not configured");
1267 return null;
1268 }
1269
1270 treatmentBuilder.pushMpls();
1271 treatmentBuilder.setMpls(srLabel);
1272 treatmentBuilder.setMplsBos(false);
1273 treatmentBuilder.copyTtlOut();
Pier Ventref3cf5b92016-11-09 14:17:26 -08001274 }
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001275
Pier Ventref3cf5b92016-11-09 14:17:26 -08001276 // We have to rewrite the src and dst mac address.
1277 MacAddress ingressMac;
1278 try {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001279 ingressMac = srManager.deviceConfiguration().getDeviceMac(srcCp.deviceId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001280 } catch (DeviceConfigNotFoundException e) {
1281 log.warn("Was not able to find the ingress mac");
1282 return null;
1283 }
1284 treatmentBuilder.setEthSrc(ingressMac);
1285 MacAddress neighborMac;
1286 try {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001287 neighborMac = srManager.deviceConfiguration().getDeviceMac(dstCp.deviceId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001288 } catch (DeviceConfigNotFoundException e) {
1289 log.warn("Was not able to find the neighbor mac");
1290 return null;
1291 }
1292 treatmentBuilder.setEthDst(neighborMac);
1293 } else {
Pier Ventreac3e1d92016-11-17 22:26:29 -08001294 // We create the next objective which
1295 // will be a simple l2 group.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001296 nextObjBuilder = DefaultNextObjective
1297 .builder()
1298 .withType(NextObjective.Type.SIMPLE)
Ray Milkeyb85de082017-04-05 09:42:04 -07001299 .fromApp(srManager.appId());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001300 }
Pier Ventreac3e1d92016-11-17 22:26:29 -08001301 treatmentBuilder.setOutput(srcCp.port());
Pier Ventref3cf5b92016-11-09 14:17:26 -08001302 nextObjBuilder.addTreatment(treatmentBuilder.build());
1303 return nextObjBuilder;
1304 }
1305
1306 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001307 * Reverses a link.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001308 *
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001309 * @param link link to be reversed
1310 * @return the reversed link
Pier Ventref3cf5b92016-11-09 14:17:26 -08001311 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001312 private Link reverseLink(Link link) {
1313
1314 DefaultLink.Builder linkBuilder = DefaultLink.builder();
1315
1316 linkBuilder.src(link.dst());
1317 linkBuilder.dst(link.src());
1318 linkBuilder.type(link.type());
1319 linkBuilder.providerId(link.providerId());
1320
1321 return linkBuilder.build();
1322 }
1323
1324 /**
1325 * Returns the path betwwen two connect points.
1326 *
Andreas Pantelopoulosafc637f2017-12-20 18:04:27 -08001327 * @param srcCp source connect point
1328 * @param dstCp destination connect point
1329 * @return the path
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001330 */
1331 private List<Link> getPath(ConnectPoint srcCp, ConnectPoint dstCp) {
Charles Chan83163812018-01-09 13:45:07 -08001332 /* TODO We retrieve a set of paths in case of a link failure, what happens
1333 * if the TopologyService gets the link notification AFTER us and has not updated the paths?
1334 *
1335 * TODO This has the potential to act on old topology.
1336 * Maybe we should make SRManager be a listener on topology events instead raw link events.
1337 */
1338 Set<Path> paths = srManager.topologyService.getPaths(
1339 srManager.topologyService.currentTopology(),
1340 srcCp.deviceId(), dstCp.deviceId());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001341
Charles Chan83163812018-01-09 13:45:07 -08001342 log.debug("Paths obtained from topology service {}", paths);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001343
1344 // We randomly pick a path.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001345 if (paths.isEmpty()) {
1346 return null;
1347 }
1348 int size = paths.size();
1349 int index = RandomUtils.nextInt(0, size);
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001350
Charles Chan83163812018-01-09 13:45:07 -08001351 List<Link> result = Iterables.get(paths, index).links();
1352 log.debug("Randomly picked a path {}", result);
1353
1354 return result;
Pier Ventref3cf5b92016-11-09 14:17:26 -08001355 }
1356
1357 /**
Pier Ventref3cf5b92016-11-09 14:17:26 -08001358 * Deletes a given policy using the parameter supplied.
1359 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001360 * @param tunnelId the tunnel id
1361 * @param ingress the ingress point
Pier Ventref3cf5b92016-11-09 14:17:26 -08001362 * @param ingressInner the ingress inner vlan id
1363 * @param ingressOuter the ingress outer vlan id
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001364 * @param future to perform the async operation
1365 * @param direction the direction: forward or reverse
Pier Ventref3cf5b92016-11-09 14:17:26 -08001366 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001367 private void deletePolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter,
1368 VlanId egressVlan, CompletableFuture<ObjectiveError> future, Direction direction) {
1369
Pier Ventreac3e1d92016-11-17 22:26:29 -08001370 String key = generateKey(tunnelId, direction);
1371 if (!l2InitiationNextObjStore.containsKey(key)) {
1372 log.warn("Abort delete of policy for tunnel {}: next does not exist in the store", tunnelId);
1373 if (future != null) {
1374 future.complete(null);
1375 }
1376 return;
1377 }
1378 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
1379 int nextId = nextObjective.id();
1380 List<Objective> objectives = Lists.newArrayList();
Pier Ventref3cf5b92016-11-09 14:17:26 -08001381 // We create the forwarding objective.
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001382 ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001383 ObjectiveContext context = new ObjectiveContext() {
1384 @Override
1385 public void onSuccess(Objective objective) {
Pier Ventreac3e1d92016-11-17 22:26:29 -08001386 log.debug("Previous fwdObj for policy {} removed", tunnelId);
1387 if (future != null) {
1388 future.complete(null);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001389 }
1390 }
1391
1392 @Override
1393 public void onError(Objective objective, ObjectiveError error) {
Pier Ventreac3e1d92016-11-17 22:26:29 -08001394 log.warn("Failed to remove previous fwdObj for policy {}: {}", tunnelId, error);
1395 if (future != null) {
1396 future.complete(error);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001397 }
1398 }
1399 };
1400 objectives.add(fwdBuilder.remove(context));
1401 // We create the filtering objective to define the
1402 // permit traffic in the switch
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001403 FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
1404 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
1405 .builder()
1406 .setTunnelId(tunnelId)
1407 .setVlanId(egressVlan);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001408 filtBuilder.withMeta(treatment.build());
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001409 context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for policy {} revoked", tunnelId),
1410 (objective, error) ->
1411 log.warn("Failed to revoke filterObj for policy {}",
1412 tunnelId, error));
Pier Ventref3cf5b92016-11-09 14:17:26 -08001413 objectives.add(filtBuilder.remove(context));
1414
1415 for (Objective objective : objectives) {
1416 if (objective instanceof ForwardingObjective) {
1417 srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
1418 } else {
1419 srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
1420 }
1421 }
1422 }
1423
1424 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001425 * Deletes the pseudo wire initiation.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001426 *
Pier Ventreac3e1d92016-11-17 22:26:29 -08001427 * @param l2TunnelId the tunnel id
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001428 * @param ingress the ingress connect point
1429 * @param future to perform an async operation
1430 * @param direction the direction: reverse of forward
Pier Ventref3cf5b92016-11-09 14:17:26 -08001431 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001432 private void tearDownPseudoWireInit(long l2TunnelId, ConnectPoint ingress,
1433 CompletableFuture<ObjectiveError> future, Direction direction) {
1434
Pier Ventreac3e1d92016-11-17 22:26:29 -08001435 String key = generateKey(l2TunnelId, direction);
Pier Ventreac3e1d92016-11-17 22:26:29 -08001436 if (!l2InitiationNextObjStore.containsKey(key)) {
1437 log.info("Abort delete of {} for {}: next does not exist in the store", INITIATION, key);
1438 if (future != null) {
1439 future.complete(null);
1440 }
1441 return;
1442 }
1443 NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001444 // un-comment in case you want to delete groups used by the pw
1445 // however, this will break the update of pseudowires cause the L2 interface group can
1446 // not be deleted (it is referenced by other groups)
1447 /*
Pier Ventref3cf5b92016-11-09 14:17:26 -08001448 ObjectiveContext context = new ObjectiveContext() {
1449 @Override
1450 public void onSuccess(Objective objective) {
Pier Ventreac3e1d92016-11-17 22:26:29 -08001451 log.debug("Previous {} next for {} removed", INITIATION, key);
1452 if (future != null) {
1453 future.complete(null);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001454 }
1455 }
1456
1457 @Override
1458 public void onError(Objective objective, ObjectiveError error) {
Pier Ventreac3e1d92016-11-17 22:26:29 -08001459 log.warn("Failed to remove previous {} next for {}: {}", INITIATION, key, error);
1460 if (future != null) {
1461 future.complete(error);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001462 }
1463 }
1464 };
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001465 srManager.flowObjectiveService.next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1466 */
1467
1468 future.complete(null);
Pier Ventref3cf5b92016-11-09 14:17:26 -08001469 l2InitiationNextObjStore.remove(key);
1470 }
1471
1472 /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001473 * Deletes the pseudo wire termination.
1474 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001475 * @param l2Tunnel the tunnel
1476 * @param egress the egress connect point
1477 * @param future the async task
Pier Ventreac3e1d92016-11-17 22:26:29 -08001478 * @param direction the direction of the tunnel
1479 */
1480 private void tearDownPseudoWireTerm(DefaultL2Tunnel l2Tunnel,
1481 ConnectPoint egress,
1482 CompletableFuture<ObjectiveError> future,
1483 Direction direction) {
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001484
Pier Ventreac3e1d92016-11-17 22:26:29 -08001485 String key = generateKey(l2Tunnel.tunnelId(), direction);
Pier Ventreac3e1d92016-11-17 22:26:29 -08001486 if (!l2TerminationNextObjStore.containsKey(key)) {
1487 log.info("Abort delete of {} for {}: next does not exist in the store", TERMINATION, key);
1488 if (future != null) {
1489 future.complete(null);
1490 }
1491 return;
1492 }
1493 NextObjective nextObjective = l2TerminationNextObjStore.get(key).value();
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001494 ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(),
1495 l2Tunnel.tunnelId(),
1496 egress.port(),
1497 nextObjective.id());
1498 ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for {} {} removed",
1499 TERMINATION,
1500 l2Tunnel.tunnelId()),
1501 (objective, error) ->
1502 log.warn("Failed to remove fwdObj for {} {}",
1503 TERMINATION,
1504 l2Tunnel.tunnelId(),
1505 error));
Pier Ventreac3e1d92016-11-17 22:26:29 -08001506 srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context));
1507
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001508 // un-comment in case you want to delete groups used by the pw
1509 // however, this will break the update of pseudowires cause the L2 interface group can
1510 // not be deleted (it is referenced by other groups)
1511 /*
Pier Ventreac3e1d92016-11-17 22:26:29 -08001512 context = new ObjectiveContext() {
1513 @Override
1514 public void onSuccess(Objective objective) {
1515 log.debug("Previous {} next for {} removed", TERMINATION, key);
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 {} next for {}: {}", TERMINATION, key, error);
1524 if (future != null) {
1525 future.complete(error);
1526 }
1527 }
1528 };
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001529 srManager.flowObjectiveService.next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
1530 */
1531
1532 future.complete(null);
Pier Ventreac3e1d92016-11-17 22:26:29 -08001533 l2TerminationNextObjStore.remove(key);
1534 }
1535
1536 /**
Pier Ventref3cf5b92016-11-09 14:17:26 -08001537 * Utilities to generate pw key.
1538 *
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001539 * @param tunnelId the tunnel id
Pier Ventref3cf5b92016-11-09 14:17:26 -08001540 * @param direction the direction of the pw
1541 * @return the key of the store
1542 */
Pier Ventreac3e1d92016-11-17 22:26:29 -08001543 private String generateKey(long tunnelId, Direction direction) {
Pier Ventref3cf5b92016-11-09 14:17:26 -08001544 return String.format("%s-%s", tunnelId, direction);
1545 }
1546
1547 /**
Pier Luigi0be3f0f2017-01-30 09:47:36 -08001548 * Pwaas pipelines.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001549 */
1550 protected enum Pipeline {
1551 /**
1552 * The initiation pipeline.
1553 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001554 INITIATION, /**
Pier Ventref3cf5b92016-11-09 14:17:26 -08001555 * The termination pipeline.
1556 */
Pier Ventreac3e1d92016-11-17 22:26:29 -08001557 TERMINATION
Pier Ventref3cf5b92016-11-09 14:17:26 -08001558 }
1559
1560 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001561 * Enum helper to carry results of various operations.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001562 */
1563 public enum Result {
1564 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001565 * Happy ending scenario.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001566 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001567 SUCCESS(0, "No error occurred"),
1568
Pier Ventref3cf5b92016-11-09 14:17:26 -08001569 /**
1570 * We have problems with the supplied parameters.
1571 */
1572 WRONG_PARAMETERS(1, "Wrong parameters"),
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001573
Pier Ventref3cf5b92016-11-09 14:17:26 -08001574 /**
1575 * We have an internal error during the deployment
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001576 * or removal phase.
Pier Ventref3cf5b92016-11-09 14:17:26 -08001577 */
1578 INTERNAL_ERROR(3, "Internal error"),
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001579
Pier Ventref3cf5b92016-11-09 14:17:26 -08001580 /**
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001581 *
Pier Ventref3cf5b92016-11-09 14:17:26 -08001582 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001583 REMOVAL_ERROR(5, "Can not remove pseudowire from network configuration"),
1584
1585 /**
1586 *
1587 */
1588 ADDITION_ERROR(6, "Can not add pseudowire in network configuration"),
1589
1590 /**
1591 *
1592 */
1593 CONFIG_NOT_FOUND(7, "Can not find configuration class for pseudowires");
Pier Ventref3cf5b92016-11-09 14:17:26 -08001594
1595 private final int code;
1596 private final String description;
1597 private int nextId;
1598
Pier Ventreac3e1d92016-11-17 22:26:29 -08001599 Result(int code, String description) {
Pier Ventref3cf5b92016-11-09 14:17:26 -08001600 this.code = code;
1601 this.description = description;
1602 }
1603
1604 public String getDescription() {
1605 return description;
1606 }
1607
Pier Ventref3cf5b92016-11-09 14:17:26 -08001608 @Override
1609 public String toString() {
1610 return code + ": " + description;
1611 }
1612 }
1613
Pier Ventreac3e1d92016-11-17 22:26:29 -08001614 /**
1615 * Enum helper for handling the direction of the pw.
1616 */
1617 public enum Direction {
1618 /**
1619 * The forward direction of the pseudo wire.
1620 */
Andreas Pantelopoulos5e7be3d2017-10-23 12:18:25 -07001621 FWD, /**
Pier Ventreac3e1d92016-11-17 22:26:29 -08001622 * The reverse direction of the pseudo wire.
1623 */
Jon Hall8f867f42017-02-22 13:37:33 -08001624 REV
Pier Ventreac3e1d92016-11-17 22:26:29 -08001625 }
1626
Pier Ventre6b19e482016-11-07 16:21:04 -08001627}