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()