Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 1 | # |
| 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 | |
| 21 | import re |
| 22 | import modi |
| 23 | import utif |
| 24 | import error |
| 25 | import command |
| 26 | import time |
| 27 | import datetime |
| 28 | import rest_to_model |
| 29 | import traceback |
| 30 | |
| 31 | from midw import * |
| 32 | |
| 33 | COMMAND_CIDR_RE = re.compile(r'^((\d{1,3}\.){3}\d{1,3})/(\d{1,2}?)$') |
| 34 | |
| 35 | |
| 36 | def 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 | |
| 60 | def 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 | |
| 136 | def 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 | |
| 169 | def 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 | |
| 176 | def 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 | |
| 205 | HEX_RE = re.compile(r'^0x[0-9a-fA-F]+$') |
| 206 | |
| 207 | def 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 | |
| 215 | def _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 | |
| 223 | def convert_inverse_netmask_handler(value, data, field): |
| 224 | data[field] = _invert_netmask(value) |
| 225 | |
| 226 | |
| 227 | def 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 | |
| 295 | def 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 | |
| 339 | def 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 | |
| 357 | def 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 | |
| 382 | def 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 | |
| 410 | def 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'}}) |