blob: fa2807e53770bfcb60d865c1b990f10d713fd8b4 [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
19import org.apache.felix.scr.annotations.Reference;
20import org.apache.felix.scr.annotations.ReferenceCardinality;
21import org.onlab.packet.MplsLabel;
22import org.onlab.packet.VlanId;
23import org.onosproject.cli.AbstractShellCommand;
24import org.onosproject.net.ConnectPoint;
25
26import java.util.HashMap;
27import java.util.HashSet;
28import 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
36/**
37 * Utility class with static methods that help
38 * parse pseudowire related information and also
39 * verify that a pseudowire combination is valid.
40 */
41public final class PwaasUtil {
42
43 private static final Logger log = LoggerFactory.getLogger(PwaasUtil.class);
44
45 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
46 public static DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);;
47
48 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
49 public static InterfaceService intfService = AbstractShellCommand.get(InterfaceService.class);
50
51 private PwaasUtil() {
52 return;
53 }
54
55 /**
56 * Parses a vlan as a string. Returns the VlanId if
57 * provided String can be parsed as an integer or is '' / '*'
58 *
59 * @param vlan string as read from configuration
60 * @return VlanId null if error
61 */
62 public static VlanId parseVlan(String vlan) {
63
64 if (vlan.equals("*") || vlan.equals("Any")) {
65 return VlanId.vlanId("Any");
66 } else if (vlan.equals("") || vlan.equals("None")) {
67 return VlanId.vlanId("None");
68 } else {
69 try {
70 VlanId newVlan = VlanId.vlanId(vlan);
71 return newVlan;
72 } catch (IllegalArgumentException e) {
73 return null;
74 }
75 }
76 }
77
78 /**
79 *
80 * @param mode RAW or TAGGED
81 * @return the L2Mode if input is correct
82 */
83 public static L2Mode parseMode(String mode) {
84
85 if (!mode.equals("RAW") && !mode.equals("TAGGED")) {
86 return null;
87 }
88
89 return L2Mode.valueOf(mode);
90 }
91
92 /**
93 *
94 * @param label the mpls label of the pseudowire
95 * @return the MplsLabel
96 * @throws IllegalArgumentException if label is invalid
97 */
98 public static MplsLabel parsePWLabel(String label) {
99
100 try {
101 MplsLabel pwLabel = MplsLabel.mplsLabel(label);
102 return pwLabel;
103 } catch (Exception e) {
104 return null;
105 }
106 }
107
108 /**
109 * Parses a string as a pseudowire id - which is an integer.
110 *
111 * @param id The id of pw in string form
112 * @return The id of pw as an Integer or null if it failed the conversion.
113 */
114 public static Integer parsePwId(String id) {
115 try {
116 return Integer.parseInt(id);
117 } catch (Exception e) {
118 return null;
119 }
120 }
121
122
123 /**
124 * Helper method to verify if the tunnel is whether or not
125 * supported.
126 *
127 * @param l2Tunnel the tunnel to verify
128 */
129 private static void verifyTunnel(L2Tunnel l2Tunnel) {
130
131 // Service delimiting tag not supported yet.
132 if (!l2Tunnel.sdTag().equals(VlanId.NONE)) {
133 throw new IllegalArgumentException(String.format("Service delimiting tag not supported yet for " +
134 "pseudowire %d.", l2Tunnel.tunnelId()));
135 }
136
137 // Tag mode not supported yet.
138 if (l2Tunnel.pwMode() == L2Mode.TAGGED) {
139 throw new IllegalArgumentException(String.format("Tagged mode not supported yet for pseudowire %d.",
140 l2Tunnel.tunnelId()));
141 }
142
143 // Raw mode without service delimiting tag
144 // is the only mode supported for now.
145 }
146
147 /**
148 * Helper method to verify if the policy is whether or not
149 * supported and if policy will be successfully instantiated in the
150 * network.
151 *
152 * @param ingressInner the ingress inner tag
153 * @param ingressOuter the ingress outer tag
154 * @param egressInner the egress inner tag
155 * @param egressOuter the egress outer tag
156 */
157 private static void verifyPolicy(ConnectPoint cP1,
158 ConnectPoint cP2,
159 VlanId ingressInner,
160 VlanId ingressOuter,
161 VlanId egressInner,
162 VlanId egressOuter,
163 Long tunnelId) {
164
165 if (cP1.deviceId().equals(cP2.deviceId())) {
166 throw new IllegalArgumentException(String.format("Pseudowire connection points can not reside in the " +
167 "same node, in pseudowire %d.", tunnelId));
168 }
169
170 // We can have multiple tags, all of them can be NONE,
171 // indicating untagged traffic, however, the outer tag can
172 // not have value if the inner tag is None
173 if (ingressInner.equals(VlanId.NONE) && !ingressOuter.equals(VlanId.NONE)) {
174 throw new IllegalArgumentException(String.format("Inner tag should not be empty when " +
175 "outer tag is set for pseudowire %d for cP1.",
176 tunnelId));
177 }
178
179 if (egressInner.equals(VlanId.NONE) && !egressOuter.equals(VlanId.NONE)) {
180 throw new IllegalArgumentException(String.valueOf(String.format("Inner tag should not be empty when" +
181 " outer tag is set for " +
182 "pseudowire %d " +
183 "for cP2.", tunnelId)));
184 }
185
186 if (ingressInner.equals(VlanId.ANY) ||
187 ingressOuter.equals(VlanId.ANY) ||
188 egressInner.equals(VlanId.ANY) ||
189 egressOuter.equals(VlanId.ANY)) {
190 throw new IllegalArgumentException(String.valueOf(String.format("Wildcard VLAN matching not yet " +
191 "supported for pseudowire %d.",
192 tunnelId)));
193 }
194
195 if (((!ingressOuter.equals(VlanId.NONE) && !ingressInner.equals(VlanId.NONE)) &&
196 (egressOuter.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE)))
197 || ((ingressOuter.equals(VlanId.NONE) && ingressInner.equals(VlanId.NONE)) &&
198 (!egressOuter.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE)))) {
199 throw new IllegalArgumentException(String.valueOf(String.format("Support for double tag <-> untag is not" +
200 "supported for pseudowire %d.",
201 tunnelId)));
202 }
203 if ((!ingressInner.equals(VlanId.NONE) &&
204 ingressOuter.equals(VlanId.NONE) &&
205 !egressOuter.equals(VlanId.NONE))
206 || (egressOuter.equals(VlanId.NONE) &&
207 !egressInner.equals(VlanId.NONE) &&
208 !ingressOuter.equals(VlanId.NONE))) {
209 throw new IllegalArgumentException(String.valueOf(String.format("Support for double-tag<->" +
210 "single-tag is not supported" +
211 " for pseudowire %d.", tunnelId)));
212 }
213
214 if ((ingressInner.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE))
215 || (!ingressInner.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE))) {
216 throw new IllegalArgumentException(String.valueOf(String.format("single-tag <-> untag is not supported" +
217 " for pseudowire %d.", tunnelId)));
218 }
219
220
221 if (!ingressInner.equals(egressInner) && !ingressOuter.equals(egressOuter)) {
222 throw new IllegalArgumentException(String.valueOf(String.format("We do not support changing both tags " +
223 "in double tagged pws, only the " +
224 "outer," +
225 " for pseudowire %d.", tunnelId)));
226 }
227
228 // check if cp1 and port of cp1 exist
229 if (deviceService.getDevice(cP1.deviceId()) == null) {
230 throw new IllegalArgumentException(String.valueOf(String.format("cP1 device %s does not exist for" +
231 " pseudowire %d.", cP1.deviceId(),
232 tunnelId)));
233 }
234
235 if (deviceService.getPort(cP1) == null) {
236 throw new IllegalArgumentException(String.valueOf(String.format("Port %s for cP1 device %s does not" +
237 " exist for pseudowire %d.",
238 cP1.port(),
239 cP1.deviceId(), tunnelId)));
240 }
241
242 // check if cp2 and port of cp2 exist
243 if (deviceService.getDevice(cP2.deviceId()) == null) {
244 throw new IllegalArgumentException(String.valueOf(String.format("cP2 device %s does not exist for" +
245 " pseudowire %d.", cP2.deviceId(),
246 tunnelId)));
247 }
248
249 if (deviceService.getPort(cP2) == null) {
250 throw new IllegalArgumentException(String.valueOf(String.format("Port %s for cP2 device %s does " +
251 "not exist for pseudowire %d.",
252 cP2.port(), cP2.deviceId(), tunnelId)));
253 }
254 }
255
256 /**
257 * Verifies that the pseudowires will not conflict with each other.
258 *
259 * Further, check if vlans for connect points are already used.
260 *
261 * @param tunnel Tunnel for pw
262 * @param policy Policy for pw
263 * @param labelSet Label set used so far with this configuration
264 * @param vlanSet Vlan set used with this configuration
265 * @param tunnelSet Tunnel set used with this configuration
266 */
267 private static void verifyGlobalValidity(L2Tunnel tunnel,
268 L2TunnelPolicy policy,
269 Set<MplsLabel> labelSet,
270 Map<ConnectPoint, Set<VlanId>> vlanSet,
271 Set<Long> tunnelSet) {
272
273 if (tunnelSet.contains(tunnel.tunnelId())) {
274 throw new IllegalArgumentException(String.valueOf(String.format("Tunnel Id %d already used by" +
275 " another pseudowire, in " +
276 "pseudowire %d!",
277 tunnel.tunnelId(),
278 tunnel.tunnelId())));
279 }
280 tunnelSet.add(tunnel.tunnelId());
281
282 // check if tunnel id is used again
283 ConnectPoint cP1 = policy.cP1();
284 ConnectPoint cP2 = policy.cP2();
285
286 // insert cps to hashmap if this is the first time seen
287 if (!vlanSet.containsKey(cP1)) {
288 vlanSet.put(cP1, new HashSet<VlanId>());
289 }
290 if (!vlanSet.containsKey(cP2)) {
291 vlanSet.put(cP2, new HashSet<VlanId>());
292 }
293
294 // if single tagged or untagged vlan is the inner
295 // if double tagged vlan is the outer
296 VlanId vlanToCheckCP1;
297 if (policy.cP1OuterTag().equals(VlanId.NONE)) {
298 vlanToCheckCP1 = policy.cP1InnerTag();
299 } else {
300 vlanToCheckCP1 = policy.cP1OuterTag();
301 }
302
303 VlanId vlanToCheckCP2;
304 if (policy.cP2OuterTag().equals(VlanId.NONE)) {
305 vlanToCheckCP2 = policy.cP2InnerTag();
306 } else {
307 vlanToCheckCP2 = policy.cP2OuterTag();
308 }
309
310 if (labelSet.contains(tunnel.pwLabel())) {
311 throw new IllegalArgumentException(String.valueOf(String.format("Label %s already used by another" +
312 " pseudowire, in pseudowire %d!",
313 tunnel.pwLabel(), tunnel.tunnelId())));
314 }
315 labelSet.add(tunnel.pwLabel());
316
317 if (vlanSet.get(cP1).contains(vlanToCheckCP1)) {
318 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already used " +
319 "by another pseudowire, in " +
320 "pseudowire" +
321 " %d!", vlanToCheckCP1, cP1,
322 tunnel.tunnelId())));
323 }
324 vlanSet.get(cP1).add(vlanToCheckCP1);
325
326 if (vlanSet.get(cP2).contains(vlanToCheckCP2)) {
327 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s already used" +
328 " by another pseudowire, in" +
329 " pseudowire %d!", vlanToCheckCP2,
330 cP2,
331 tunnel.tunnelId())));
332 }
333 vlanSet.get(cP2).add(vlanToCheckCP2);
334
335 // check that vlans for the connect points are not used
336 intfService.getInterfacesByPort(cP1).stream()
337 .forEach(intf -> {
338
339 // check if tagged pw affects tagged interface
340 if (intf.vlanTagged().contains(vlanToCheckCP1)) {
341 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already" +
342 " used for this" +
343 " interface, in" +
344 " pseudowire %d!",
345 vlanToCheckCP1, cP1,
346 tunnel.tunnelId())));
347 }
348
349 // if vlanNative != null this interface is configured with untagged traffic also
350 // check if it collides with untagged interface
351 if ((intf.vlanNative() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
352 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP1 " +
353 "%s already used " +
354 "for this " +
355 "interface, in " +
356 "pseudowire " +
357 "%d!", cP1,
358 tunnel.tunnelId())));
359 }
360
361 // if vlanUntagged != null this interface is configured only with untagged traffic
362 // check if it collides with untagged interface
363 if ((intf.vlanUntagged() != null) && vlanToCheckCP1.equals(VlanId.NONE)) {
364 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for " +
365 "cP1 %s already" +
366 " used for this" +
367 " interface," +
368 " in pseudowire %d!",
369 cP1, tunnel.tunnelId())));
370 }
371 });
372
373 intfService.getInterfacesByPort(cP2).stream()
374 .forEach(intf -> {
375 if (intf.vlanTagged().contains(vlanToCheckCP2)) {
376 throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s " +
377 " used for " +
378 "this interface, " +
379 "in pseudowire %d!",
380 vlanToCheckCP2, cP2,
381 tunnel.tunnelId())));
382 }
383
384 // if vlanNative != null this interface is configured with untagged traffic also
385 // check if it collides with untagged interface
386 if ((intf.vlanNative() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
387 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic " +
388 "for cP2 %s " +
389 "already " +
390 "used for this" +
391 " interface, " +
392 "in pseudowire %d!",
393 cP2, tunnel.tunnelId())));
394 }
395
396 // if vlanUntagged != null this interface is configured only with untagged traffic
397 // check if it collides with untagged interface
398 if ((intf.vlanUntagged() != null) && vlanToCheckCP2.equals(VlanId.NONE)) {
399 throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP2 %s" +
400 " already" +
401 " used for " +
402 "this interface, " +
403 "in pseudowire %d!",
404 cP2, tunnel.tunnelId())));
405 }
406 });
407
408 }
409
410 /**
411 * Helper method to verify the integrity of the pseudo wire.
412 *
413 * @param l2TunnelDescription the pseudo wire description
414 */
415 private static void verifyPseudoWire(L2TunnelDescription l2TunnelDescription,
416 Set<MplsLabel> labelSet,
417 Map<ConnectPoint, Set<VlanId>> vlanset,
418 Set<Long> tunnelSet) {
419
420 L2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel();
421 L2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy();
422
423 verifyTunnel(l2Tunnel);
424
425 verifyPolicy(
426 l2TunnelPolicy.cP1(),
427 l2TunnelPolicy.cP2(),
428 l2TunnelPolicy.cP1InnerTag(),
429 l2TunnelPolicy.cP1OuterTag(),
430 l2TunnelPolicy.cP2InnerTag(),
431 l2TunnelPolicy.cP2OuterTag(),
432 l2Tunnel.tunnelId()
433 );
434
435 verifyGlobalValidity(l2Tunnel,
436 l2TunnelPolicy,
437 labelSet,
438 vlanset,
439 tunnelSet);
440
441 }
442
443 public static boolean configurationValidity(Set<L2TunnelDescription> pseudowires) {
444
445 // structures to keep pw information
446 // in order to see if instantiating them will create
447 // problems
448 Set<Long> tunIds = new HashSet<>();
449 Set<MplsLabel> labelsUsed = new HashSet<>();
450 Map<ConnectPoint, Set<VlanId>> vlanIds = new HashMap<>();
451
452 // TODO : I know we should not use exceptions for flow control,
453 // however this code was originally implemented in the configuration
454 // addition where the exceptions were propagated and the configuration was
455 // deemed not valid. I plan in the future to refactor the parts that
456 // check the pseudowire validity.
457 //
458 // Ideally we would like to return a String which could also return to
459 // the user issuing the rest request for adding the pseudowire.
460 try {
461
462 // check that pseudowires can be instantiated in the network
463 // we try to guarantee that all the pws will work before
464 // instantiating any of them
465 for (L2TunnelDescription pw : pseudowires) {
466 verifyPseudoWire(pw, labelsUsed, vlanIds, tunIds);
467 }
468 } catch (Exception e) {
469
470 log.error("Caught exception while validating pseudowire : {}", e.getMessage());
471 return false;
472 }
473
474 // return true
475 return true;
476 }
477
478}