blob: ccf760ff4f8149240d57e3ad2f9fcded681ad384 [file] [log] [blame]
Andreas Pantelopouloscd339592018-02-23 14:18:00 -08001/*
2 * Copyright 2015-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
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080019import org.onlab.packet.MplsLabel;
20import org.onlab.packet.VlanId;
21import org.onosproject.cli.AbstractShellCommand;
22import org.onosproject.net.ConnectPoint;
23
24import java.util.HashMap;
25import java.util.HashSet;
Andreas Pantelopoulos811bbae2018-03-15 16:56:09 -070026import java.util.List;
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080027import java.util.Map;
28import java.util.Set;
29
30import org.onosproject.net.device.DeviceService;
31import org.onosproject.net.intf.InterfaceService;
32import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
Andreas Pantelopoulosd21d0ca2018-04-10 19:34:47 -070035import static com.google.common.base.Preconditions.checkArgument;
36
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080037/**
38 * Utility class with static methods that help
39 * parse pseudowire related information and also
40 * verify that a pseudowire combination is valid.
41 */
42public final class PwaasUtil {
43
44 private static final Logger log = LoggerFactory.getLogger(PwaasUtil.class);
45
Charles Chan636906e2018-07-13 18:08:33 -070046 private static DeviceService deviceService;
47 private static InterfaceService intfService;
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080048
Charles Chan636906e2018-07-13 18:08:33 -070049 // Suppress ExceptionInInitializerError and simply set the value to null,
50 // such that unit tests have a chance to replace the variable with mocked service
51 static {
52 try {
53 deviceService = AbstractShellCommand.get(DeviceService.class);
54 } catch (NullPointerException e) {
55 deviceService = null;
56 }
57 try {
58 intfService = AbstractShellCommand.get(InterfaceService.class);
59 } catch (NullPointerException e) {
60 intfService = null;
61 }
62 }
63
64 static final String ERR_SERVICE_UNAVAIL = "Service %s not available";
65 static final String ERR_SAME_DEV =
66 "Pseudowire connection points can not reside in the same node, in pseudowire %d.";
67 static final String ERR_EMPTY_INNER_WHEN_OUTER_PRESENT =
68 "Inner tag should not be empty when outer tag is set for pseudowire %d for %s.";
69 static final String ERR_WILDCARD_VLAN =
70 "Wildcard VLAN matching not yet supported for pseudowire %d.";
71 static final String ERR_DOUBLE_TO_UNTAGGED =
72 "Support for double tag <-> untag is not supported for pseudowire %d.";
73 static final String ERR_DOUBLE_TO_SINGLE =
74 "Support for double-tag<-> single-tag is not supported for pseudowire %d.";
75 static final String ERR_SINGLE_TO_UNTAGGED =
76 "single-tag <-> untag is not supported for pseudowire %d.";
77 static final String ERR_VLAN_TRANSLATION =
78 "We do not support VLAN translation for pseudowire %d.";
79 static final String ERR_DEV_NOT_FOUND =
80 "Device %s does not exist for pseudowire %d.";
81 static final String ERR_PORT_NOT_FOUND =
82 "Port %s of device %s does not exist for pseudowire %d.";
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080083
84 private PwaasUtil() {
Charles Chan636906e2018-07-13 18:08:33 -070085
Andreas Pantelopouloscd339592018-02-23 14:18:00 -080086 }
87
88 /**
89 * Parses a vlan as a string. Returns the VlanId if
90 * provided String can be parsed as an integer or is '' / '*'
91 *
92 * @param vlan string as read from configuration
93 * @return VlanId null if error
94 */
95 public static VlanId parseVlan(String vlan) {
96
97 if (vlan.equals("*") || vlan.equals("Any")) {
98 return VlanId.vlanId("Any");
99 } else if (vlan.equals("") || vlan.equals("None")) {
100 return VlanId.vlanId("None");
101 } else {
Andreas Pantelopoulosd21d0ca2018-04-10 19:34:47 -0700102 return VlanId.vlanId(vlan);
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800103 }
104 }
105
106 /**
107 *
108 * @param mode RAW or TAGGED
109 * @return the L2Mode if input is correct
110 */
111 public static L2Mode parseMode(String mode) {
Andreas Pantelopoulosd21d0ca2018-04-10 19:34:47 -0700112 checkArgument(mode.equals("RAW") || mode.equals("TAGGED"),
113 "Invalid pseudowire mode of operation, should be TAGGED or RAW.");
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800114 return L2Mode.valueOf(mode);
115 }
116
117 /**
118 *
119 * @param label the mpls label of the pseudowire
120 * @return the MplsLabel
121 * @throws IllegalArgumentException if label is invalid
122 */
123 public static MplsLabel parsePWLabel(String label) {
Andreas Pantelopoulosd21d0ca2018-04-10 19:34:47 -0700124 return MplsLabel.mplsLabel(label);
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800125 }
126
127 /**
128 * Parses a string as a pseudowire id - which is an integer.
129 *
130 * @param id The id of pw in string form
131 * @return The id of pw as an Integer or null if it failed the conversion.
132 */
133 public static Integer parsePwId(String id) {
Andreas Pantelopoulosd21d0ca2018-04-10 19:34:47 -0700134 return Integer.parseInt(id);
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800135 }
136
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800137 /**
138 * Helper method to verify if the tunnel is whether or not
139 * supported.
140 *
141 * @param l2Tunnel the tunnel to verify
142 */
143 private static void verifyTunnel(L2Tunnel l2Tunnel) {
144
145 // Service delimiting tag not supported yet.
146 if (!l2Tunnel.sdTag().equals(VlanId.NONE)) {
147 throw new IllegalArgumentException(String.format("Service delimiting tag not supported yet for " +
148 "pseudowire %d.", l2Tunnel.tunnelId()));
149 }
150
151 // Tag mode not supported yet.
152 if (l2Tunnel.pwMode() == L2Mode.TAGGED) {
153 throw new IllegalArgumentException(String.format("Tagged mode not supported yet for pseudowire %d.",
154 l2Tunnel.tunnelId()));
155 }
156
157 // Raw mode without service delimiting tag
158 // is the only mode supported for now.
159 }
160
161 /**
162 * Helper method to verify if the policy is whether or not
163 * supported and if policy will be successfully instantiated in the
164 * network.
165 *
Charles Chan636906e2018-07-13 18:08:33 -0700166 * @param cP1 pseudo wire endpoint 1
167 * @param cP2 pseudo wire endpoint 2
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800168 * @param ingressInner the ingress inner tag
169 * @param ingressOuter the ingress outer tag
170 * @param egressInner the egress inner tag
171 * @param egressOuter the egress outer tag
Charles Chan636906e2018-07-13 18:08:33 -0700172 * @param tunnelId tunnel ID
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800173 */
Charles Chan636906e2018-07-13 18:08:33 -0700174 static void verifyPolicy(ConnectPoint cP1,
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800175 ConnectPoint cP2,
176 VlanId ingressInner,
177 VlanId ingressOuter,
178 VlanId egressInner,
179 VlanId egressOuter,
180 Long tunnelId) {
181
182 if (cP1.deviceId().equals(cP2.deviceId())) {
Charles Chan636906e2018-07-13 18:08:33 -0700183 throw new IllegalArgumentException(String.format(ERR_SAME_DEV, tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800184 }
185
186 // We can have multiple tags, all of them can be NONE,
187 // indicating untagged traffic, however, the outer tag can
188 // not have value if the inner tag is None
189 if (ingressInner.equals(VlanId.NONE) && !ingressOuter.equals(VlanId.NONE)) {
Charles Chan636906e2018-07-13 18:08:33 -0700190 throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT,
191 tunnelId, "cp1"));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800192 }
193
194 if (egressInner.equals(VlanId.NONE) && !egressOuter.equals(VlanId.NONE)) {
Charles Chan636906e2018-07-13 18:08:33 -0700195 throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT,
196 tunnelId, "cp2"));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800197 }
198
199 if (ingressInner.equals(VlanId.ANY) ||
200 ingressOuter.equals(VlanId.ANY) ||
201 egressInner.equals(VlanId.ANY) ||
202 egressOuter.equals(VlanId.ANY)) {
Charles Chan636906e2018-07-13 18:08:33 -0700203 throw new IllegalArgumentException(String.format(ERR_WILDCARD_VLAN, tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800204 }
205
206 if (((!ingressOuter.equals(VlanId.NONE) && !ingressInner.equals(VlanId.NONE)) &&
207 (egressOuter.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE)))
208 || ((ingressOuter.equals(VlanId.NONE) && ingressInner.equals(VlanId.NONE)) &&
209 (!egressOuter.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE)))) {
Charles Chan636906e2018-07-13 18:08:33 -0700210 throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_UNTAGGED, tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800211 }
212 if ((!ingressInner.equals(VlanId.NONE) &&
213 ingressOuter.equals(VlanId.NONE) &&
214 !egressOuter.equals(VlanId.NONE))
215 || (egressOuter.equals(VlanId.NONE) &&
216 !egressInner.equals(VlanId.NONE) &&
217 !ingressOuter.equals(VlanId.NONE))) {
Charles Chan636906e2018-07-13 18:08:33 -0700218 throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_SINGLE, tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800219 }
220
221 if ((ingressInner.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE))
222 || (!ingressInner.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE))) {
Charles Chan636906e2018-07-13 18:08:33 -0700223 throw new IllegalArgumentException(String.format(ERR_SINGLE_TO_UNTAGGED, tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800224 }
225
Charles Chan636906e2018-07-13 18:08:33 -0700226 // FIXME PW VLAN translation is not supported on Dune
227 // Need to explore doing that in egress table later if there is a requirement
228 if (!ingressInner.equals(egressInner) || !ingressOuter.equals(egressOuter)) {
229 throw new IllegalArgumentException(String.format(ERR_VLAN_TRANSLATION, tunnelId));
230 }
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800231
Charles Chan636906e2018-07-13 18:08:33 -0700232 if (deviceService == null) {
233 throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "DeviceService"));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800234 }
235
236 // check if cp1 and port of cp1 exist
237 if (deviceService.getDevice(cP1.deviceId()) == null) {
Charles Chan636906e2018-07-13 18:08:33 -0700238 throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP1.deviceId(), tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800239 }
240
241 if (deviceService.getPort(cP1) == null) {
Charles Chan636906e2018-07-13 18:08:33 -0700242 throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP1.port(),
243 cP1.deviceId(), tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800244 }
245
246 // check if cp2 and port of cp2 exist
247 if (deviceService.getDevice(cP2.deviceId()) == null) {
Charles Chan636906e2018-07-13 18:08:33 -0700248 throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP2.deviceId(), tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800249 }
250
251 if (deviceService.getPort(cP2) == null) {
Charles Chan636906e2018-07-13 18:08:33 -0700252 throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP2.port(),
253 cP2.deviceId(), tunnelId));
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800254 }
255 }
256
257 /**
258 * Verifies that the pseudowires will not conflict with each other.
259 *
260 * Further, check if vlans for connect points are already used.
261 *
262 * @param tunnel Tunnel for pw
263 * @param policy Policy for pw
264 * @param labelSet Label set used so far with this configuration
265 * @param vlanSet Vlan set used with this configuration
266 * @param tunnelSet Tunnel set used with this configuration
267 */
268 private static void verifyGlobalValidity(L2Tunnel tunnel,
269 L2TunnelPolicy policy,
270 Set<MplsLabel> labelSet,
271 Map<ConnectPoint, Set<VlanId>> vlanSet,
272 Set<Long> tunnelSet) {
273
274 if (tunnelSet.contains(tunnel.tunnelId())) {
275 throw new IllegalArgumentException(String.valueOf(String.format("Tunnel Id %d already used by" +
276 " another pseudowire, in " +
277 "pseudowire %d!",
278 tunnel.tunnelId(),
279 tunnel.tunnelId())));
280 }
281 tunnelSet.add(tunnel.tunnelId());
282
283 // check if tunnel id is used again
284 ConnectPoint cP1 = policy.cP1();
285 ConnectPoint cP2 = policy.cP2();
286
287 // insert cps to hashmap if this is the first time seen
288 if (!vlanSet.containsKey(cP1)) {
289 vlanSet.put(cP1, new HashSet<VlanId>());
290 }
291 if (!vlanSet.containsKey(cP2)) {
292 vlanSet.put(cP2, new HashSet<VlanId>());
293 }
294
295 // if single tagged or untagged vlan is the inner
296 // if double tagged vlan is the outer
297 VlanId vlanToCheckCP1;
298 if (policy.cP1OuterTag().equals(VlanId.NONE)) {
299 vlanToCheckCP1 = policy.cP1InnerTag();
300 } else {
301 vlanToCheckCP1 = policy.cP1OuterTag();
302 }
303
304 VlanId vlanToCheckCP2;
305 if (policy.cP2OuterTag().equals(VlanId.NONE)) {
306 vlanToCheckCP2 = policy.cP2InnerTag();
307 } else {
308 vlanToCheckCP2 = policy.cP2OuterTag();
309 }
310
311 if (labelSet.contains(tunnel.pwLabel())) {
312 throw new IllegalArgumentException(String.valueOf(String.format("Label %s already used by another" +
313 " pseudowire, in pseudowire %d!",
314 tunnel.pwLabel(), tunnel.tunnelId())));
315 }
316 labelSet.add(tunnel.pwLabel());
317
318 if (vlanSet.get(cP1).contains(vlanToCheckCP1)) {
319 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already used " +
320 "by another pseudowire, in " +
321 "pseudowire" +
322 " %d!", vlanToCheckCP1, cP1,
323 tunnel.tunnelId())));
324 }
325 vlanSet.get(cP1).add(vlanToCheckCP1);
326
327 if (vlanSet.get(cP2).contains(vlanToCheckCP2)) {
328 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s already used" +
329 " by another pseudowire, in" +
330 " pseudowire %d!", vlanToCheckCP2,
331 cP2,
332 tunnel.tunnelId())));
333 }
334 vlanSet.get(cP2).add(vlanToCheckCP2);
335
Charles Chan636906e2018-07-13 18:08:33 -0700336 if (intfService == null) {
337 throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "InterfaceService"));
338 }
339
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800340 // check that vlans for the connect points are not used
341 intfService.getInterfacesByPort(cP1).stream()
342 .forEach(intf -> {
343
344 // check if tagged pw affects tagged interface
345 if (intf.vlanTagged().contains(vlanToCheckCP1)) {
346 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already" +
347 " used for this" +
348 " interface, in" +
349 " pseudowire %d!",
350 vlanToCheckCP1, cP1,
351 tunnel.tunnelId())));
352 }
353
354 // if vlanNative != null this interface is configured with untagged traffic also
355 // check if it collides with untagged interface
356 if ((intf.vlanNative() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
357 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP1 " +
358 "%s already used " +
359 "for this " +
360 "interface, in " +
361 "pseudowire " +
362 "%d!", cP1,
363 tunnel.tunnelId())));
364 }
365
366 // if vlanUntagged != null this interface is configured only with untagged traffic
367 // check if it collides with untagged interface
368 if ((intf.vlanUntagged() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
369 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for " +
370 "cP1 %s already" +
371 " used for this" +
372 " interface," +
373 " in pseudowire %d!",
374 cP1, tunnel.tunnelId())));
375 }
376 });
377
378 intfService.getInterfacesByPort(cP2).stream()
379 .forEach(intf -> {
380 if (intf.vlanTagged().contains(vlanToCheckCP2)) {
381 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s " +
382 " used for " +
383 "this interface, " +
384 "in pseudowire %d!",
385 vlanToCheckCP2, cP2,
386 tunnel.tunnelId())));
387 }
388
389 // if vlanNative != null this interface is configured with untagged traffic also
390 // check if it collides with untagged interface
391 if ((intf.vlanNative() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
392 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic " +
393 "for cP2 %s " +
394 "already " +
395 "used for this" +
396 " interface, " +
397 "in pseudowire %d!",
398 cP2, tunnel.tunnelId())));
399 }
400
401 // if vlanUntagged != null this interface is configured only with untagged traffic
402 // check if it collides with untagged interface
403 if ((intf.vlanUntagged() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
404 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP2 %s" +
405 " already" +
406 " used for " +
407 "this interface, " +
408 "in pseudowire %d!",
409 cP2, tunnel.tunnelId())));
410 }
411 });
412
413 }
414
415 /**
416 * Helper method to verify the integrity of the pseudo wire.
417 *
418 * @param l2TunnelDescription the pseudo wire description
419 */
420 private static void verifyPseudoWire(L2TunnelDescription l2TunnelDescription,
421 Set<MplsLabel> labelSet,
422 Map<ConnectPoint, Set<VlanId>> vlanset,
423 Set<Long> tunnelSet) {
424
425 L2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel();
426 L2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy();
427
428 verifyTunnel(l2Tunnel);
429
430 verifyPolicy(
431 l2TunnelPolicy.cP1(),
432 l2TunnelPolicy.cP2(),
433 l2TunnelPolicy.cP1InnerTag(),
434 l2TunnelPolicy.cP1OuterTag(),
435 l2TunnelPolicy.cP2InnerTag(),
436 l2TunnelPolicy.cP2OuterTag(),
437 l2Tunnel.tunnelId()
438 );
439
440 verifyGlobalValidity(l2Tunnel,
441 l2TunnelPolicy,
442 labelSet,
443 vlanset,
444 tunnelSet);
445
446 }
447
Andreas Pantelopoulosffe69742018-03-20 13:58:49 -0700448 public static L2TunnelHandler.Result configurationValidity(List<L2TunnelDescription> pseudowires) {
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800449
450 // structures to keep pw information
451 // in order to see if instantiating them will create
452 // problems
453 Set<Long> tunIds = new HashSet<>();
454 Set<MplsLabel> labelsUsed = new HashSet<>();
455 Map<ConnectPoint, Set<VlanId>> vlanIds = new HashMap<>();
456
457 // TODO : I know we should not use exceptions for flow control,
458 // however this code was originally implemented in the configuration
459 // addition where the exceptions were propagated and the configuration was
460 // deemed not valid. I plan in the future to refactor the parts that
461 // check the pseudowire validity.
462 //
463 // Ideally we would like to return a String which could also return to
464 // the user issuing the rest request for adding the pseudowire.
465 try {
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800466 // check that pseudowires can be instantiated in the network
467 // we try to guarantee that all the pws will work before
468 // instantiating any of them
469 for (L2TunnelDescription pw : pseudowires) {
Andreas Pantelopoulos811bbae2018-03-15 16:56:09 -0700470 log.debug("Verifying pseudowire {}", pw);
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800471 verifyPseudoWire(pw, labelsUsed, vlanIds, tunIds);
472 }
Andreas Pantelopoulosffe69742018-03-20 13:58:49 -0700473
474 return L2TunnelHandler.Result.SUCCESS;
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800475 } catch (Exception e) {
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800476 log.error("Caught exception while validating pseudowire : {}", e.getMessage());
Andreas Pantelopoulosffe69742018-03-20 13:58:49 -0700477 return L2TunnelHandler.Result.CONFIGURATION_ERROR
478 .appendError(e.getMessage());
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800479 }
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800480 }
Andreas Pantelopouloscd339592018-02-23 14:18:00 -0800481}