blob: c083812caf68eb5aa3d54b230039a6a6f63908fc [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2011,2012,2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
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
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17import error
18import command
19import collections
20import numbers
21import re
22import traceback
23import utif
24import rest_to_model
25import datetime
26import socket
27
28from midw import *
29
30def validate_string(typedef, value):
31 """
32 Validate a string, check length.
33 """
34
35 # Check to see if the typedef has any pattern restrictions. If it does,
36 # then match against all of the patterns. If it matches any
37 # of the patterns (i.e. not all of the patterns), then the argument
38 # value is considered to be valid.
39 patterns = typedef.get('pattern')
40 if patterns:
41 # If it's a single pattern convert it to a tuple, so we can handle
42 # it in a common way below. Note that we don't use collection.Sequence
43 # in the isinstance call because strings are sequences
44 if command._is_string(patterns):
45 patterns = (patterns,)
46
47 for pattern in patterns:
48 if re.match(pattern, str(value)):
49 break
50 else:
51 command._raise_argument_validation_exception(typedef, value,
52 'invalid string pattern')
53
54 # Check for any length restrictions. This can be a single scalar length or
55 # a single length range or a sequence of either of those. For scalar lengths
56 # the length must match it exactly. For length ranges the length of the argument
57 # must be between the two values (inclusive). For a list of length specs, the
58 # length must match any of the lengths (either scalar or range) in the list.
59 lengths = typedef.get('length')
60 if lengths:
61 if command._is_single_range(lengths):
62 lengths = (lengths,)
63 for length in lengths:
64 command._check_one_range(length)
65 if isinstance(length, numbers.Integral):
66 if len(value) == length:
67 break
68 if len(value) >= length[0] and len(value) <= length[1]:
69 break
70 else:
71 command._raise_argument_validation_exception(typedef, value,
72 'invalid string length')
73
74 return value
75
76def validate_integer_range(typedef, value, ranges):
77 # Check for any range restrictions. This can be a single scalar value or
78 # a single range or a list/tuple of a combination of those. For scalar ranges
79 # the arg must match it exactly. For ranges the value of the argument
80 # must be between the two values (inclusive). For a list of range specs, the
81 # value must match any of the lengths (either scalar or range) in the list.
82 if ranges:
83 if command._is_single_range(ranges):
84 ranges = (ranges,)
85 for r in ranges:
86 command._check_one_range(r)
87 if isinstance(r, numbers.Integral):
88 if value == r:
89 break
90 else:
91 lower_boundary = command._convert_range_boundary(r[0], value)
92 upper_boundary = command._convert_range_boundary(r[1], value)
93 if value >= lower_boundary and value <= upper_boundary:
94 break
95 else:
96 command._raise_argument_validation_exception(typedef, value,
97 'value is outside specified '
98 'range: (%d-%d)' % (r[0], r[1]))
99
100def validate_integer(typedef, value):
101 # Check that the value is actually an integer
102 try:
103 value = int(value)
104 except (ValueError, TypeError):
105 command._raise_argument_validation_exception(typedef, value,
106 'value is not an integer')
107 validate_integer_range(typedef, value, typedef.get('range'))
108
109 return value
110
111HEX_RE = re.compile(r'^0x[0-9a-fA-F]+$')
112
113def validate_hex_or_dec_integer(typedef, value):
114 # Check that the value is actually an hex or decimal integer
115 if HEX_RE.match(value):
116 # if it matches, it ought to succeed conversion, below's really not needed
117 try:
118 _value = int(value, 16)
119 except (ValueError, TypeError):
120 command._raise_argument_validation_exception(typedef, value,
121 'value is not a hex integer')
122 else:
123 try:
124 _value = int(value)
125 except (ValueError, TypeError):
126 command._raise_argument_validation_exception(typedef, value,
127 'value is not an integer')
128 validate_integer_range(typedef, _value, typedef.get('range'))
129
130 return value
131
132
133IP_ADDR_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
134
135def is_netmask(value):
136 """
137 return True or False, True for a dotted-quad which can
138 be interpreted as a netmask, False otherwise
139 """
140 if not IP_ADDR_RE.match(value):
141 return False
142
143 # convert the netmask string to an integer
144 split_bytes = value.split('.')
145 netmask_int = 0
146 for b in split_bytes:
147 netmask_int = (netmask_int << 8) + int(b)
148
149 # Check that the value is composed of leading 1 bits followed by
150 # trailing 0 bits.
151 valid_netmasks = [0xffffffff - ((1 << i) - 1) for i in range(0,33)]
152 if netmask_int in valid_netmasks:
153 return True
154 return False
155
156def validate_netmask(typedef, value):
157 if not is_netmask(value):
158 command._raise_argument_validation_exception(typedef, value,
159 'invalid netmask')
160
161 return value
162
163def is_inverse_netmask(value):
164 """
165 return True or False, True for a dotted-quad which
166 can be interpreted as inverse netmasks (like 0.0.0.255),
167 False otherwise. The full mask of (255.255.255.255) is not
168 treated as inverse_netmask, but a valid netmask. This helps
169 disambiguate inverse_netmask from regular netmask
170 """
171 if not IP_ADDR_RE.match(value):
172 return False
173
174 # First convert the netmask string to an integer
175 split_bytes = value.split('.')
176 netmask_int = 0
177 for b in split_bytes:
178 netmask_int = (netmask_int << 8) + int(b)
179
180 # Check that the value is composed of leading 0 bits followed by
181 # trailing 1 bits. To disambiguate inverse from regular netmask,
182 # we mandate a leading zero bit in the netmask
183 valid_netmasks = [((1 << i) - 1) for i in range(0,32)]
184 if netmask_int in valid_netmasks:
185 return True
186 return False
187
188def validate_inverse_netmask(typedef, value):
189 """
190 These look like 0.0.0.255, and are intended to only match
191 when the there is one group of 1's and one group of zero's.
192 """
193 if is_inverse_netmask(value):
194 return value
195
196 command._raise_argument_validation_exception(typedef, value,
197 'invalid netmask')
198
199def validate_ip_address_not_netmask(typedef, value):
200 """
201 For ip addresses associated with interfaces, the configured value
202 must not be one of the many masks.
203 """
204 # if its not an ip address, then its not a netmask, requiring
205 # a test for ip address match.
206 if not IP_ADDR_RE.match(value):
207 command._raise_argument_validation_exception(typedef, value,
208 'not an ip-address')
209
210 # must be four dotted quad's, validate each is within 2^8 range
211 if len([x for x in value.split('.') if int(x) < 256]) != 4:
212 command._raise_argument_validation_exception(typedef, value,
213 'not an ip-address')
214
215 if (not is_inverse_netmask(value)) and (not is_netmask(value)):
216 return value
217
218 command._raise_argument_validation_exception(typedef, value,
219 'must not be a mask')
220
221
222CIDR_RE = re.compile(r'^((\d{1,3}\.){3}\d{1,3})/(\d{1,2}?)$')
223
224def validate_cidr_range(typedef, value):
225 match = CIDR_RE.match(value)
226 if not match:
227 command._raise_argument_validation_exception(typedef, value,
228 'not cidr syntax: ip/n')
229 if int(match.group(3)) > 32:
230 command._raise_argument_validation_exception(typedef, value,
231 'cidr range above 32')
232
233 if len([x for x in match.group(1).split('.') if int(x) < 256]) != 4:
234 command._raise_argument_validation_exception(typedef, value,
235 'cidr range above 32')
236
237 return value
238
239
240def validate_resolvable_ip_address(typedef, value):
241 # see if this is an object we know about, for exameple a switch.
242 dpid = convert_alias_to_object_key('switch-config', value)
243 try:
244 validate_dpid(typedef, dpid)
245 except:
246
247 try:
248 socket.gethostbyname(value)
249 except:
250 msg = 'unresolvable name'
251 command._raise_argument_validation_exception(typedef, value,
252 msg)
253
254
255
256def validate_identifier(typedef, value, reserved = None):
257 """
258 When identifier is used as a 'type' in the description, it will
259 validate against the sdnsh reserved_words.
260
261 When identifier is used as a 'base-type' in the description, it
262 will also use any 'reserved' attributes in the description.
263
264 This allows some control over whether the 'reserved' attribute
265 takes effect at the syntax-layer or at the action-layer
266 """
267 if not re.match(r'^[a-zA-Z0-9_-]+$', str(value)):
268 msg = 'Invalid characters in identifier'
269 command._raise_argument_validation_exception(typedef, value, msg)
270 if value in sdnsh.reserved_words:
271 msg = 'reserved word "%s" in "%s"' % (value, ', '.join(sdnsh.reserved_words))
272 command._raise_argument_validation_exception(typedef, value, msg)
273 if reserved and value in reserved:
274 msg = 'reserved word "%s" in "%s"' % (value, ', '.join(reserved))
275 command._raise_argument_validation_exception(typedef, value, msg)
276 return value
277
278
279def validate_date(typedef, value):
280 if (value == 'now' or value == 'current'):
281 return value
282
283 try:
284 return str(int(value))
285 except (ValueError, TypeError):
286 pass
287
288 for f,pre in [('%Y-%m-%dT%H:%M:%S', None),
289 ('%Y-%m-%d %H:%M:%S', None),
290 ('%Y-%m-%dT%H:%M:%S%z', None),
291 ('%Y-%m-%d %H:%M:%S%z', None),
292 ('%Y-%m-%d', None),
293 ('%m-%d', '%Y-'),
294 ('%H:%M', '%Y-%m-%dT')]:
295 try:
296 t = value
297 if pre:
298 pref = datetime.datetime.now().strftime(pre)
299 f = pre + f
300 t = pref + t
301
302 return value
303 except:
304 pass
305
306 command._raise_argument_validation_exception(typedef, value, 'invalid date')
307
308
309def validate_duration(typedef, value):
310 for s in ['weeks', 'days', 'hours', 'mins', 'secs', 'ms']:
311 if value.endswith(s):
312 return value
313 command._raise_argument_validation_exception(typedef, value,
314 'invalid duration')
315
316
317def validate_enum(typedef, value):
318 # FIXME: Sort of a hack. The base enum class doesn't have a values
319 # field, so there's nothing to check against. We really just use it
320 # as a base type to indicate the validation function (i.e. this function)
321 # to use.
322 name = typedef.get('name')
323 if name == 'enum':
324 return
325
326 enum_values = typedef.get('values')
327 if not enum_values:
328 raise error.CommandDescriptionError('Unspecified enum values')
329
330 if not isinstance(enum_values, collections.Mapping):
331 # If it's not a dictionary then it should be an array
332 # or tuple where the (string) elements are both the key
333 # and the value for the enum items. So here we convert
334 # to a dictionary so we can use the same logic for both
335 # cases below.
336 if command._is_string(enum_values):
337 enum_values = (enum_values,)
338 if isinstance(enum_values, collections.Sequence):
339 enum_values = dict((v,v) for v in enum_values)
340 else:
341 raise error.CommandDescriptionError(
342 'Enum values must be either a string, dict, tuple, or list')
343
344 prefix_matches = []
345 lower_value = value.lower()
346 for enum_value, return_value in enum_values.items():
347 lower_enum_value = enum_value.lower()
348 if lower_enum_value == lower_value:
349 return (return_value, enum_value)
350 if lower_enum_value.startswith(lower_value):
351 prefix_matches.append((return_value, enum_value))
352
353 if len(prefix_matches) == 0:
354 command._raise_argument_validation_exception(typedef, value,
355 'unexpected value for enum', enum_values.keys())
356
357 return prefix_matches
358
359
360COMMAND_MAC_ADDRESS_RE = re.compile(r'^(([A-Fa-f\d]){2}:?){5}[A-Fa-f\d]{2}$')
361COMMAND_MAC_ALT_RE = re.compile(r'^(([A-Fa-f\d]){4}\.?){2}[A-Fa-f\d]{4}$')
362
363def _is_mac_address(value):
364 if COMMAND_MAC_ALT_RE.match(value):
365 #
366 # Convert 1234.5678.9012 into 01:34:56:78:90:12
367 _mac = '%s:%s:%s:%s:%s:%s' % (
368 value[0:2], value[2:4],
369 value[5:7], value[7:9],
370 value[10:12], value[12:14])
371 return True
372 elif COMMAND_MAC_ADDRESS_RE.match(value):
373 return True
374 return False
375
376def validate_mac_address(typedef, value):
377 if _is_mac_address(value):
378 return value
379 command._raise_argument_validation_exception(typedef, value, 'mac address')
380
381def validate_host(typedef, value):
382 """
383 """
384
385 if _is_mac_address(value):
386 return value
387
388 pk = mi.pk('host-config')
389 key = convert_alias_to_object_key('host-config', value)
390 key_dict = { pk : key }
391 mi.split_compound_into_dict('host-config', pk, key_dict, is_prefix = True)
392 if 'mac' in key_dict and _is_mac_address(key_dict['mac']):
393 try:
394 _exists = rest_to_model.get_model_from_url('host', key_dict)
395 return value
396 except:
397 raise error.ArgumentValidationError(
398 'host "%s": doesn\'t exist' % value)
399
400 command._raise_argument_validation_exception(typedef, value,
401 'not host alias nor mac address')
402
403def validate_dpid(typedef, value):
404 if not utif.COMMAND_DPID_RE.match(value):
405 command._raise_argument_validation_exception(typedef, value, 'switch dpid')
406 return value
407
408
409def validate_switch_dpid(typedef, value):
410 """
411 Either the value must be a syntactic dpid (eight-hex-bytes),
412 Or the value must be a switch alias, and the underlying dpid
413 must exist.
414 """
415
416 try:
417 validate_dpid(typedef, value)
418 return value
419 except error.ArgumentValidationError, e:
420 dpid = convert_alias_to_object_key('switch-config', value)
421 try:
422 validate_dpid(typedef, dpid)
423 try:
424 _exists = sdnsh.get_object_from_store('switch-config', dpid)
425 return value
426 except e:
427 command._raise_argument_validation_exception(typedef, value,
428 'switch "%s" doesn\'t exist' % value)
429 except error.ArgumentValidationError, e:
430 command._raise_argument_validation_exception(typedef, value,
431 'not switch alias nor dpid')
432
433def validate_existing_obj(typedef, value, obj_type):
434 """
435 Lookup the specific obj_type's primary key value for validation.
436 """
437
438 lookup_value = convert_alias_to_object_key(obj_type, value)
439
440 try:
441 sdnsh.get_object_from_store(obj_type, lookup_value)
442 return value
443 except Exception, _e:
444 command._raise_argument_validation_exception(typedef, value, "doesn't exist ")
445
446
447def validate_config(typedef, value):
448 if value.startswith('config://'):
449 return value
450 elif utif.full_word_from_choices(value, ['running-config',
451 'upgrade-config',
452 'trash' ]):
453 return value
454 else:
455 for prefixes in ['http://', 'ftp://', 'tftp://', 'file://']:
456 if value.startswith(prefixes):
457 return value
458 data = sdnsh.store.get_user_data_table(value, 'latest')
459 if len(data):
460 return value
461
462 msg = 'not a valid copy, must be (running-config, upgrade-config ' \
463 'or must start with config://, http://, ftp://, or tftp://'
464 command._raise_argument_validation_exception(typedef, value, msg)
465
466
467def init_validations(bs, modi):
468 global sdnsh, mi
469 sdnsh = bs
470 mi = modi
471
472 # Initialize validation functions
473 command.add_validation('validate-string', validate_string)
474 command.add_validation('validate-integer', validate_integer)
475 command.add_validation('validate-hex-or-dec-integer', validate_hex_or_dec_integer)
476 command.add_validation('validate-netmask', validate_netmask)
477 command.add_validation('validate-inverse-netmask', validate_inverse_netmask)
478 command.add_validation('validate-ip-address-not-mask', validate_ip_address_not_netmask)
479 command.add_validation('validate-cidr-range', validate_cidr_range)
480 command.add_validation('validate-resolvable-ip-address',
481 validate_resolvable_ip_address)
482 command.add_validation('validate-identifier', validate_identifier,
483 {'kwargs': {'typedef' : '$typedef',
484 'value' : '$value',
485 'reserved' : '$reserved'}})
486 command.add_validation('validate-date', validate_date)
487 command.add_validation('validate-duration', validate_duration)
488 command.add_validation('validate-enum', validate_enum)
489 command.add_validation('validate-mac-address', validate_mac_address)
490 command.add_validation('validate-host', validate_host)
491 command.add_validation('validate-switch-dpid', validate_switch_dpid)
492 command.add_validation('validate-existing-obj', validate_existing_obj,
493 {'kwargs': {'typedef' : '$typedef',
494 'value' : '$value',
495 'obj_type' : '$obj_type'}})
496 command.add_validation('validate-config', validate_config)
497