blob: 04c648126ece01f95718dd8f46f5841b56d9afb6 [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
17#
18# DATA HANDLERS
19#
20
21import re
22import modi
23import utif
24import error
25import command
26import time
27import datetime
28import rest_to_model
29import traceback
30
31from midw import *
32
33COMMAND_CIDR_RE = re.compile(r'^((\d{1,3}\.){3}\d{1,3})/(\d{1,2}?)$')
34
35
36def split_cidr_data_handler(value, data,
37 dest_ip='ip', dest_netmask='netmask', neg = False):
38 """
39 Split a cidr address (e.g. 192.168.1.1/24) into separate IP address
40 and netmask value. The names of the ip and netmask fields are
41 specified (typically directly in the same block/dictionary where
42 the argument data handler is specifed) with a 'dest-ip' and a
43 'dest-netmask' values.
44 """
45 global sdnsh
46
47 m = COMMAND_CIDR_RE.match(value)
48 if m:
49 bits = int(m.group(3))
50 if bits > 32:
51 raise error.ArgumentValidationError("max cidr block is 32")
52
53 data[dest_ip] = m.group(1)
54 if neg:
55 data[dest_netmask] = utif.inet_ntoa(~(0xffffffff << (32 - bits)))
56 else:
57 data[dest_netmask] = utif.inet_ntoa((0xffffffff << (32 - bits)))
58
59
60def alias_to_value_handler(value, obj_type, data, field, other = None):
61 """
62 Compute the alias value for the named field for the obj_type.
63 Place the resulting converted field into the data dictionary.
64
65 Since this is a data-handler, the data dict must be updated
66 even if this isn't an alias, otherwise the field value is lost.
67 """
68 global sdnsh
69 if sdnsh.description:
70 print 'alias_to_value_handler: ', value, obj_type, data, field
71
72 if field != mi.pk(obj_type):
73 # if this is a forgeign key, use the obj_type of the fk.
74 if mi.is_foreign_key(obj_type, field):
75 (obj_type, fk_name) = mi.foreign_key_references(obj_type, field)
76 else:
77 # XXX possibly other choices to determine alias_obj_type?
78 if sdnsh.description:
79 print 'alias_to_value_handler: field %s no obj-type ref %s ' % \
80 (field, obj_type)
81
82 if other:
83 parts = other.split('|')
84 key = mi.pk(parts[0]) # parts[0] <- first part of other
85 if len(parts) > 0:
86 other = parts[0]
87 key = field
88 # not clear whether the part[1] is useful, two parts used
89 # in other functions, example complete-from-another()
90 other = mi.obj_type_related_config_obj_type(other)
91 converted_value = convert_alias_to_object_key(other, value)
92
93 pk = mi.pk(other)
94 if mi.is_compound_key(other, pk) and converted_value != value:
95 pk_dict = { pk : converted_value }
96 mi.split_compound_into_dict(other, pk, pk_dict, is_prefix = True)
97 for (k,v) in pk_dict.items():
98 if k != pk:
99 data[k] = v
100 if sdnsh.description:
101 print "alias_to_value_handler: compound (other) %s:%s <- %s:%s" % \
102 (k, data[k], other, converted_value)
103 else:
104 case = mi.get_obj_type_field_case_sensitive(other, field)
105 data[field] = utif.convert_case(case, converted_value)
106
107 if sdnsh.description:
108 print "alias_to_value_handler: (other) %s:%s <- %s:%s" % \
109 (key, data[key], other, value)
110 else:
111 # Some obj_types, for example, host, have no cassandra data,
112 # but do have a related obj_type which is in the store
113 obj_type = mi.obj_type_related_config_obj_type(obj_type)
114 converted_value = convert_alias_to_object_key(obj_type, value)
115
116 pk = mi.pk(obj_type)
117 if mi.is_compound_key(obj_type, pk) and converted_value != value:
118 pk_dict = { pk : converted_value }
119 split_obj_type = other if other != None else obj_type
120 mi.split_compound_into_dict(split_obj_type, pk, pk_dict, is_prefix = True)
121 for (k,v) in pk_dict.items():
122 if k != pk:
123 data[k] = v
124 if sdnsh.description:
125 print "alias_to_value_handler: compound %s:%s <- %s:%s" % \
126 (k, data[k], obj_type, converted_value)
127 else:
128 case = mi.get_obj_type_field_case_sensitive(obj_type, field)
129 data[field] = utif.convert_case(case, converted_value)
130
131 if sdnsh.description:
132 print "alias_to_value_handler: %s:%s <- %s:%s" % (field,
133 data[field], obj_type, value)
134
135
136def replace_value_handler(value, obj_type, data, field, other = None):
137 """
138 Use the other field when its present to find a related obj_type,
139 look for the field in that structure to populate data.
140 """
141 global sdnsh
142
143 table = obj_type
144
145 if sdnsh.description:
146 print "replace_value_handler: obj_type: %s value: %s data: %s field %s other %s" % \
147 (obj_type, value, data, field, other)
148 fields = [field]
149 if other:
150 parts = other.split('|')
151 table = parts[0]
152 fields = parts[1:]
153
154 try:
155 row = sdnsh.get_object_from_store(table, value)
156 except Exception, e:
157 raise error.ArgumentValidationError("Unknown value %s (%s)" %
158 (value, obj_type))
159 for field in fields:
160 if field not in row:
161 raise error.ArgumentValidationError("Unknown field %s (%s)" %
162 (field, obj_type))
163 if sdnsh.description:
164 print 'replace_value_handler: set %s <- %s from obj-type %s' %\
165 (field, row[field], table)
166 data[field] = row[field]
167
168
169def enable_disable_to_boolean_handler(value, data, field):
170 if value == 'enable':
171 data[field] = True
172 if value == 'disable':
173 data[field] = False
174
175
176def date_to_integer_handler(value, data, field):
177 if (value == 'now' or value == 'current'):
178 data[field] = int(time.time()*1000)
179
180 try:
181 data[field] = int(value)
182 except:
183 pass
184
185 for f,pre in [('%Y-%m-%dT%H:%M:%S', None),
186 ('%Y-%m-%d %H:%M:%S', None),
187 ('%Y-%m-%dT%H:%M:%S%z', None),
188 ('%Y-%m-%d %H:%M:%S%z', None),
189 ('%Y-%m-%d', None),
190 ('%m-%d', '%Y-'),
191 ('%H:%M', '%Y-%m-%dT')]:
192 try:
193 t = value
194 if pre:
195 pref = datetime.datetime.now().strftime(pre)
196 f = pre + f
197 t = pref + t
198
199 thetime = datetime.datetime.strptime(t, f)
200 data[field] = int(time.mktime(thetime.timetuple())*1000)
201 except:
202 pass
203
204
205HEX_RE = re.compile(r'^0x[0-9a-fA-F]+$')
206
207def hex_to_integer_handler(value, data, field):
208 if HEX_RE.match(str(value)):
209 _value = str(int(value, 16))
210 else:
211 _value = str(int(value))
212 data[field] = _value
213
214
215def _invert_netmask(value):
216 split_bytes = value.split('.')
217 return "%s.%s.%s.%s" % (255-int(split_bytes[0]),
218 255-int(split_bytes[1]),
219 255-int(split_bytes[2]),
220 255-int(split_bytes[3]))
221
222
223def convert_inverse_netmask_handler(value, data, field):
224 data[field] = _invert_netmask(value)
225
226
227def interface_ranges(names):
228 """
229 Given a list of interfaces (strings), in any order, with a numeric suffix,
230 collect together the prefix components, and create ranges with
231 adjacent numeric interfaces, so that a large collection of names
232 becomes easier to read. At the worst, the list will be as
233 complicated as the original (which would typically be very unlikely)
234
235 Example: names <- ['Eth0', 'Eth1', 'Eth2', 'Eth4', 'Eth5', 'Eth8']
236 result <- ['Eth0-2', 'Eth4-5', 'Eth8']
237
238 names <- ['1','2','3']
239 result <- ['1-3']
240
241 """
242 # collect the interfaces into dictionaries based on prefixes
243 # ahead of groups of digits.
244 groups = {}
245
246 def is_digit(c):
247 c_ord = ord(c)
248 if c_ord >= ord('0') and c_ord <= ord('9'):
249 return True
250 return False
251
252 for name in names:
253 if is_digit(name[-1]):
254 for index in range(-2, -len(name)-1, -1):
255 if not is_digit(name[index]):
256 index += 1
257 break;
258 else:
259 index = -len(name)
260
261 prefix = name[:index]
262 number = int(name[index:])
263 if not prefix in groups:
264 groups[prefix] = []
265 groups[prefix].append(number)
266 else:
267 groups[name] = []
268
269 for prefix in groups:
270 groups[prefix] = sorted(utif.unique_list_from_list(groups[prefix]))
271
272 ranges = []
273 for (prefix, value) in groups.items():
274 if len(value) == 0:
275 ranges.append(prefix)
276 else:
277 low = value[0]
278 prev = low
279 for next in value[1:] + [value[-1] + 2]: # +[] flushes last item
280 if next > prev + 1:
281 if prev == low:
282 ranges.append('%s%s' % (prefix, low))
283 else:
284 ranges.append('%s%s-%s' % (prefix, low, prev))
285 low = next
286 prev = next
287
288 return ranges
289
290
291#print interface_ranges(['1','2','3', 'oe'])
292#print interface_ranges(['Eth1','Eth2','Eth3', 'Eth4', 'o5', 'o6'])
293
294
295def check_missing_interface(switch, interface):
296 #
297 # The switch value could be a compound key reference to a
298 # switch, if there's a '|' in the switch valud, try to guess
299 # which entry is the switch
300
301 parts = switch.split('|')
302 if len(parts) > 1:
303 for part in parts:
304 if utif.COMMAND_DPID_RE.match(part):
305 switch = part
306 break
307 else:
308 switch = part[0]
309
310 try:
311 row = rest_to_model.get_model_from_url('switches', {'dpid' : switch })
312 except:
313 if sdnsh.description or sdnsh.debug_backtrace:
314 traceback.print_exc()
315 row = []
316
317 if len(row) == 0 or row[0].get('ip-address', '') == '':
318 sdnsh.warning('switch %s currently not active, '
319 'interface %s may not exist' %
320 (switch, interface))
321 return
322
323 try:
324 ports = rest_to_model.get_model_from_url('interfaces', {'dpid' : switch })
325 except:
326 # can't validate current list of interface names
327 return
328
329 if_names = [x['portName'].lower() for x in ports]
330 if not interface.lower() in if_names:
331 # pre-servce case, try to identify unique ranges
332 ranges = interface_ranges([x['portName'] for x in ports])
333
334 sdnsh.warning( 'active switch has no interface "%s", '
335 'known: %s' % (interface, ', '.join(ranges)) +
336 '\nUse \"exit; no interface %s\" to remove' % interface)
337
338
339def warn_missing_interface(value, data, field, is_no, obj_type, obj_value ):
340 if not is_no:
341 # need switch, if_name
342 pk_data = { mi.pk(obj_type) : obj_value }
343 mi.split_compound_into_dict(obj_type, mi.pk(obj_type), pk_data, True)
344 switch = pk_data.get('switch')
345 if switch == None:
346 switch = pk_data.get('dpid')
347 if switch == None:
348 switch = data.get('switch')
349 if switch == None:
350 switch = data.get('dpid')
351 if switch == None:
352 raise error.ArgumentValidationError("Can't identify switch for validation")
353 force = True if data.get('force', '') != '' else False
354 check_missing_interface(switch, value)
355 data[field] = value
356
357def convert_interface_to_port(value, data, field, other = None, scoped = None):
358 # look for the switch name in data
359 if scoped:
360 dpid = data.get(scoped)
361 elif 'dpid' in data:
362 dpid = data['dpid']
363 else:
364 dpid = data.get('switch', '') # possibly other choices
365
366 # if its not a specific switch, no conversion is possible
367 # should the value be passed through?
368 if dpid == '':
369 data[field] = value
370 return
371
372 ports = rest_to_model.get_model_from_url('interfaces', {'dpid' : dpid})
373 for port in ports:
374 if port['portName'] == value:
375 data[field] = port['portNumber'] # should this be a string?
376 break
377 else:
378 raise error.ArgumentValidationError("Can't find port %s on switch %s" %
379 (value, dpid))
380
381
382def convert_tag_to_parts(value, data, namespace_key, name_key, value_key):
383 """
384 Split a tag of the form [ns].name=value into the three
385 component parts
386 """
387
388 if sdnsh.description:
389 print "convert_tag_to_parts: %s %s %s %s %s" % (
390 value, data, namespace_key, name_key, value_key)
391
392 tag_and_value = value.split('=')
393 if len(tag_and_value) != 2:
394 raise error.ArgumentValidationError("tag <[tag-namespace.]name>=<value>")
395
396 tag_parts = tag_and_value[0].split('.')
397 if len(tag_parts) == 1:
398 tag_namespace = "default"
399 tag_name = tag_parts[0]
400 elif len(tag_parts) >= 2:
401 tag_namespace = '.'.join(tag_parts[:-1])
402 tag_name = tag_parts[-1]
403
404 # should the names have some specific validation?
405 data[namespace_key] = tag_namespace
406 data[name_key] = tag_name
407 data[value_key] = tag_and_value[1]
408
409
410def init_data_handlers(bs, modi):
411 global sdnsh, mi
412 sdnsh = bs
413 mi = modi
414
415 command.add_argument_data_handler('split-cidr-data', split_cidr_data_handler,
416 {'kwargs': {'value': '$value',
417 'data': '$data',
418 'dest_ip': '$dest-ip',
419 'dest_netmask': '$dest-netmask'}})
420
421 command.add_argument_data_handler('split-cidr-data-inverse', split_cidr_data_handler,
422 {'kwargs': {'value': '$value',
423 'data': '$data',
424 'dest_ip': '$dest-ip',
425 'dest_netmask': '$dest-netmask',
426 'neg' : True}})
427
428 command.add_argument_data_handler('alias-to-value', alias_to_value_handler,
429 {'kwargs': {'value': '$value',
430 'data': '$data',
431 'field': '$field',
432 'other': '$other',
433 'obj_type' : '$obj-type'}})
434
435 command.add_argument_data_handler('replace-value', replace_value_handler,
436 {'kwargs': {'value': '$value',
437 'data': '$data',
438 'field': '$field',
439 'other': '$other',
440 'obj_type' : '$obj-type'}})
441
442 command.add_argument_data_handler('enable-disable-to-boolean', enable_disable_to_boolean_handler,
443 {'kwargs': {'value': '$value',
444 'data': '$data',
445 'field': '$field'}})
446
447 command.add_argument_data_handler('date-to-integer', date_to_integer_handler,
448 {'kwargs': {'value' : '$value',
449 'data' : '$data',
450 'field' : '$field'}})
451
452 command.add_argument_data_handler('hex-to-integer', hex_to_integer_handler,
453 {'kwargs': {'value' : '$value',
454 'data' : '$data',
455 'field' : '$field'}})
456
457 command.add_argument_data_handler('convert-inverse-netmask', convert_inverse_netmask_handler,
458 {'kwargs': {'value' : '$value',
459 'data' : '$data',
460 'field' : '$field'}})
461
462 command.add_argument_data_handler('warn-missing-interface', warn_missing_interface,
463 {'kwargs': {'value' : '$value',
464 'data' : '$data',
465 'field' : '$field',
466 'is_no' : '$is-no-command',
467 'obj_type' : '$current-mode-obj-type',
468 'obj_value' : '$current-mode-obj-id'}})
469
470 command.add_argument_data_handler('convert-interface-to-port', convert_interface_to_port,
471 {'kwargs': {'value' : '$value',
472 'data' : '$data',
473 'field' : '$field',
474 'other' : '$other',
475 'scoped' : '$scoped'}})
476
477 command.add_argument_data_handler('convert-tag-to-parts', convert_tag_to_parts,
478 {'kwargs': {'value' : '$value',
479 'data' : '$data',
480 'namespace_key' : '$namespace-key',
481 'name_key' : '$name-key',
482 'value_key' : '$value-key'}})