blob: 7cb60a64170fe02481fa23917fbe095c76d4147c [file] [log] [blame]
Andreas Pantelopouloscdbb22c2018-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
Charles Chan3958b752018-07-13 18:08:33 -070019import org.onlab.osgi.ServiceNotFoundException;
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080020import org.onlab.packet.MplsLabel;
21import org.onlab.packet.VlanId;
22import org.onosproject.cli.AbstractShellCommand;
23import org.onosproject.net.ConnectPoint;
24
25import java.util.HashMap;
26import java.util.HashSet;
Andreas Pantelopoulosd988c1a2018-03-15 16:56:09 -070027import java.util.List;
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080028import java.util.Map;
29import java.util.Set;
30
31import org.onosproject.net.device.DeviceService;
32import org.onosproject.net.intf.InterfaceService;
33import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
35
Andreas Pantelopoulosf172ef82018-04-10 19:34:47 -070036import static com.google.common.base.Preconditions.checkArgument;
37
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080038/**
39 * Utility class with static methods that help
40 * parse pseudowire related information and also
41 * verify that a pseudowire combination is valid.
42 */
43public final class PwaasUtil {
44
45 private static final Logger log = LoggerFactory.getLogger(PwaasUtil.class);
46
Charles Chan3958b752018-07-13 18:08:33 -070047 private static DeviceService deviceService;
48 private static InterfaceService intfService;
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080049
Charles Chan3958b752018-07-13 18:08:33 -070050 // Suppress ExceptionInInitializerError and simply set the value to null,
51 // such that unit tests have a chance to replace the variable with mocked service
52 static {
53 try {
54 deviceService = AbstractShellCommand.get(DeviceService.class);
55 } catch (NullPointerException | ServiceNotFoundException e) {
56 deviceService = null;
57 }
58 try {
59 intfService = AbstractShellCommand.get(InterfaceService.class);
60 } catch (NullPointerException | ServiceNotFoundException e) {
61 intfService = null;
62 }
63 }
64
65 static final String ERR_SERVICE_UNAVAIL = "Service %s not available";
66 static final String ERR_SAME_DEV =
67 "Pseudowire connection points can not reside in the same node, in pseudowire %d.";
68 static final String ERR_EMPTY_INNER_WHEN_OUTER_PRESENT =
69 "Inner tag should not be empty when outer tag is set for pseudowire %d for %s.";
70 static final String ERR_WILDCARD_VLAN =
71 "Wildcard VLAN matching not yet supported for pseudowire %d.";
72 static final String ERR_DOUBLE_TO_UNTAGGED =
73 "Support for double tag <-> untag is not supported for pseudowire %d.";
74 static final String ERR_DOUBLE_TO_SINGLE =
75 "Support for double-tag<-> single-tag is not supported for pseudowire %d.";
76 static final String ERR_SINGLE_TO_UNTAGGED =
77 "single-tag <-> untag is not supported for pseudowire %d.";
78 static final String ERR_VLAN_TRANSLATION =
79 "We do not support VLAN translation for pseudowire %d.";
80 static final String ERR_DEV_NOT_FOUND =
81 "Device %s does not exist for pseudowire %d.";
82 static final String ERR_PORT_NOT_FOUND =
83 "Port %s of device %s does not exist for pseudowire %d.";
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080084
85 private PwaasUtil() {
Charles Chan3958b752018-07-13 18:08:33 -070086
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -080087 }
88
89 /**
90 * Parses a vlan as a string. Returns the VlanId if
91 * provided String can be parsed as an integer or is '' / '*'
92 *
93 * @param vlan string as read from configuration
94 * @return VlanId null if error
95 */
96 public static VlanId parseVlan(String vlan) {
97
98 if (vlan.equals("*") || vlan.equals("Any")) {
99 return VlanId.vlanId("Any");
100 } else if (vlan.equals("") || vlan.equals("None")) {
101 return VlanId.vlanId("None");
102 } else {
Andreas Pantelopoulosf172ef82018-04-10 19:34:47 -0700103 return VlanId.vlanId(vlan);
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800104 }
105 }
106
107 /**
108 *
109 * @param mode RAW or TAGGED
110 * @return the L2Mode if input is correct
111 */
112 public static L2Mode parseMode(String mode) {
Andreas Pantelopoulosf172ef82018-04-10 19:34:47 -0700113 checkArgument(mode.equals("RAW") || mode.equals("TAGGED"),
114 "Invalid pseudowire mode of operation, should be TAGGED or RAW.");
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800115 return L2Mode.valueOf(mode);
116 }
117
118 /**
119 *
120 * @param label the mpls label of the pseudowire
121 * @return the MplsLabel
122 * @throws IllegalArgumentException if label is invalid
123 */
124 public static MplsLabel parsePWLabel(String label) {
Andreas Pantelopoulosf172ef82018-04-10 19:34:47 -0700125 return MplsLabel.mplsLabel(label);
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800126 }
127
128 /**
129 * Parses a string as a pseudowire id - which is an integer.
130 *
131 * @param id The id of pw in string form
132 * @return The id of pw as an Integer or null if it failed the conversion.
133 */
134 public static Integer parsePwId(String id) {
Andreas Pantelopoulosf172ef82018-04-10 19:34:47 -0700135 return Integer.parseInt(id);
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800136 }
137
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800138 /**
139 * Helper method to verify if the tunnel is whether or not
140 * supported.
141 *
142 * @param l2Tunnel the tunnel to verify
143 */
144 private static void verifyTunnel(L2Tunnel l2Tunnel) {
145
146 // Service delimiting tag not supported yet.
147 if (!l2Tunnel.sdTag().equals(VlanId.NONE)) {
148 throw new IllegalArgumentException(String.format("Service delimiting tag not supported yet for " +
149 "pseudowire %d.", l2Tunnel.tunnelId()));
150 }
151
152 // Tag mode not supported yet.
153 if (l2Tunnel.pwMode() == L2Mode.TAGGED) {
154 throw new IllegalArgumentException(String.format("Tagged mode not supported yet for pseudowire %d.",
155 l2Tunnel.tunnelId()));
156 }
157
158 // Raw mode without service delimiting tag
159 // is the only mode supported for now.
160 }
161
162 /**
163 * Helper method to verify if the policy is whether or not
164 * supported and if policy will be successfully instantiated in the
165 * network.
166 *
Charles Chan3958b752018-07-13 18:08:33 -0700167 * @param cP1 pseudo wire endpoint 1
168 * @param cP2 pseudo wire endpoint 2
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800169 * @param ingressInner the ingress inner tag
170 * @param ingressOuter the ingress outer tag
171 * @param egressInner the egress inner tag
172 * @param egressOuter the egress outer tag
Charles Chan3958b752018-07-13 18:08:33 -0700173 * @param tunnelId tunnel ID
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800174 */
Charles Chan3958b752018-07-13 18:08:33 -0700175 static void verifyPolicy(ConnectPoint cP1,
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800176 ConnectPoint cP2,
177 VlanId ingressInner,
178 VlanId ingressOuter,
179 VlanId egressInner,
180 VlanId egressOuter,
181 Long tunnelId) {
182
183 if (cP1.deviceId().equals(cP2.deviceId())) {
Charles Chan3958b752018-07-13 18:08:33 -0700184 throw new IllegalArgumentException(String.format(ERR_SAME_DEV, tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800185 }
186
187 // We can have multiple tags, all of them can be NONE,
188 // indicating untagged traffic, however, the outer tag can
189 // not have value if the inner tag is None
190 if (ingressInner.equals(VlanId.NONE) && !ingressOuter.equals(VlanId.NONE)) {
Charles Chan3958b752018-07-13 18:08:33 -0700191 throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT,
192 tunnelId, "cp1"));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800193 }
194
195 if (egressInner.equals(VlanId.NONE) && !egressOuter.equals(VlanId.NONE)) {
Charles Chan3958b752018-07-13 18:08:33 -0700196 throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT,
197 tunnelId, "cp2"));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800198 }
199
200 if (ingressInner.equals(VlanId.ANY) ||
201 ingressOuter.equals(VlanId.ANY) ||
202 egressInner.equals(VlanId.ANY) ||
203 egressOuter.equals(VlanId.ANY)) {
Charles Chan3958b752018-07-13 18:08:33 -0700204 throw new IllegalArgumentException(String.format(ERR_WILDCARD_VLAN, tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800205 }
206
207 if (((!ingressOuter.equals(VlanId.NONE) && !ingressInner.equals(VlanId.NONE)) &&
208 (egressOuter.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE)))
209 || ((ingressOuter.equals(VlanId.NONE) && ingressInner.equals(VlanId.NONE)) &&
210 (!egressOuter.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE)))) {
Charles Chan3958b752018-07-13 18:08:33 -0700211 throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_UNTAGGED, tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800212 }
213 if ((!ingressInner.equals(VlanId.NONE) &&
214 ingressOuter.equals(VlanId.NONE) &&
215 !egressOuter.equals(VlanId.NONE))
216 || (egressOuter.equals(VlanId.NONE) &&
217 !egressInner.equals(VlanId.NONE) &&
218 !ingressOuter.equals(VlanId.NONE))) {
Charles Chan3958b752018-07-13 18:08:33 -0700219 throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_SINGLE, tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800220 }
221
222 if ((ingressInner.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE))
223 || (!ingressInner.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE))) {
Charles Chan3958b752018-07-13 18:08:33 -0700224 throw new IllegalArgumentException(String.format(ERR_SINGLE_TO_UNTAGGED, tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800225 }
226
Charles Chan3958b752018-07-13 18:08:33 -0700227 // FIXME PW VLAN translation is not supported on Dune
228 // Need to explore doing that in egress table later if there is a requirement
229 if (!ingressInner.equals(egressInner) || !ingressOuter.equals(egressOuter)) {
230 throw new IllegalArgumentException(String.format(ERR_VLAN_TRANSLATION, tunnelId));
231 }
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800232
Charles Chan3958b752018-07-13 18:08:33 -0700233 if (deviceService == null) {
234 throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "DeviceService"));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800235 }
236
237 // check if cp1 and port of cp1 exist
238 if (deviceService.getDevice(cP1.deviceId()) == null) {
Charles Chan3958b752018-07-13 18:08:33 -0700239 throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP1.deviceId(), tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800240 }
241
242 if (deviceService.getPort(cP1) == null) {
Charles Chan3958b752018-07-13 18:08:33 -0700243 throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP1.port(),
244 cP1.deviceId(), tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800245 }
246
247 // check if cp2 and port of cp2 exist
248 if (deviceService.getDevice(cP2.deviceId()) == null) {
Charles Chan3958b752018-07-13 18:08:33 -0700249 throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP2.deviceId(), tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800250 }
251
252 if (deviceService.getPort(cP2) == null) {
Charles Chan3958b752018-07-13 18:08:33 -0700253 throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP2.port(),
254 cP2.deviceId(), tunnelId));
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800255 }
256 }
257
258 /**
259 * Verifies that the pseudowires will not conflict with each other.
260 *
261 * Further, check if vlans for connect points are already used.
262 *
263 * @param tunnel Tunnel for pw
264 * @param policy Policy for pw
265 * @param labelSet Label set used so far with this configuration
266 * @param vlanSet Vlan set used with this configuration
267 * @param tunnelSet Tunnel set used with this configuration
268 */
269 private static void verifyGlobalValidity(L2Tunnel tunnel,
270 L2TunnelPolicy policy,
271 Set<MplsLabel> labelSet,
272 Map<ConnectPoint, Set<VlanId>> vlanSet,
273 Set<Long> tunnelSet) {
274
275 if (tunnelSet.contains(tunnel.tunnelId())) {
276 throw new IllegalArgumentException(String.valueOf(String.format("Tunnel Id %d already used by" +
277 " another pseudowire, in " +
278 "pseudowire %d!",
279 tunnel.tunnelId(),
280 tunnel.tunnelId())));
281 }
282 tunnelSet.add(tunnel.tunnelId());
283
284 // check if tunnel id is used again
285 ConnectPoint cP1 = policy.cP1();
286 ConnectPoint cP2 = policy.cP2();
287
288 // insert cps to hashmap if this is the first time seen
289 if (!vlanSet.containsKey(cP1)) {
290 vlanSet.put(cP1, new HashSet<VlanId>());
291 }
292 if (!vlanSet.containsKey(cP2)) {
293 vlanSet.put(cP2, new HashSet<VlanId>());
294 }
295
296 // if single tagged or untagged vlan is the inner
297 // if double tagged vlan is the outer
298 VlanId vlanToCheckCP1;
299 if (policy.cP1OuterTag().equals(VlanId.NONE)) {
300 vlanToCheckCP1 = policy.cP1InnerTag();
301 } else {
302 vlanToCheckCP1 = policy.cP1OuterTag();
303 }
304
305 VlanId vlanToCheckCP2;
306 if (policy.cP2OuterTag().equals(VlanId.NONE)) {
307 vlanToCheckCP2 = policy.cP2InnerTag();
308 } else {
309 vlanToCheckCP2 = policy.cP2OuterTag();
310 }
311
312 if (labelSet.contains(tunnel.pwLabel())) {
313 throw new IllegalArgumentException(String.valueOf(String.format("Label %s already used by another" +
314 " pseudowire, in pseudowire %d!",
315 tunnel.pwLabel(), tunnel.tunnelId())));
316 }
317 labelSet.add(tunnel.pwLabel());
318
319 if (vlanSet.get(cP1).contains(vlanToCheckCP1)) {
320 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already used " +
321 "by another pseudowire, in " +
322 "pseudowire" +
323 " %d!", vlanToCheckCP1, cP1,
324 tunnel.tunnelId())));
325 }
326 vlanSet.get(cP1).add(vlanToCheckCP1);
327
328 if (vlanSet.get(cP2).contains(vlanToCheckCP2)) {
329 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s already used" +
330 " by another pseudowire, in" +
331 " pseudowire %d!", vlanToCheckCP2,
332 cP2,
333 tunnel.tunnelId())));
334 }
335 vlanSet.get(cP2).add(vlanToCheckCP2);
336
Charles Chan3958b752018-07-13 18:08:33 -0700337 if (intfService == null) {
338 throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "InterfaceService"));
339 }
340
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800341 // check that vlans for the connect points are not used
342 intfService.getInterfacesByPort(cP1).stream()
343 .forEach(intf -> {
344
345 // check if tagged pw affects tagged interface
346 if (intf.vlanTagged().contains(vlanToCheckCP1)) {
347 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already" +
348 " used for this" +
349 " interface, in" +
350 " pseudowire %d!",
351 vlanToCheckCP1, cP1,
352 tunnel.tunnelId())));
353 }
354
355 // if vlanNative != null this interface is configured with untagged traffic also
356 // check if it collides with untagged interface
357 if ((intf.vlanNative() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
358 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP1 " +
359 "%s already used " +
360 "for this " +
361 "interface, in " +
362 "pseudowire " +
363 "%d!", cP1,
364 tunnel.tunnelId())));
365 }
366
367 // if vlanUntagged != null this interface is configured only with untagged traffic
368 // check if it collides with untagged interface
369 if ((intf.vlanUntagged() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
370 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for " +
371 "cP1 %s already" +
372 " used for this" +
373 " interface," +
374 " in pseudowire %d!",
375 cP1, tunnel.tunnelId())));
376 }
377 });
378
379 intfService.getInterfacesByPort(cP2).stream()
380 .forEach(intf -> {
381 if (intf.vlanTagged().contains(vlanToCheckCP2)) {
382 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s " +
383 " used for " +
384 "this interface, " +
385 "in pseudowire %d!",
386 vlanToCheckCP2, cP2,
387 tunnel.tunnelId())));
388 }
389
390 // if vlanNative != null this interface is configured with untagged traffic also
391 // check if it collides with untagged interface
392 if ((intf.vlanNative() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
393 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic " +
394 "for cP2 %s " +
395 "already " +
396 "used for this" +
397 " interface, " +
398 "in pseudowire %d!",
399 cP2, tunnel.tunnelId())));
400 }
401
402 // if vlanUntagged != null this interface is configured only with untagged traffic
403 // check if it collides with untagged interface
404 if ((intf.vlanUntagged() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
405 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP2 %s" +
406 " already" +
407 " used for " +
408 "this interface, " +
409 "in pseudowire %d!",
410 cP2, tunnel.tunnelId())));
411 }
412 });
413
414 }
415
416 /**
417 * Helper method to verify the integrity of the pseudo wire.
418 *
419 * @param l2TunnelDescription the pseudo wire description
420 */
421 private static void verifyPseudoWire(L2TunnelDescription l2TunnelDescription,
422 Set<MplsLabel> labelSet,
423 Map<ConnectPoint, Set<VlanId>> vlanset,
424 Set<Long> tunnelSet) {
425
426 L2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel();
427 L2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy();
428
429 verifyTunnel(l2Tunnel);
430
431 verifyPolicy(
432 l2TunnelPolicy.cP1(),
433 l2TunnelPolicy.cP2(),
434 l2TunnelPolicy.cP1InnerTag(),
435 l2TunnelPolicy.cP1OuterTag(),
436 l2TunnelPolicy.cP2InnerTag(),
437 l2TunnelPolicy.cP2OuterTag(),
438 l2Tunnel.tunnelId()
439 );
440
441 verifyGlobalValidity(l2Tunnel,
442 l2TunnelPolicy,
443 labelSet,
444 vlanset,
445 tunnelSet);
446
447 }
448
Andreas Pantelopoulos96851252018-03-20 13:58:49 -0700449 public static L2TunnelHandler.Result configurationValidity(List<L2TunnelDescription> pseudowires) {
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800450
451 // structures to keep pw information
452 // in order to see if instantiating them will create
453 // problems
454 Set<Long> tunIds = new HashSet<>();
455 Set<MplsLabel> labelsUsed = new HashSet<>();
456 Map<ConnectPoint, Set<VlanId>> vlanIds = new HashMap<>();
457
458 // TODO : I know we should not use exceptions for flow control,
459 // however this code was originally implemented in the configuration
460 // addition where the exceptions were propagated and the configuration was
461 // deemed not valid. I plan in the future to refactor the parts that
462 // check the pseudowire validity.
463 //
464 // Ideally we would like to return a String which could also return to
465 // the user issuing the rest request for adding the pseudowire.
466 try {
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800467 // check that pseudowires can be instantiated in the network
468 // we try to guarantee that all the pws will work before
469 // instantiating any of them
470 for (L2TunnelDescription pw : pseudowires) {
Andreas Pantelopoulosd988c1a2018-03-15 16:56:09 -0700471 log.debug("Verifying pseudowire {}", pw);
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800472 verifyPseudoWire(pw, labelsUsed, vlanIds, tunIds);
473 }
Andreas Pantelopoulos96851252018-03-20 13:58:49 -0700474
475 return L2TunnelHandler.Result.SUCCESS;
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800476 } catch (Exception e) {
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800477 log.error("Caught exception while validating pseudowire : {}", e.getMessage());
Andreas Pantelopoulos96851252018-03-20 13:58:49 -0700478 return L2TunnelHandler.Result.CONFIGURATION_ERROR
479 .appendError(e.getMessage());
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800480 }
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800481 }
Andreas Pantelopouloscdbb22c2018-02-23 14:18:00 -0800482}