blob: e7101300ec254d24bf6edb9b27a49ca35d5ffec1 [file] [log] [blame]
#!/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()