Adding ONOS Segment Routing CLI files to new repo
diff --git a/cli/tools/validate.py b/cli/tools/validate.py
new file mode 100755
index 0000000..e710130
--- /dev/null
+++ b/cli/tools/validate.py
@@ -0,0 +1,664 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2010,2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+import subprocess
+import os, atexit
+import sys, traceback # traceback.print_exc()
+from optparse import OptionParser
+from types import StringType
+import datetime
+import json
+import traceback
+import re
+import time
+import urllib2
+import httplib # provides error processing for isinstance
+import socket
+
+from prettyprint import PrettyPrinter
+from storeclient import StoreClient
+from climodelinfo import CliModelInfo
+from vendor import VendorDB
+
+class ParamException(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+TIMESTAMP_HELP = \
+ ('Enter an integer timestamp in milliseconds since the epoch or formatted\n' +
+ 'as one of the following options in your local timezone:\n'
+ '"YYYY-MM-DD HH:MM:SS" YYYY-MM-DDTHH:MM:SS\n' +
+ '"YYYY-MM-DD HH:MM:SS+TTT" YYYY-MM-DDTHH:MM:SS+TTT\n' +
+ 'YYYY-MM-DD MM-DD\n' +
+ 'HH:MM now')
+
+
+class Validate():
+
+ # initialize in init_obj_type_info
+ obj_type_info_dict = {} # see init_obj_type_info_dict
+ obj_types = []
+ obj_keys = {}
+
+ known_controllers = ["127.0.0.1:8000"]
+ controller = None
+ # LOOK! manage this separately until we can for sure talk to eth0 addr instead of 127.0.0.1
+ controller_for_prompt = "127.0.0.1:8000"
+ cluster = "default"
+
+ run = True
+
+ # Cached options for last completion
+ last_line = None
+ last_options = None
+
+ # Are we running in batch mode, i.e. "cat commands | cli.py"
+ batch = False
+
+ # used for parsing as an arg
+ local_name_pattern = "localhost://([A-Za-z0-9_:@\-\.\/]+$)"
+
+ # contained objects
+ pp = None
+ store = None
+ model_handler = None
+
+ #
+ # --------------------------------------------------------------------------------
+ # init_obj_type_info_dict
+ #
+ # Builds the init_obj_type_info_dict, which is a dictionary indexed by
+ # the current mode, which also results in a dictionary, then indexed
+ # by a either a 'field' or 'field_ordering' key.
+ #
+ # When init_obj_type_info_dict is indexed by the table/model name, the
+ # value is a dictionary. That dictionary has several keys:
+ # - 'fields'
+ # - 'field_orderings'
+ #
+ # The 'fields' indexed result returns a dictionary, each of which
+ # is indexed by the names of the fields within the table/model. further
+ # indicies describe details about that field within the table:
+ # - 'formatter' desscribes the prettyPrinter function for that field
+ # - 'primary_key' identifes this field as the key for the table/model.
+ # - 'verbose_name' identfies an additonal name for the field, inteneded
+ # to be more descriptive
+ # - 'has_rest_model' True/False
+ # - 'json_serialize_string' True/False
+ # - 'help_text' String providing more clues about the intent of this field
+ #
+ # The 'field_ordering' dictionary associated with the name of the table/model
+ # is used to order the output.
+ #
+ # Also populates the self.obj_keys[], which is a dictionary mapping the table/model
+ # name to the name of the storage key (table/model's column) for that table.
+ #
+ def init_obj_type_info_dict(self):
+ self.cli_model_info = CliModelInfo()
+ self.obj_type_info_dict = self.cli_model_info.get_complete_obj_type_info_dict()
+ self.obj_types = [k for (k,v) in self.obj_type_info_dict.items()
+ if 'has_rest_model' in v]
+ for (k,d) in self.obj_type_info_dict.items():
+ candidate_keys = [f for f in d['fields'].keys()
+ if d['fields'][f].get('primary_key', False)]
+ if len(candidate_keys) > 0:
+ self.obj_keys[k] = candidate_keys[0]
+
+
+ #
+ # --------------------------------------------------------------------------------
+ # make_unit_formatter
+ #
+ def make_unit_formatter(self, units):
+ return lambda x,y: '%s %s' % (x, units)
+
+ #
+ # --------------------------------------------------------------------------------
+ # init_obj_type_info_dict
+ #
+ # Builds the init_obj_type_info_dict, which is a dictionary indexed by
+ # the current mode, which also results in a dictionary, then indexed
+ # by a either a 'field' or 'field_ordering' key.
+ #
+ # When init_obj_type_info_dict is indexed by the table/model name, the
+ # value is a dictionary. That dictionary has several keys:
+ # - 'fields'
+ # - 'field_orderings'
+ #
+ # The 'fields' indexed result returns a dictionary, each of which
+ # is indexed by the names of the fields within the table/model. further
+ # indicies describe details about that field within the table:
+ # - 'formatter' desscribes the prettyPrinter function for that field
+ # - 'primary_key' identifes this field as the key for the table/model.
+ # - 'verbose_name' identfies an additonal name for the field, inteneded
+ # to be more descriptive
+ # - 'has_rest_model' True/False
+ # - 'json_serialize_string' True/False
+ # - 'help_text' String providing more clues about the intent of this field
+ #
+ # The 'field_ordering' dictionary associated with the name of the table/model
+ # is used to order the output.
+ #
+ # Also populates the self.obj_keys[], which is a dictionary mapping the table/model
+ # name to the name of the storage key (table/model's column) for that table.
+ #
+ def init_obj_type_info_dict(self):
+ self.cli_model_info = CliModelInfo()
+ self.obj_type_info_dict = self.cli_model_info.get_complete_obj_type_info_dict()
+ self.obj_types = [k for (k,v) in self.obj_type_info_dict.items()
+ if 'has_rest_model' in v]
+ for (k,d) in self.obj_type_info_dict.items():
+ candidate_keys = [f for f in d['fields'].keys()
+ if d['fields'][f].get('primary_key', False)]
+ if len(candidate_keys) > 0:
+ self.obj_keys[k] = candidate_keys[0]
+
+ #
+ # --------------------------------------------------------------------------------
+ # init
+ #
+ def init(self):
+
+ self.vendordb = VendorDB()
+ self.vendordb.init()
+
+ parser = OptionParser()
+ parser.add_option("-c", "--controller", dest="controller",
+ help="set default controller to CONTROLLER",
+ metavar="CONTROLLER", default=self.controller)
+ (options, args) = parser.parse_args()
+ self.controller = options.controller
+ if not self.controller:
+ self.controller = "127.0.0.1:8000"
+
+ if not sys.stdin.isatty():
+ self.batch = True
+
+ self.init_obj_type_info_dict()
+
+ self.pp = PrettyPrinter(self.obj_type_info_dict)
+ self.store = StoreClient()
+ self.store.set_controller(self.controller)
+
+
+ #
+ # --------------------------------------------------------------------------------
+ # parse_optional_parameters
+ #
+ def parse_optional_parameters(self, params, words):
+ parsed = {}
+ i = 0
+ while i < len(words):
+ word = words[i]
+ possible = [x for x in params if x.startswith(word)]
+ if len(possible) == 0:
+ raise ParamException('unknown option: %s' % word)
+ elif len(possible) > 1:
+ raise ParamException('ambiguous option: %s\n%s' %
+ (word, "\n".join(possible)))
+ else:
+ param_name = possible[0]
+ param = params[param_name]
+ if (param['type'] == 'flag'):
+ parsed[param_name] = True
+ else:
+ if i+1 < len(words):
+ argument = words[i+1]
+ if (param['type'] == 'string'):
+ parsed[param_name] = argument
+ elif (param['type'] == 'int'):
+ try:
+ parsed[param_name] = int(argument)
+ except ValueError:
+ raise ParamException('option %s requires ' +
+ 'integer argument'
+ % word)
+ elif (param['type'] == 'enum'):
+ arg_possible = [x
+ for x in param['values']
+ if x.startswith(argument)]
+ if (len(arg_possible) == 0):
+ raise ParamException('option %s value must be in (%s)' %
+ (word,", ".join(param['values'])))
+ elif (len(arg_possible) > 1):
+ raise ParamException('ambiguous option %s value:\n%s' %
+ (word, "\n".join(arg_possible)))
+ else:
+ parsed[param_name] = arg_possible[0]
+ i += 1
+ else:
+ raise ParamException('option %s requires an argument'
+ % word)
+ i += 1
+ return parsed
+
+ #
+ # --------------------------------------------------------------------------------
+ # rest_error_to_dict
+ # Turn an exception into an error dictionary, which can later be printed.
+ # using rest_error_dict_to_message().
+ #
+ def rest_error_to_dict(self, e, detail=None):
+ errors = None
+ # try to identifify validation requests
+ if isinstance(e, httplib.BadStatusLine):
+ errors = {'connection_error' : 'REST API server %s: '
+ 'server not running' %
+ self.controller}
+ return errors
+
+ elif isinstance(e, urllib2.HTTPError):
+ code = e.code
+ error_returned = e.readline()
+
+ if code == 404:
+ if detail:
+ errors = {'not_found_error' : 'Not Found: %s' % detail}
+ else:
+ errors = {'not_found_error' : 'Not Found: %s' % error_returned}
+ elif code == 500 or code == 403:
+ errors = {'connection_error' : 'REST API server %s unable to connect: '
+ 'Cassandra possibly not running' %
+ self.controller}
+ elif code == 400:
+ try:
+ errors = json.loads(error_returned)
+ except:
+ # if the error can't be converted into a dictionary, then imbed the complete
+ # errors as the value for a specific error key.
+ errors = {'error_result_error': "Can't convert returned error: %s" % error_returned}
+ pass
+ else:
+ errors = {'unknown_error': 'HttpError %s' % error_returned}
+ else:
+ errors = {'unknown_error': "Need error managmenet for error %s" % type(e)}
+
+ return errors
+
+ #
+ # --------------------------------------------------------------------------------
+ # rest_error_dict_to_message
+ # Turn an rest_error_dict returned from rest_error_to_dict
+ # into an error message which can ge printed. Code assumes multiple errors
+ # won't occur; if a 'field_error' exists, for example, a 'model_error' won't
+ # also be posted in the error
+ #
+ def rest_error_dict_to_message(self, rest_error_dict):
+ error_msg = ""
+ if 'field_errors' in rest_error_dict:
+ for (k,v) in rest_error_dict['field_errors'].items():
+ error_msg += "Syntax error: field %s: %s" % (k,v)
+ # traceback.print_stack(), to find out why the error occured
+ elif 'model_error' in rest_error_dict:
+ error_msg += "Error: %s" % rest_error_dict['model_error']
+ elif 'not_found_error' in rest_error_dict:
+ error_msg += "Error: %s" % rest_error_dict['not_found_error']
+ elif 'connection_error' in rest_error_dict:
+ error_msg += rest_error_dict['connection_error']
+ elif 'error_result_error' in rest_error_dict:
+ error_msg += rest_error_dict['error_result_error']
+ elif 'unknown_error' in rest_error_dict:
+ error_msg += rest_error_dict['unknown_error']
+ else:
+ error_msg = "REST API server on controller-node %s " % self.controller
+ error_msg += "had %s error:\n" % rest_error_dict['error_type']
+ error_msg += rest_error_dict['description']
+ return error_msg
+
+ #
+ # --------------------------------------------------------------------------------
+ # method_from_name
+ #
+ def method_from_name(self, name):
+ return getattr(self, "validate_" + name.replace("-","_"), None)
+
+ #
+ # --------------------------------------------------------------------------------
+ # validate_port
+ #
+ # - validate port foreign key (to switch)
+ #
+ def validate_port(self):
+ error = None
+ print "validate_port"
+
+ # collect known switches
+ switch_ids = None
+ switch_key = self.obj_keys["switch"]
+ try:
+ switch_table = self.store.get_table_from_store("switch")
+ switch_ids = [x[switch_key] for x in switch_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect switch, no switch dpid validation for port table"
+ error = None
+
+ # collect known ports
+ port_table = None
+ port_key = self.obj_keys["port"]
+ try:
+ port_table = self.store.get_table_from_store("port")
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect ports"
+ return
+
+ for port in port_table:
+ if not port_key in port:
+ print "No port id in row"
+ else:
+ port_id = port[port_key]
+ if not 'switch' in port:
+ print 'port %s No switch in port (foreign key)' % port_id
+
+ #
+ # --------------------------------------------------------------------------------
+ # validate_switch
+ #
+ # - validate switch foreign key (to controller)
+ #
+ def validate_switch(self):
+ print "validate_switch"
+ error = None
+
+ # collect known controllers
+ controller_ids = None
+ controller_key = self.obj_keys["controller-node"]
+ try:
+ controller_table = self.store.get_table_from_store("controller-node")
+ controller_ids = [x[contoller_key] for x in controller_table if controller_key in controller_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect controller, no controller validation for switches"
+ error = None
+ if len(controller_ids) == 0:
+ print "Unable to collect any controller ids"
+
+ # collect known ports
+ switch_table = None
+ switch_key = self.obj_keys["switch"]
+ try:
+ switch_table = self.store.get_table_from_store("switch")
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect switches"
+ return
+
+ if len(switch_table) == 0:
+ print "switch table empty"
+
+ for switch in switch_table:
+ if not switch_key in switch:
+ print "No switch id in row"
+ else:
+ switch_id = switch[switch_key]
+ if not 'switch' in switch:
+ print 'switch %s No controller foreign key' % switch_id
+ else:
+ controller = switch['controller']
+ if not controller in controller_ids:
+ print "switch %s missing controller (foreign key) %s " % (switch_id, controller)
+
+ #
+ # --------------------------------------------------------------------------------
+ # validate_host_vns_interface
+ #
+ # - validate host-vns-interface foreigb key (to vns-interface)
+ # - validate host-vns-interface foreigb key (to host)
+ # - crack the id into three fragments, and validate each of the
+ # fragments references the expected componont (ie: matches the foreign key)
+ #
+ def validate_host_vns_interface(self):
+ print "host_vns_interface"
+
+ error = None
+ # collect host's
+ host_ids = None
+ host_key = self.obj_keys["host"]
+ try:
+ host_table = self.store.get_table_from_store("host")
+ host_ids = [x[host_key] for x in host_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect hosts, no host-vns-interface host validation"
+ error = None
+
+ # collect vns-interfaces
+ vns_interface_ids = None
+ vns_interface_key = self.obj_keys["vns-interface"]
+ try:
+ vns_interface_table = self.store.get_table_from_store("vns-interface")
+ vns_interface_ids = [x[vns_interface_key] for x in vns_interface_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect vns-interfaces, no host-vns-interface validation for vns-interfaces"
+ error = None
+
+ # collect host-vns-interface
+ host_vns_interface_ids = None
+ host_vns_interface_key = self.obj_keys["host-vns-interface"]
+ try:
+ host_vns_interface_table = self.store.get_table_from_store("host-vns-interface")
+ host_vns_interface_ids = [x[host_vns_interface_key] for x in host_vns_interface_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect host-vns-interface"
+ return
+
+ if len(host_vns_interface_table) == 0:
+ print "host_vns_interface_table empty"
+
+ host_vns_interface_id = self.obj_keys['host-vns-interface']
+ for host_vns_interface in host_vns_interface_table:
+ if not host_vns_interface_id in host_vns_interface:
+ print "host_vns_interface no primary key"
+ this_host_vns_interface_id = host_vns_interface[host_vns_interface_id]
+ if not 'host' in host_vns_interface:
+ print "host_vns_interface %s no host foreign key" % this_host_vns_interface_id
+ else:
+ host_foreign_key = host_vns_interface['host']
+ if not host_foreign_key in host_ids:
+ print "host_vns_interface %s foreign key %s references missing host" % \
+ (this_host_vns_interface_id, host_foreign_key)
+
+ if not 'interface' in host_vns_interface:
+ print "host_vns_interface %s no vns-interface foreign key %s" % \
+ (this_host_vns_interface_id, foreign_key)
+ else:
+ interface_foreign_key = host_vns_interface['interface']
+ if not interface_foreign_key in vns_interface_ids:
+ print "host_vns_interface %s foreign key %s" % \
+ (this_host_vns_interface, interface_foreign_key)
+
+ parts = this_host_vns_interface_id.split("|")
+ if len(parts) != 3:
+ print "host_vns_interface_id %d needs to be three fields split by '|'"
+ else:
+ if parts[0] != host_foreign_key:
+ print "host_vns_interface %s related host foreign key %s isn't part of id" % \
+ (this_host_vns_interface, host_foreign_key)
+ # the interface_foreign_key should have two parts.
+ interface_parts = interface_foreign_key.split('|')
+ if len(interface_parts) != 2:
+ print "host_vns_interface %s related vns-interface foreign key %s " \
+ "needs to be two words split by '|'" % \
+ (this_host_vns_interface_id, interface_foreign_key)
+ elif interface_parts[0] != parts[1]:
+ print "host_vns_interface %s related vns_interface foreign key %s " \
+ "doesn't match host id part %s" % \
+ (this_host_vns_interface, interface_part[0], parts[1])
+ elif interface_parts[1] != parts[2]:
+ print "host_vns_interface %s related vns_interface foreign key %s " \
+ "doesn't match interface long name part %s" % \
+ (this_host_vns_interface, interface_part[1], parts[2])
+
+ #
+ # --------------------------------------------------------------------------------
+ # validate_vns_interface
+ #
+ def validate_vns_interface(self):
+ print "vns-interface"
+
+ # --------------------------------------------------------------------------------
+ # validate_vns_interface_rule
+ #
+ # - id exists,
+ # - each row has a foreign key
+ # - each foreign key exists
+ # - the id, which is a concatenation of vns name and row id, has the correct foreign key
+ #
+ def validate_vns_interface_rule(self):
+ print "vns_interface_rule"
+
+ error = None
+ # collect known vns's
+ try:
+ vns_table = self.store.get_table_from_store("vns-definition")
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ pass
+ if error:
+ print "Unable to collect vns-definition"
+ return
+
+ # collect known switches
+ switch_ids = None
+ switch_key = self.obj_keys["switch"]
+ try:
+ switch_table = self.store.get_table_from_store("switch")
+ switch_ids = [x[switch_key] for x in switch_table]
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ print self.rest_error_dict_to_message(error)
+ pass
+
+ if error:
+ print "Unable to collect switch, no switch dpid validation for vns rules"
+ error = None
+
+ try:
+ vns_interface_rules_table = self.store.get_table_from_store("vns-interface-rule")
+ except Exception, e:
+ error = self.rest_error_to_dict(e)
+ pass
+ if error:
+ print "Unable to collect vns-interface-rule"
+ return
+ vns_id = self.obj_keys["vns-interface-rule"]
+
+ for rule in vns_interface_rules_table:
+ if not vns_id in rule:
+ print "rule has missing rule id"
+ rule_id = rule[vns_id]
+ parts = rule_id.split("|")
+ if len(parts) < 2:
+ print "rule %s has invalid id" % rule_id
+ vns_part = parts[0]
+ if not 'vns' in rule:
+ print "rule %s has missing vns foreign key" % rule_id
+ else:
+ if rule['vns'] != vns_part:
+ print "rule %s has inconsistent vns foreign key: %s" % (rule_id, rule['vns'])
+ if 'ports' in rule and not 'switch' in rule:
+ print "rule %s has a ports field populated but no switch" % rule_id
+ elif 'switch' in rule and not 'ports' in rule:
+ print "rule %s has a switch field populated but no switch" % rule_id
+ if 'switch' in rule and not rule['switch'] in switch_ids:
+ print "rule %s has an unknown switch dpid %s" % (rule_id, rule['switch'])
+
+
+ #
+ # --------------------------------------------------------------------------------
+ # validate
+ #
+ def validate(self):
+ print "store validation"
+
+ tables = self.obj_type_info_dict.keys()
+ for table in tables:
+ method = self.method_from_name(table)
+ if method:
+ method()
+
+
+#
+# --------------------------------------------------------------------------------
+# Initialization crazyness to make it work across platforms. Many platforms don't include
+# GNU readline (e.g. mac os x) and we need to compensate for this
+
+import sys
+try:
+ import readline
+except ImportError:
+ try:
+ import pyreadline as readline
+ except ImportError:
+ print "Can't find any readline equivalent - aborting."
+else:
+ import rlcompleter
+ if(sys.platform == 'darwin'):
+ # needed for Mac, please fix Apple
+ readline.parse_and_bind ("bind ^I rl_complete")
+ else:
+ readline.parse_and_bind("tab: complete")
+ readline.parse_and_bind("?: possible-completions")
+
+
+#
+# --------------------------------------------------------------------------------
+# Run the shell
+
+def main():
+ # Uncomment the next two lines to enable remote debugging with PyDev
+ #import pydevd
+ #pydevd.settrace()
+ validate = Validate()
+ validate.init()
+ validate.validate()
+
+if __name__ == '__main__':
+ main()