PEP8 fixes

This commit is contained in:
Maxim Kulkin 2013-10-16 18:07:40 +04:00
parent 8ac684d600
commit a059946079
34 changed files with 10240 additions and 4887 deletions

View File

@ -3,4 +3,3 @@ if __name__ == '__main__':
import sys import sys
from ostack_validator.main import main from ostack_validator.main import main
main(sys.argv[1:]) main(sys.argv[1:])

View File

@ -18,7 +18,9 @@ app.conf.update(
CELERY_TRACK_STARTED=True CELERY_TRACK_STARTED=True
) )
class InspectionRequest(object): class InspectionRequest(object):
def __init__(self, nodes, username, password=None, private_key=None): def __init__(self, nodes, username, password=None, private_key=None):
super(InspectionRequest, self).__init__() super(InspectionRequest, self).__init__()
self.nodes = nodes self.nodes = nodes
@ -26,12 +28,15 @@ class InspectionRequest(object):
self.password = password self.password = password
self.private_key = private_key self.private_key = private_key
class InspectionResult(object): class InspectionResult(object):
def __init__(self, request, value): def __init__(self, request, value):
super(InspectionResult, self).__init__() super(InspectionResult, self).__init__()
self.request = request self.request = request
self.value = value self.value = value
@app.task @app.task
def ostack_inspect_task(request): def ostack_inspect_task(request):
logger = logging.getLogger('ostack_validator.task.inspect') logger = logging.getLogger('ostack_validator.task.inspect')
@ -39,7 +44,8 @@ def ostack_inspect_task(request):
discovery = OpenstackDiscovery() discovery = OpenstackDiscovery()
try: try:
openstack = discovery.discover(request.nodes, request.username, private_key=request.private_key) openstack = discovery.discover(request.nodes, request.username,
private_key=request.private_key)
except: except:
message = traceback.format_exc() message = traceback.format_exc()
logger.error(message) logger.error(message)
@ -53,13 +59,17 @@ def ostack_inspect_task(request):
except: except:
message = traceback.format_exc() message = traceback.format_exc()
logger.error(message) logger.error(message)
openstack.report_issue(Issue(Issue.ERROR, 'Unexpected error running inspection "%s". See log for details' % inspection.name)) openstack.report_issue(
Issue(
Issue.ERROR,
'Unexpected error running inspection "%s". See log for details' %
inspection.name))
return InspectionResult(request, openstack) return InspectionResult(request, openstack)
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
logging.getLogger('ostack_validator').setLevel(logging.DEBUG) logging.getLogger('ostack_validator').setLevel(logging.DEBUG)
app.start() app.start()

View File

@ -1,9 +1,11 @@
import copy import copy
def find(l, predicate): def find(l, predicate):
results = [x for x in l if predicate(x)] results = [x for x in l if predicate(x)]
return results[0] if len(results) > 0 else None return results[0] if len(results) > 0 else None
def index(l, predicate): def index(l, predicate):
i = 0 i = 0
while i < len(l): while i < len(l):
@ -12,19 +14,23 @@ def index(l, predicate):
i += 1 i += 1
return -1 return -1
def all_subclasses(klass): def all_subclasses(klass):
subclasses = klass.__subclasses__() subclasses = klass.__subclasses__()
for d in list(subclasses): for d in list(subclasses):
subclasses.extend(all_subclasses(d)) subclasses.extend(all_subclasses(d))
return subclasses return subclasses
def path_relative_to(path, base_path): def path_relative_to(path, base_path):
if not path.startswith('/'): if not path.startswith('/'):
path = os.path.join(base_path, paste_config_path) path = os.path.join(base_path, paste_config_path)
return path return path
class Version: class Version:
def __init__(self, major, minor=0, maintenance=0): def __init__(self, major, minor=0, maintenance=0):
"Create Version object by either passing 3 integers, one string or an another Version object" "Create Version object by either passing 3 integers, one string or an another Version object"
if isinstance(major, str): if isinstance(major, str):
@ -77,33 +83,51 @@ class Version:
class Mark(object): class Mark(object):
def __init__(self, source, line=0, column=0): def __init__(self, source, line=0, column=0):
self.source = source self.source = source
self.line = line self.line = line
self.column = column self.column = column
def __eq__(self, other): def __eq__(self, other):
return (self.source == source) and (self.line == other.line) and (self.column == other.column) return (
(self.source == source) and (
self.line == other.line) and (self.column == other.column)
)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
def merge(self, other): def merge(self, other):
return Mark(self.source, self.line + other.line, self.column + other.column) return (
Mark(
self.source,
self.line +
other.line,
self.column +
other.column)
)
def __repr__(self): def __repr__(self):
return '%s line %d column %d' % (self.source, self.line, self.column) return '%s line %d column %d' % (self.source, self.line, self.column)
class Error: class Error:
def __init__(self, message): def __init__(self, message):
self.message = message self.message = message
def __repr__(self): def __repr__(self):
return '<%s "%s">' % (str(self.__class__).split('.')[-1][:-2], self.message) return (
'<%s "%s">' % (
str(self.__class__).split('.')[-1][:-2],
self.message)
)
def __str__(self): def __str__(self):
return self.message return self.message
class Issue(object): class Issue(object):
FATAL = 'FATAL' FATAL = 'FATAL'
ERROR = 'ERROR' ERROR = 'ERROR'
@ -115,12 +139,19 @@ class Issue(object):
self.message = message self.message = message
def __repr__(self): def __repr__(self):
return '<%s type=%s message=%s>' % (str(self.__class__).split('.')[-1][:-2], self.type, self.message) return (
'<%s type=%s message=%s>' % (
str(self.__class__).split('.')[-1][:-2],
self.type,
self.message)
)
def __str__(self): def __str__(self):
return '%s: %s' % (self.type, self.message) return '%s: %s' % (self.type, self.message)
class MarkedIssue(Issue): class MarkedIssue(Issue):
def __init__(self, type, message, mark): def __init__(self, type, message, mark):
super(MarkedIssue, self).__init__(type, message) super(MarkedIssue, self).__init__(type, message)
self.mark = mark self.mark = mark
@ -131,16 +162,27 @@ class MarkedIssue(Issue):
return other return other
def __repr__(self): def __repr__(self):
return '<%s type=%s message=%s mark=%s>' % (str(self.__class__).split('.')[-1][:-2], self.type, self.message, self.mark) return (
'<%s type=%s message=%s mark=%s>' % (
str(self.__class__).split('.')[-1][:-2],
self.type,
self.message,
self.mark)
)
def __str__(self): def __str__(self):
return super(MarkedIssue, self).__str__() + (' (source "%s" line %d column %d)' % (self.mark.source, self.mark.line+1, self.mark.column+1)) return (
super(
MarkedIssue, self).__str__() + (' (source "%s" line %d column %d)' %
(self.mark.source, self.mark.line + 1, self.mark.column + 1))
)
class Inspection(object): class Inspection(object):
@classmethod @classmethod
def all_inspections(klass): def all_inspections(klass):
return [c for c in all_subclasses(klass)] return [c for c in all_subclasses(klass)]
def inspect(self, openstack): def inspect(self, openstack):
pass pass

View File

@ -1,4 +1,3 @@
from common import * from common import *
from ini import IniConfigParser from ini import IniConfigParser

View File

@ -1,6 +1,7 @@
from ostack_validator.common import Mark, Issue, MarkedIssue from ostack_validator.common import Mark, Issue, MarkedIssue
class ParseError(MarkedIssue): class ParseError(MarkedIssue):
def __init__(self, message, mark): def __init__(self, message, mark):
super(ParseError, self).__init__(Issue.ERROR, message, mark) super(ParseError, self).__init__(Issue.ERROR, message, mark)

View File

@ -4,6 +4,7 @@ from StringIO import StringIO
from ostack_validator.config_model import * from ostack_validator.config_model import *
from ostack_validator.config_formats.common import * from ostack_validator.config_formats.common import *
class IniConfigParser: class IniConfigParser:
key_value_re = re.compile("^(\S+?)\s*([:=])\s*('.*'|\".*\"|.*)\s*$") key_value_re = re.compile("^(\S+?)\s*([:=])\s*('.*'|\".*\"|.*)\s*$")
@ -29,16 +30,23 @@ class IniConfigParser:
line_number += 1 line_number += 1
if current_param_name and (current_param_value.quotechar or (line == '' or not line[0].isspace())): if current_param_name and (current_param_value.quotechar or (line == '' or not line[0].isspace())):
param = ConfigParameter(current_param_name.start_mark, current_param_value.end_mark, current_param_name, current_param_value, current_param_delimiter) param = ConfigParameter(
current_param_name.start_mark,
current_param_value.end_mark,
current_param_name,
current_param_value,
current_param_delimiter)
parameters.append(param) parameters.append(param)
current_param_name = None current_param_name = None
current_param_value = None current_param_value = None
current_param_delimiter = None current_param_delimiter = None
if line == '': continue if line == '':
continue
if line[0] in '#;': continue if line[0] in '#;':
continue
if line[0].isspace(): if line[0].isspace():
if current_param_name: if current_param_name:
@ -46,29 +54,38 @@ class IniConfigParser:
current_param_value.text += line.lstrip() current_param_value.text += line.lstrip()
continue continue
else: else:
errors.append(ParseError('Unexpected multiline value continuation', mark(line_number))) errors.append(
ParseError('Unexpected multiline value continuation', mark(line_number)))
continue continue
if line[0] == '[': if line[0] == '[':
end_index = line.find(']') end_index = line.find(']')
if end_index == -1: if end_index == -1:
errors.append(ParseError('Unclosed section', mark(line_number, len(line)))) errors.append(
ParseError('Unclosed section', mark(line_number, len(line))))
end_index = len(line) end_index = len(line)
while line[end_index-1].isspace(): end_index -= 1 while line[end_index - 1].isspace():
end_index -= 1
if end_index <= 1: if end_index <= 1:
errors.append(ParseError('Missing section name', mark(line_number))) errors.append(
ParseError('Missing section name', mark(line_number)))
continue continue
else: else:
i = end_index + 1 i = end_index + 1
while i < len(line): while i < len(line):
if not line[i].isspace(): if not line[i].isspace():
errors.append(ParseError('Extra chars after section name', mark(line_number, i))) errors.append(
ParseError('Extra chars after section name', mark(line_number, i)))
break break
i += 1 i += 1
if current_section_name.text != '' or len(parameters) > 0: if current_section_name.text != '' or len(parameters) > 0:
section = ConfigSection(current_section_name.start_mark, mark(line_number), current_section_name, parameters) section = ConfigSection(
current_section_name.start_mark,
mark(line_number),
current_section_name,
parameters)
sections.append(section) sections.append(section)
parameters = [] parameters = []
@ -105,14 +122,25 @@ class IniConfigParser:
quotechar=quotechar quotechar=quotechar
) )
else: else:
errors.append(ParseError('Syntax error in line "%s"' % line, mark(line_number))) errors.append(
ParseError('Syntax error in line "%s"' %
line, mark(line_number)))
if current_param_name: if current_param_name:
param = ConfigParameter(current_param_name.start_mark, current_param_value.end_mark, current_param_name, current_param_value, current_param_delimiter) param = ConfigParameter(
current_param_name.start_mark,
current_param_value.end_mark,
current_param_name,
current_param_value,
current_param_delimiter)
parameters.append(param) parameters.append(param)
if current_section_name.text != '' or len(parameters) > 0: if current_section_name.text != '' or len(parameters) > 0:
section = ConfigSection(current_section_name.start_mark, mark(line_number), current_section_name, parameters) section = ConfigSection(
current_section_name.start_mark,
mark(line_number),
current_section_name,
parameters)
sections.append(section) sections.append(section)
parameters = [] parameters = []
@ -123,4 +151,3 @@ class IniConfigParser:
config = ComponentConfig(base_mark, end_mark, name, sections, errors) config = ComponentConfig(base_mark, end_mark, name, sections, errors)
return config return config

View File

@ -2,7 +2,9 @@ import unittest
from ostack_validator.config_formats.ini import * from ostack_validator.config_formats.ini import *
class IniConfigParserTests(unittest.TestCase): class IniConfigParserTests(unittest.TestCase):
def setUp(self): def setUp(self):
self.parser = IniConfigParser() self.parser = IniConfigParser()
@ -12,7 +14,8 @@ class IniConfigParserTests(unittest.TestCase):
lines = lines[1:-1] lines = lines[1:-1]
first_line = lines[0] first_line = lines[0]
margin_size = 0 margin_size = 0
while margin_size < len(first_line) and first_line[margin_size].isspace(): margin_size += 1 while margin_size < len(first_line) and first_line[margin_size].isspace():
margin_size += 1
stripped_lines = [line[margin_size:] for line in lines] stripped_lines = [line[margin_size:] for line in lines]
@ -29,7 +32,10 @@ class IniConfigParserTests(unittest.TestCase):
self.assertEqual(0, len(config.errors)) self.assertEqual(0, len(config.errors))
self.assertParameter('param1', 'value1', config.sections[0].parameters[0]) self.assertParameter(
'param1',
'value1',
config.sections[0].parameters[0])
self.assertEqual(1, len(config.sections[0].parameters)) self.assertEqual(1, len(config.sections[0].parameters))
def test_colon_as_delimiter(self): def test_colon_as_delimiter(self):
@ -41,12 +47,18 @@ class IniConfigParserTests(unittest.TestCase):
def test_use_colon_delimiter_if_it_comes_before_equals_sign(self): def test_use_colon_delimiter_if_it_comes_before_equals_sign(self):
c = self.parse('param1: value=123') c = self.parse('param1: value=123')
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'value=123', c.sections[0].parameters[0]) self.assertParameter(
'param1',
'value=123',
c.sections[0].parameters[0])
def test_use_equals_delimiter_if_it_comes_before_colon(self): def test_use_equals_delimiter_if_it_comes_before_colon(self):
c = self.parse('param1=value:123') c = self.parse('param1=value:123')
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'value:123', c.sections[0].parameters[0]) self.assertParameter(
'param1',
'value:123',
c.sections[0].parameters[0])
def test_wrapping_value_with_single_quotes(self): def test_wrapping_value_with_single_quotes(self):
c = self.parse("param = 'foo bar'") c = self.parse("param = 'foo bar'")
@ -146,7 +158,10 @@ class IniConfigParserTests(unittest.TestCase):
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'line1line2', c.sections[0].parameters[0]) self.assertParameter(
'param1',
'line1line2',
c.sections[0].parameters[0])
def test_multiline_value_finished_by_other_parameter(self): def test_multiline_value_finished_by_other_parameter(self):
c = self.parse(""" c = self.parse("""
@ -190,12 +205,11 @@ class IniConfigParserTests(unittest.TestCase):
self.assertEqual(1, len(c.errors)) self.assertEqual(1, len(c.errors))
self.assertParameter('param2', 'value2', c.sections[0].parameters[0]) self.assertParameter('param2', 'value2', c.sections[0].parameters[0])
def _getattr(self, o, name): def _getattr(self, o, name):
if name.find('.') != -1: if name.find('.') != -1:
parts = name.split('.') parts = name.split('.')
o = getattr(o, parts[0]) o = getattr(o, parts[0])
if o == None: if o is None:
return None return None
return self._getattr(o, '.'.join(parts[1:])) return self._getattr(o, '.'.join(parts[1:]))
else: else:
@ -204,7 +218,9 @@ class IniConfigParserTests(unittest.TestCase):
def assertAttributes(self, attribute_values, subject): def assertAttributes(self, attribute_values, subject):
for attr, expected in attribute_values.items(): for attr, expected in attribute_values.items():
actual = self._getattr(subject, attr) actual = self._getattr(subject, attr)
self.assertEqual(expected, actual, "%s expected to have %s = %s, but the value was %s" % (subject, attr, expected, actual)) self.assertEqual(
expected, actual, "%s expected to have %s = %s, but the value was %s" %
(subject, attr, expected, actual))
def assertParameter(self, name, value, o): def assertParameter(self, name, value, o):
self.assertAttributes({'name.text': name, 'value.text': value}, o) self.assertAttributes({'name.text': name, 'value.text': value}, o)
@ -212,4 +228,3 @@ class IniConfigParserTests(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,7 +2,9 @@ import string
from ostack_validator.common import Mark from ostack_validator.common import Mark
class ConfigurationSection(object): class ConfigurationSection(object):
def __init__(self, config, section): def __init__(self, config, section):
super(ConfigurationSection, self).__init__() super(ConfigurationSection, self).__init__()
self.config = config self.config = config
@ -15,19 +17,50 @@ class ConfigurationSection(object):
return '%s.%s' % (section, param) return '%s.%s' % (section, param)
def get(self, name, *args, **kwargs): def get(self, name, *args, **kwargs):
return self.config.get(self._combine_names(self.section, name), *args, **kwargs) return (
self.config.get(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def set(self, name, *args, **kwargs): def set(self, name, *args, **kwargs):
self.config.set(self._combine_names(self.section, name), *args, **kwargs) self.config.set(
self._combine_names(
self.section,
name),
*args,
**kwargs)
def set_default(self, name, *args, **kwargs): def set_default(self, name, *args, **kwargs):
self.config.set_default(self._combine_names(self.section, name), *args, **kwargs) self.config.set_default(
self._combine_names(
self.section,
name),
*args,
**kwargs)
def contains(self, name, *args, **kwargs): def contains(self, name, *args, **kwargs):
return self.config.contains(self._combine_names(self.section, name), *args, **kwargs) return (
self.config.contains(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def is_default(self, name, *args, **kwargs): def is_default(self, name, *args, **kwargs):
return self.config.is_default(self._combine_names(self.section, name), *args, **kwargs) return (
self.config.is_default(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def __getitem__(self, key): def __getitem__(self, key):
return self.config.get(self._combine_names(self.section, key)) return self.config.get(self._combine_names(self.section, key))
@ -44,7 +77,9 @@ class ConfigurationSection(object):
def items(self, *args, **kwargs): def items(self, *args, **kwargs):
return self.config.items(self.section, *args, **kwargs) return self.config.items(self.section, *args, **kwargs)
class ConfigurationWrapper(object): class ConfigurationWrapper(object):
def __init__(self, config, state): def __init__(self, config, state):
super(ConfigurationWrapper, self).__init__() super(ConfigurationWrapper, self).__init__()
self.config = config self.config = config
@ -58,6 +93,7 @@ class ConfigurationWrapper(object):
class Configuration(object): class Configuration(object):
def __init__(self): def __init__(self):
super(Configuration, self).__init__() super(Configuration, self).__init__()
self._defaults = dict() self._defaults = dict()
@ -94,7 +130,9 @@ class Configuration(object):
return value return value
tmpl = string.Template(value) tmpl = string.Template(value)
return tmpl.safe_substitute(ConfigurationWrapper(self, _state + [name])) return (
tmpl.safe_substitute(ConfigurationWrapper(self, _state + [name]))
)
def contains(self, name, ignoreDefault=False): def contains(self, name, ignoreDefault=False):
section, name = self._normalize_name(name) section, name = self._normalize_name(name)
@ -110,7 +148,10 @@ class Configuration(object):
def is_default(self, name): def is_default(self, name):
section, name = self._normalize_name(name) section, name = self._normalize_name(name)
return not (section in self._normal and name in self._normal[section]) and (section in self._defaults and name in self._defaults[section]) return (
not (section in self._normal and name in self._normal[section]) and (
section in self._defaults and name in self._defaults[section])
)
def set_default(self, name, value): def set_default(self, name, value):
section, name = self._normalize_name(name) section, name = self._normalize_name(name)
@ -163,23 +204,35 @@ class Configuration(object):
def items(self, section=None): def items(self, section=None):
if section: if section:
return [(name, self.get(self._combine_names(section, name))) for name in self.keys(section)] return (
[(name, self.get(self._combine_names(section, name)))
for name in self.keys(section)]
)
else: else:
return [(name, ConfigurationSection(self, name)) for name in self.keys()] return (
[(name, ConfigurationSection(self, name))
for name in self.keys()]
)
class Element(object): class Element(object):
def __init__(self, start_mark, end_mark): def __init__(self, start_mark, end_mark):
self.start_mark = start_mark self.start_mark = start_mark
self.end_mark = end_mark self.end_mark = end_mark
def __eq__(self, other): def __eq__(self, other):
return (self.__class__ == other.__class__) and (self.start_mark == other.start_mark) and (self.end_mark == other.end_mark) return (
(self.__class__ == other.__class__) and (
self.start_mark == other.start_mark) and (self.end_mark == other.end_mark)
)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
class ComponentConfig(Element): class ComponentConfig(Element):
def __init__(self, start_mark, end_mark, name, sections=[], errors=[]): def __init__(self, start_mark, end_mark, name, sections=[], errors=[]):
super(ComponentConfig, self).__init__(start_mark, end_mark) super(ComponentConfig, self).__init__(start_mark, end_mark)
self.name = name self.name = name
@ -189,12 +242,16 @@ class ComponentConfig(Element):
self.errors = errors self.errors = errors
class TextElement(Element): class TextElement(Element):
def __init__(self, start_mark, end_mark, text): def __init__(self, start_mark, end_mark, text):
super(TextElement, self).__init__(start_mark, end_mark) super(TextElement, self).__init__(start_mark, end_mark)
self.text = text self.text = text
class ConfigSection(Element): class ConfigSection(Element):
def __init__(self, start_mark, end_mark, name, parameters): def __init__(self, start_mark, end_mark, name, parameters):
super(ConfigSection, self).__init__(start_mark, end_mark) super(ConfigSection, self).__init__(start_mark, end_mark)
self.name = name self.name = name
@ -202,9 +259,13 @@ class ConfigSection(Element):
for parameter in self.parameters: for parameter in self.parameters:
parameter.parent = self parameter.parent = self
class ConfigSectionName(TextElement): pass
class ConfigSectionName(TextElement):
pass
class ConfigParameter(Element): class ConfigParameter(Element):
def __init__(self, start_mark, end_mark, name, value, delimiter): def __init__(self, start_mark, end_mark, name, value, delimiter):
super(ConfigParameter, self).__init__(start_mark, end_mark) super(ConfigParameter, self).__init__(start_mark, end_mark)
self.name = name self.name = name
@ -217,20 +278,30 @@ class ConfigParameter(Element):
self.delimiter.parent = self self.delimiter.parent = self
def __eq__(self, other): def __eq__(self, other):
return (self.name.text == other.name.text) and (self.value.text == other.value.text) return (
(self.name.text == other.name.text) and (
self.value.text == other.value.text)
)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
def __repr__(self): def __repr__(self):
return "<ConfigParameter %s=%s delimiter=%s>" % (self.name.text, self.value.text, self.delimiter.text) return (
"<ConfigParameter %s=%s delimiter=%s>" % (
self.name.text,
self.value.text,
self.delimiter.text)
)
class ConfigParameterName(TextElement): pass class ConfigParameterName(TextElement):
pass
class ConfigParameterValue(TextElement): class ConfigParameterValue(TextElement):
def __init__(self, start_mark, end_mark, text, value=None, quotechar=None): def __init__(self, start_mark, end_mark, text, value=None, quotechar=None):
super(ConfigParameterValue, self).__init__(start_mark, end_mark, text) super(ConfigParameterValue, self).__init__(start_mark, end_mark, text)
self.value = value self.value = value
self.quotechar = quotechar self.quotechar = quotechar

View File

@ -11,9 +11,15 @@ from ostack_validator.model import *
class NodeClient(object): class NodeClient(object):
def __init__(self, node_address, username, private_key_file, ssh_port=22): def __init__(self, node_address, username, private_key_file, ssh_port=22):
super(NodeClient, self).__init__() super(NodeClient, self).__init__()
self.shell = spur.SshShell(hostname=node_address, port=ssh_port, username=username, private_key_file=private_key_file, missing_host_key=spur.ssh.MissingHostKey.accept) self.shell = spur.SshShell(
hostname=node_address,
port=ssh_port,
username=username,
private_key_file=private_key_file,
missing_host_key=spur.ssh.MissingHostKey.accept)
def run(self, command, *args, **kwargs): def run(self, command, *args, **kwargs):
return self.shell.run(command, allow_error=True, *args, **kwargs) return self.shell.run(command, allow_error=True, *args, **kwargs)
@ -24,7 +30,9 @@ class NodeClient(object):
python_re = re.compile('(/?([^/]*/)*)python[0-9.]*') python_re = re.compile('(/?([^/]*/)*)python[0-9.]*')
host_port_re = re.compile('(\d+\.\d+\.\d+\.\d+):(\d+)') host_port_re = re.compile('(\d+\.\d+\.\d+\.\d+):(\d+)')
class OpenstackDiscovery(object): class OpenstackDiscovery(object):
def discover(self, initial_nodes, username, private_key): def discover(self, initial_nodes, username, private_key):
"Takes a list of node addresses and returns discovered openstack installation info" "Takes a list of node addresses and returns discovered openstack installation info"
openstack = Openstack() openstack = Openstack()
@ -44,10 +52,18 @@ class OpenstackDiscovery(object):
else: else:
host = address host = address
port = 22 port = 22
client = NodeClient(host, ssh_port=port, username=username, private_key_file=private_key_file.name) client = NodeClient(
host,
ssh_port=port,
username=username,
private_key_file=private_key_file.name)
client.run(['echo', 'test']) client.run(['echo', 'test'])
except: except:
openstack.report_issue(Issue(Issue.WARNING, "Can't connect to node %s" % address)) openstack.report_issue(
Issue(
Issue.WARNING,
"Can't connect to node %s" %
address))
continue continue
host = self._discover_node(client) host = self._discover_node(client)
@ -58,7 +74,8 @@ class OpenstackDiscovery(object):
openstack.add_host(host) openstack.add_host(host)
if len(openstack.hosts) == 0: if len(openstack.hosts) == 0:
openstack.report_issue(Issue(Issue.FATAL, "No OpenStack nodes were discovered")) openstack.report_issue(
Issue(Issue.FATAL, "No OpenStack nodes were discovered"))
if private_key_file: if private_key_file:
private_key_file.close() private_key_file.close()
@ -86,7 +103,6 @@ class OpenstackDiscovery(object):
return host return host
def _find_process(self, client, name): def _find_process(self, client, name):
processes = self._get_processes(client) processes = self._get_processes(client)
for line in processes: for line in processes:
@ -106,12 +122,15 @@ class OpenstackDiscovery(object):
return None return None
def _find_python_package_version(self, client, package): def _find_python_package_version(self, client, package):
result = client.run(['python', '-c', 'import pkg_resources; version = pkg_resources.get_provider(pkg_resources.Requirement.parse("%s")).version; print(version)' % package]) result = client.run(
['python', '-c', 'import pkg_resources; version = pkg_resources.get_provider(pkg_resources.Requirement.parse("%s")).version; print(version)' %
package])
s = result.output.strip() s = result.output.strip()
parts = [] parts = []
for p in s.split('.'): for p in s.split('.'):
if not p[0].isdigit(): break if not p[0].isdigit():
break
parts.append(p) parts.append(p)
@ -120,7 +139,10 @@ class OpenstackDiscovery(object):
return version return version
def _get_processes(self, client): def _get_processes(self, client):
return [line.split() for line in client.run(['ps', '-Ao', 'cmd', '--no-headers']).output.split("\n")] return (
[line.split()
for line in client.run(['ps', '-Ao', 'cmd', '--no-headers']).output.split("\n")]
)
def _collect_host_id(self, client): def _collect_host_id(self, client):
ether_re = re.compile('link/ether (([0-9a-f]{2}:){5}([0-9a-f]{2})) ') ether_re = re.compile('link/ether (([0-9a-f]{2}:){5}([0-9a-f]{2})) ')
@ -147,7 +169,8 @@ class OpenstackDiscovery(object):
return None return None
line = ls.output.split("\n")[0] line = ls.output.split("\n")[0]
perm, links, owner, group, size, date, time, timezone, name = line.split() perm, links, owner, group, size, date, time, timezone, name = line.split(
)
permissions = self._permissions_string_to_number(perm) permissions = self._permissions_string_to_number(perm)
with client.open(path) as f: with client.open(path) as f:
@ -155,7 +178,6 @@ class OpenstackDiscovery(object):
return FileResource(path, contents, owner, group, permissions) return FileResource(path, contents, owner, group, permissions)
def _get_keystone_db_data(self, client, command, env={}): def _get_keystone_db_data(self, client, command, env={}):
result = client.run(['keystone', command], update_env=env) result = client.run(['keystone', command], update_env=env)
if result.return_code != 0: if result.return_code != 0:
@ -201,7 +223,8 @@ class OpenstackDiscovery(object):
config_path = '/etc/keystone/keystone.conf' config_path = '/etc/keystone/keystone.conf'
keystone = KeystoneComponent() keystone = KeystoneComponent()
keystone.version = self._find_python_package_version(client, 'keystone') keystone.version = self._find_python_package_version(
client, 'keystone')
keystone.config_files = [] keystone.config_files = []
keystone.config_files.append(self._collect_file(client, config_path)) keystone.config_files.append(self._collect_file(client, config_path))
@ -217,10 +240,14 @@ class OpenstackDiscovery(object):
} }
keystone.db = dict() keystone.db = dict()
keystone.db['tenants'] = self._get_keystone_db_data(client, 'tenant-list', env=keystone_env) keystone.db['tenants'] = self._get_keystone_db_data(
keystone.db['users'] = self._get_keystone_db_data(client, 'user-list', env=keystone_env) client, 'tenant-list', env=keystone_env)
keystone.db['services'] = self._get_keystone_db_data(client, 'service-list', env=keystone_env) keystone.db['users'] = self._get_keystone_db_data(
keystone.db['endpoints'] = self._get_keystone_db_data(client, 'endpoint-list', env=keystone_env) client, 'user-list', env=keystone_env)
keystone.db['services'] = self._get_keystone_db_data(
client, 'service-list', env=keystone_env)
keystone.db['endpoints'] = self._get_keystone_db_data(
client, 'endpoint-list', env=keystone_env)
return keystone return keystone
@ -240,8 +267,11 @@ class OpenstackDiscovery(object):
nova_api.config_files = [] nova_api.config_files = []
nova_api.config_files.append(self._collect_file(client, config_path)) nova_api.config_files.append(self._collect_file(client, config_path))
paste_config_path = path_relative_to(nova_api.config['api_paste_config'], os.path.dirname(config_path)) paste_config_path = path_relative_to(
nova_api.paste_config_file = self._collect_file(client, paste_config_path) nova_api.config['api_paste_config'],
os.path.dirname(config_path))
nova_api.paste_config_file = self._collect_file(
client, paste_config_path)
return nova_api return nova_api
@ -257,9 +287,11 @@ class OpenstackDiscovery(object):
config_path = '/etc/nova/nova.conf' config_path = '/etc/nova/nova.conf'
nova_compute = NovaComputeComponent() nova_compute = NovaComputeComponent()
nova_compute.version = self._find_python_package_version(client, 'nova') nova_compute.version = self._find_python_package_version(
client, 'nova')
nova_compute.config_files = [] nova_compute.config_files = []
nova_compute.config_files.append(self._collect_file(client, config_path)) nova_compute.config_files.append(
self._collect_file(client, config_path))
return nova_compute return nova_compute
@ -275,9 +307,11 @@ class OpenstackDiscovery(object):
config_path = '/etc/nova/nova.conf' config_path = '/etc/nova/nova.conf'
nova_scheduler = NovaSchedulerComponent() nova_scheduler = NovaSchedulerComponent()
nova_scheduler.version = self._find_python_package_version(client, 'nova') nova_scheduler.version = self._find_python_package_version(
client, 'nova')
nova_scheduler.config_files = [] nova_scheduler.config_files = []
nova_scheduler.config_files.append(self._collect_file(client, config_path)) nova_scheduler.config_files.append(
self._collect_file(client, config_path))
return nova_scheduler return nova_scheduler
@ -293,7 +327,8 @@ class OpenstackDiscovery(object):
config_path = '/etc/glance/glance-api.conf' config_path = '/etc/glance/glance-api.conf'
glance_api = GlanceApiComponent() glance_api = GlanceApiComponent()
glance_api.version = self._find_python_package_version(client, 'glance') glance_api.version = self._find_python_package_version(
client, 'glance')
glance_api.config_files = [] glance_api.config_files = []
glance_api.config_files.append(self._collect_file(client, config_path)) glance_api.config_files.append(self._collect_file(client, config_path))
@ -311,9 +346,11 @@ class OpenstackDiscovery(object):
config_path = '/etc/glance/glance-registry.conf' config_path = '/etc/glance/glance-registry.conf'
glance_registry = GlanceRegistryComponent() glance_registry = GlanceRegistryComponent()
glance_registry.version = self._find_python_package_version(client, 'glance') glance_registry.version = self._find_python_package_version(
client, 'glance')
glance_registry.config_files = [] glance_registry.config_files = []
glance_registry.config_files.append(self._collect_file(client, config_path)) glance_registry.config_files.append(
self._collect_file(client, config_path))
return glance_registry return glance_registry
@ -329,12 +366,16 @@ class OpenstackDiscovery(object):
config_path = '/etc/cinder/cinder.conf' config_path = '/etc/cinder/cinder.conf'
cinder_api = CinderApiComponent() cinder_api = CinderApiComponent()
cinder_api.version = self._find_python_package_version(client, 'cinder') cinder_api.version = self._find_python_package_version(
client, 'cinder')
cinder_api.config_files = [] cinder_api.config_files = []
cinder_api.config_files.append(self._collect_file(client, config_path)) cinder_api.config_files.append(self._collect_file(client, config_path))
paste_config_path = path_relative_to(cinder_api.config['api_paste_config'], os.path.dirname(config_path)) paste_config_path = path_relative_to(
cinder_api.paste_config_file = self._collect_file(client, paste_config_path) cinder_api.config['api_paste_config'],
os.path.dirname(config_path))
cinder_api.paste_config_file = self._collect_file(
client, paste_config_path)
return cinder_api return cinder_api
@ -350,12 +391,17 @@ class OpenstackDiscovery(object):
config_path = '/etc/cinder/cinder.conf' config_path = '/etc/cinder/cinder.conf'
cinder_volume = CinderVolumeComponent() cinder_volume = CinderVolumeComponent()
cinder_volume.version = self._find_python_package_version(client, 'cinder') cinder_volume.version = self._find_python_package_version(
client, 'cinder')
cinder_volume.config_files = [] cinder_volume.config_files = []
cinder_volume.config_files.append(self._collect_file(client, config_path)) cinder_volume.config_files.append(
self._collect_file(client, config_path))
rootwrap_config_path = path_relative_to(cinder_volume.config['rootwrap_config'], os.path.dirname(config_path)) rootwrap_config_path = path_relative_to(
cinder_volume.rootwrap_config = self._collect_file(client, rootwrap_config_path) cinder_volume.config['rootwrap_config'],
os.path.dirname(config_path))
cinder_volume.rootwrap_config = self._collect_file(
client, rootwrap_config_path)
return cinder_volume return cinder_volume
@ -371,9 +417,11 @@ class OpenstackDiscovery(object):
config_path = '/etc/cinder/cinder.conf' config_path = '/etc/cinder/cinder.conf'
cinder_scheduler = CinderSchedulerComponent() cinder_scheduler = CinderSchedulerComponent()
cinder_scheduler.version = self._find_python_package_version(client, 'cinder') cinder_scheduler.version = self._find_python_package_version(
client, 'cinder')
cinder_scheduler.config_files = [] cinder_scheduler.config_files = []
cinder_scheduler.config_files.append(self._collect_file(client, config_path)) cinder_scheduler.config_files.append(
self._collect_file(client, config_path))
return cinder_scheduler return cinder_scheduler
@ -391,8 +439,10 @@ class OpenstackDiscovery(object):
mysql.version = m.group(1) if m else 'unknown' mysql.version = m.group(1) if m else 'unknown'
mysql.config_files = [] mysql.config_files = []
config_locations_result = client.run(['bash', '-c', 'mysqld --help --verbose | grep "Default options are read from the following files in the given order" -A 1']) config_locations_result = client.run(
config_locations = config_locations_result.output.strip().split("\n")[-1].split() ['bash', '-c', 'mysqld --help --verbose | grep "Default options are read from the following files in the given order" -A 1'])
config_locations = config_locations_result.output.strip().split(
"\n")[-1].split()
for path in config_locations: for path in config_locations:
f = self._collect_file(client, path) f = self._collect_file(client, path)
if f: if f:
@ -412,4 +462,3 @@ class OpenstackDiscovery(object):
rabbitmq.version = 'unknown' rabbitmq.version = 'unknown'
return rabbitmq return rabbitmq

View File

@ -1,4 +1,3 @@
from ostack_validator.inspections.keystone_authtoken import KeystoneAuthtokenSettingsInspection from ostack_validator.inspections.keystone_authtoken import KeystoneAuthtokenSettingsInspection
from ostack_validator.inspections.keystone_endpoints import KeystoneEndpointsInspection from ostack_validator.inspections.keystone_endpoints import KeystoneEndpointsInspection
from ostack_validator.inspections.lettuce_runner import LettuceRunnerInspection from ostack_validator.inspections.lettuce_runner import LettuceRunnerInspection

View File

@ -3,6 +3,7 @@ from ostack_validator.common import Inspection, Issue, find
KEYSTONE_AUTHTOKEN_FILTER_FACTORY = 'keystoneclient.middleware.auth_token:filter_factory' KEYSTONE_AUTHTOKEN_FILTER_FACTORY = 'keystoneclient.middleware.auth_token:filter_factory'
class KeystoneAuthtokenSettingsInspection(Inspection): class KeystoneAuthtokenSettingsInspection(Inspection):
name = 'Keystone auth' name = 'Keystone auth'
description = 'Validate correctness of keystone settings' description = 'Validate correctness of keystone settings'
@ -14,7 +15,8 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
keystones = [c for c in components if c.name == 'keystone'] keystones = [c for c in components if c.name == 'keystone']
if len(keystones) == 0: if len(keystones) == 0:
openstack.report_issue(Issue(Issue.FATAL, 'No keystone service found')) openstack.report_issue(
Issue(Issue.FATAL, 'No keystone service found'))
return return
keystone = keystones[0] keystone = keystones[0]
@ -28,16 +30,22 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
(authtoken_section, _) = find( (authtoken_section, _) = find(
nova.paste_config.items(), nova.paste_config.items(),
lambda (name, values): name.startswith('filter:') and values.get('paste.filter_factory') == KEYSTONE_AUTHTOKEN_FILTER_FACTORY lambda name_values: name_values[0].startswith('filter:') and name_values[
1].get('paste.filter_factory') == KEYSTONE_AUTHTOKEN_FILTER_FACTORY
) )
if not authtoken_section: continue if not authtoken_section:
continue
authtoken_settings = nova.paste_config.section(authtoken_section) authtoken_settings = nova.paste_config.section(authtoken_section)
def get_value(name): def get_value(name):
return authtoken_settings[name] or nova.config['keystone_authtoken.%s' % name] return (
authtoken_settings[
name] or nova.config[
'keystone_authtoken.%s' %
name]
)
auth_host = get_value('auth_host') auth_host = get_value('auth_host')
auth_port = get_value('auth_port') auth_port = get_value('auth_port')
@ -47,37 +55,85 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
admin_tenant_name = get_value('admin_tenant_name') admin_tenant_name = get_value('admin_tenant_name')
admin_token = get_value('admin_token') admin_token = get_value('admin_token')
msg_prefix = 'Service "%s" on host "%s"' % (nova.name, nova.host.name) msg_prefix = 'Service "%s" on host "%s"' % (
nova.name, nova.host.name)
if not auth_host: if not auth_host:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_host" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' miss "auth_host" setting in keystone authtoken config'))
elif not auth_host in keystone_addresses: elif not auth_host in keystone_addresses:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_host" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' has incorrect "auth_host" setting in keystone authtoken config'))
if not auth_port: if not auth_port:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_port" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' miss "auth_port" setting in keystone authtoken config'))
elif auth_port != keystone.config['admin_port']: elif auth_port != keystone.config['admin_port']:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_port" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' has incorrect "auth_port" setting in keystone authtoken config'))
if not auth_protocol: if not auth_protocol:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_protocol" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' miss "auth_protocol" setting in keystone authtoken config'))
elif not auth_protocol in ['http', 'https']: elif not auth_protocol in ['http', 'https']:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_protocol" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' has incorrect "auth_protocol" setting in keystone authtoken config'))
if not admin_user: if not admin_user:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "admin_user" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' miss "admin_user" setting in keystone authtoken config'))
else: else:
user = find(keystone.db['users'], lambda u: u['name'] == admin_user) user = find(
keystone.db['users'],
lambda u: u['name'] == admin_user)
if not user: if not user:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has "admin_user" that is missing in Keystone catalog')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' has "admin_user" that is missing in Keystone catalog'))
if not admin_tenant_name: if not admin_tenant_name:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "admin_tenant_name" setting in keystone authtoken config')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' miss "admin_tenant_name" setting in keystone authtoken config'))
else: else:
tenant = find(keystone.db['tenants'], lambda t: t['name'] == admin_tenant_name) tenant = find(
keystone.db['tenants'],
lambda t: t['name'] == admin_tenant_name)
if not tenant: if not tenant:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has "admin_tenant_name" that is missing in Keystone catalog')) nova.report_issue(
Issue(
Issue.ERROR,
msg_prefix +
' has "admin_tenant_name" that is missing in Keystone catalog'))
if admin_token: if admin_token:
nova.report_issue(Issue(Issue.WARNING, msg_prefix + ' uses insecure admin_token for authentication')) nova.report_issue(
Issue(
Issue.WARNING,
msg_prefix +
' uses insecure admin_token for authentication'))

View File

@ -2,6 +2,7 @@ from urlparse import urlparse
from ostack_validator.common import Inspection, Issue, find from ostack_validator.common import Inspection, Issue, find
class KeystoneEndpointsInspection(Inspection): class KeystoneEndpointsInspection(Inspection):
name = 'Keystone endpoints' name = 'Keystone endpoints'
description = 'Validate that each keystone endpoint leads to proper service' description = 'Validate that each keystone endpoint leads to proper service'
@ -13,29 +14,41 @@ class KeystoneEndpointsInspection(Inspection):
for service in keystone.db['services']: for service in keystone.db['services']:
if service['type'] == 'compute': if service['type'] == 'compute':
endpoint = find(keystone.db['endpoints'], lambda e: e['service_id'] == service['id']) endpoint = find(
keystone.db['endpoints'],
lambda e: e['service_id'] == service['id'])
if not endpoint: if not endpoint:
keystone.report_issue(Issue(Issue.WARNING, 'Keystone catalog contains service "%s" that has no defined endpoints' % service['name'])) keystone.report_issue(
Issue(
Issue.WARNING, 'Keystone catalog contains service "%s" that has no defined endpoints' %
service['name']))
continue continue
for url_attr in ['adminurl', 'publicurl', 'internalurl']: for url_attr in ['adminurl', 'publicurl', 'internalurl']:
url = urlparse(endpoint[url_attr]) url = urlparse(endpoint[url_attr])
# TODO: resolve endpoint url host address # TODO: resolve endpoint url host address
host = find(openstack.hosts, lambda h: url.hostname in h.network_addresses) host = find(
openstack.hosts,
lambda h: url.hostname in h.network_addresses)
if not host: if not host:
keystone.report_issue(Issue(Issue.ERROR, 'Keystone catalog has endpoint for service "%s" (id %s) that has "%s" set pointing to unknown host' % (service['name'], service['id'], url_attr))) keystone.report_issue(
Issue(
Issue.ERROR, 'Keystone catalog has endpoint for service "%s" (id %s) that has "%s" set pointing to unknown host' %
(service['name'], service['id'], url_attr)))
continue continue
nova_compute = None nova_compute = None
for c in host.components: for c in host.components:
if c.name != 'nova-compute': continue if c.name != 'nova-compute':
continue
if c.config['osapi_compute_listen'] in ['0.0.0.0', url.hostname] and c.config['osapi_compute_listen_port'] == url.port: if c.config['osapi_compute_listen'] in ['0.0.0.0', url.hostname] and c.config['osapi_compute_listen_port'] == url.port:
nova_compute = c nova_compute = c
break break
if not nova_compute: if not nova_compute:
keystone.report_issue(Issue(Issue.ERROR, 'Keystone catalog has endpoint for service "%s" (id %s) that has "%s" set pointing to no service' % (service['name'], service['id'], url_attr))) keystone.report_issue(
Issue(
Issue.ERROR, 'Keystone catalog has endpoint for service "%s" (id %s) that has "%s" set pointing to no service' %
(service['name'], service['id'], url_attr)))

View File

@ -4,18 +4,21 @@ from lettuce import *
from ostack_validator.common import Issue, Version, find from ostack_validator.common import Issue, Version, find
from ostack_validator.model import * from ostack_validator.model import *
def get_variable(name): def get_variable(name):
if not hasattr(world, 'variables'): if not hasattr(world, 'variables'):
return None return None
return world.variables.get(name) return world.variables.get(name)
def set_variable(name, value): def set_variable(name, value):
if not hasattr(world, 'variables'): if not hasattr(world, 'variables'):
world.variables = {} world.variables = {}
world.variables[name] = value world.variables[name] = value
def subst(template): def subst(template):
if not hasattr(world, 'variables'): if not hasattr(world, 'variables'):
return template return template
@ -23,39 +26,49 @@ def subst(template):
tmpl = string.Template(template) tmpl = string.Template(template)
return tmpl.safe_substitute(world.variables) return tmpl.safe_substitute(world.variables)
def stop(): def stop():
assert False, "stop" assert False, "stop"
# Openstack general step description section # Openstack general step description section
@step(r'I use OpenStack (\w+)') @step(r'I use OpenStack (\w+)')
def use_openstack_version(step, version): def use_openstack_version(step, version):
version = Version(version) version = Version(version)
for component in [c for c in world.openstack.components if isinstance(c, OpenstackComponent)]: for component in [c for c in world.openstack.components if isinstance(c, OpenstackComponent)]:
if not Version(component.version) >= version: stop() if not Version(component.version) >= version:
stop()
@step(r'Controller addresses are @(\w+)') @step(r'Controller addresses are @(\w+)')
def controller_addresses(self, variable): def controller_addresses(self, variable):
controller = find(world.openstack.components, lambda c: c.name == 'nova') controller = find(world.openstack.components, lambda c: c.name == 'nova')
if controller.config['s3_host'] == '0.0.0.0': if controller.config['s3_host'] == '0.0.0.0':
addresses = filter(lambda ip: not ip.startswith('127.'), controller.host.network_addresses) addresses = filter(
lambda ip: not ip.startswith('127.'),
controller.host.network_addresses)
else: else:
addresses = [controller.config['s3_host']] addresses = [controller.config['s3_host']]
set_variable(variable, addresses) set_variable(variable, addresses)
# Keystone steps section # Keystone steps section
@step(r'Keystone addresses are @(\w+)') @step(r'Keystone addresses are @(\w+)')
def keystone_addresses(self, variable): def keystone_addresses(self, variable):
keystone = find(world.openstack.components, lambda c: c.name == 'keystone') keystone = find(world.openstack.components, lambda c: c.name == 'keystone')
if keystone.config['bind_host'] == '0.0.0.0': if keystone.config['bind_host'] == '0.0.0.0':
addresses = filter(lambda ip: not ip.startswith('127.'), keystone.host.network_addresses) addresses = filter(
lambda ip: not ip.startswith('127.'),
keystone.host.network_addresses)
else: else:
addresses = [keystone.config['bind_host']] addresses = [keystone.config['bind_host']]
set_variable(variable, addresses) set_variable(variable, addresses)
# Nova steps section # Nova steps section
@step(r'Nova has "(.+)" equal to "(.*)"') @step(r'Nova has "(.+)" equal to "(.*)"')
def nova_has_property(step, name, value): def nova_has_property(step, name, value):
@ -63,7 +76,9 @@ def nova_has_property(step, name, value):
value = subst(value) value = subst(value)
for nova in [c for c in world.openstack.components if c.name.startswith('nova')]: for nova in [c for c in world.openstack.components if c.name.startswith('nova')]:
if not nova.config[name] == value: stop() if not nova.config[name] == value:
stop()
@step(r'Nova should have "(.+)" in "(.*)"') @step(r'Nova should have "(.+)" in "(.*)"')
def nova_property_assertion(self, name, values): def nova_property_assertion(self, name, values):
@ -77,4 +92,6 @@ def nova_property_assertion(self, name, values):
nova_value = nova.config[name] nova_value = nova.config[name]
if not (nova_value and nova_value in values): if not (nova_value and nova_value in values):
nova.report_issue(Issue(Issue.ERROR, 'Nova should have "%s" in %s' % (name, values))) nova.report_issue(
Issue(Issue.ERROR, 'Nova should have "%s" in %s' %
(name, values)))

View File

@ -3,7 +3,9 @@ import lettuce
from ostack_validator.common import Inspection, Issue from ostack_validator.common import Inspection, Issue
class LettuceRunnerInspection(Inspection): class LettuceRunnerInspection(Inspection):
def inspect(self, openstack): def inspect(self, openstack):
runner = lettuce.Runner( runner = lettuce.Runner(
base_path=os.path.join(os.path.dirname(__file__), 'lettuce') base_path=os.path.join(os.path.dirname(__file__), 'lettuce')
@ -16,5 +18,8 @@ class LettuceRunnerInspection(Inspection):
for feature_result in result.feature_results: for feature_result in result.feature_results:
for scenario_result in [s for s in feature_result.scenario_results if not s.passed]: for scenario_result in [s for s in feature_result.scenario_results if not s.passed]:
for step in scenario_result.steps_undefined: for step in scenario_result.steps_undefined:
openstack.report_issue(Issue(Issue.ERROR, 'Undefined step "%s"' % step.sentence)) openstack.report_issue(
Issue(
Issue.ERROR,
'Undefined step "%s"' %
step.sentence))

View File

@ -5,9 +5,14 @@ import argparse
from ostack_validator.model_parser import ModelParser from ostack_validator.model_parser import ModelParser
from ostack_validator.inspection import MainConfigValidationInspection from ostack_validator.inspection import MainConfigValidationInspection
def main(args): def main(args):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-d', '--debug', help='set debug log level', action='store_true') parser.add_argument(
'-d',
'--debug',
help='set debug log level',
action='store_true')
parser.add_argument('path', help='Path to config snapshot') parser.add_argument('path', help='Path to config snapshot')
args = parser.parse_args(args) args = parser.parse_args(args)
@ -39,4 +44,3 @@ def main(args):
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) main(sys.argv[1:])

View File

@ -10,7 +10,9 @@ import ostack_validator.schemas
from ostack_validator.config_formats import IniConfigParser from ostack_validator.config_formats import IniConfigParser
from ostack_validator.utils import memoized from ostack_validator.utils import memoized
class IssueReporter(object): class IssueReporter(object):
def __init__(self): def __init__(self):
super(IssueReporter, self).__init__() super(IssueReporter, self).__init__()
self.issues = [] self.issues = []
@ -23,13 +25,16 @@ class IssueReporter(object):
def all_issues(self): def all_issues(self):
return list(self.issues) return list(self.issues)
class Openstack(IssueReporter): class Openstack(IssueReporter):
def __init__(self): def __init__(self):
super(Openstack, self).__init__() super(Openstack, self).__init__()
self.hosts = [] self.hosts = []
def add_host(self, host): def add_host(self, host):
if not host: return if not host:
return
self.hosts.append(host) self.hosts.append(host)
host.parent = self host.parent = self
@ -53,6 +58,7 @@ class Openstack(IssueReporter):
class Host(IssueReporter): class Host(IssueReporter):
def __init__(self, name): def __init__(self, name):
super(Host, self).__init__() super(Host, self).__init__()
self.name = name self.name = name
@ -62,7 +68,8 @@ class Host(IssueReporter):
return 'Host "%s"' % self.name return 'Host "%s"' % self.name
def add_component(self, component): def add_component(self, component):
if not component: return if not component:
return
self.components.append(component) self.components.append(component)
component.parent = self component.parent = self
@ -82,6 +89,7 @@ class Host(IssueReporter):
class Service(IssueReporter): class Service(IssueReporter):
def __init__(self): def __init__(self):
super(Service, self).__init__() super(Service, self).__init__()
self.issues = [] self.issues = []
@ -102,7 +110,8 @@ class Service(IssueReporter):
result = super(Service, self).all_issues result = super(Service, self).all_issues
if hasattr(self, 'config_files') and self.config_files: if hasattr(self, 'config_files') and self.config_files:
[result.extend(config_file.all_issues) for config_file in self.config_files] [result.extend(config_file.all_issues)
for config_file in self.config_files]
return result return result
@ -119,7 +128,9 @@ class OpenstackComponent(Service):
def config(self): def config(self):
schema = ConfigSchemaRegistry.get_schema(self.component, self.version) schema = ConfigSchemaRegistry.get_schema(self.component, self.version)
if not schema: if not schema:
self.logger.debug('No schema for component "%s" main config version %s. Skipping it' % (self.component, self.version)) self.logger.debug(
'No schema for component "%s" main config version %s. Skipping it' %
(self.component, self.version))
return None return None
return self._parse_config_resources(self.config_files, schema) return self._parse_config_resources(self.config_files, schema)
@ -133,19 +144,33 @@ class OpenstackComponent(Service):
if parameter.section == 'DEFAULT': if parameter.section == 'DEFAULT':
config.set_default(parameter.name, parameter.default) config.set_default(parameter.name, parameter.default)
else: else:
config.set_default('%s.%s' % (parameter.section, parameter.name), parameter.default) config.set_default(
'%s.%s' %
(parameter.section, parameter.name), parameter.default)
for resource in reversed(resources): for resource in reversed(resources):
self._parse_config_file(Mark(resource.path), resource.contents, config, schema, issue_reporter=resource) self._parse_config_file(
Mark(resource.path),
resource.contents,
config,
schema,
issue_reporter=resource)
return config return config
def _parse_config_file(self, base_mark, config_contents, config=Configuration(), schema=None, issue_reporter=None): def _parse_config_file(
self,
base_mark,
config_contents,
config=Configuration(),
schema=None,
issue_reporter=None):
if issue_reporter: if issue_reporter:
def report_issue(issue): def report_issue(issue):
issue_reporter.report_issue(issue) issue_reporter.report_issue(issue)
else: else:
def report_issue(issue): pass def report_issue(issue):
pass
# Parse config file # Parse config file
config_parser = IniConfigParser() config_parser = IniConfigParser()
@ -155,13 +180,21 @@ class OpenstackComponent(Service):
# Validate config parameters and store them # Validate config parameters and store them
section_name_text_f = lambda s: s.name.text section_name_text_f = lambda s: s.name.text
sections_by_name = groupby(sorted(parsed_config.sections, key=section_name_text_f), key=section_name_text_f) sections_by_name = groupby(
sorted(
parsed_config.sections,
key=section_name_text_f),
key=section_name_text_f)
for section_name, sections in sections_by_name: for section_name, sections in sections_by_name:
sections = list(sections) sections = list(sections)
if len(sections) > 1: if len(sections) > 1:
report_issue(Issue(Issue.INFO, 'Section "%s" appears multiple times' % section_name)) report_issue(
Issue(
Issue.INFO,
'Section "%s" appears multiple times' %
section_name))
seen_parameters = set() seen_parameters = set()
@ -171,35 +204,52 @@ class OpenstackComponent(Service):
unknown_section = not schema.has_section(section.name.text) unknown_section = not schema.has_section(section.name.text)
if unknown_section: if unknown_section:
report_issue(MarkedIssue(Issue.WARNING, 'Unknown section "%s"' % (section_name), section.start_mark)) report_issue(
MarkedIssue(Issue.WARNING, 'Unknown section "%s"' %
(section_name), section.start_mark))
continue continue
for parameter in section.parameters: for parameter in section.parameters:
parameter_schema = None parameter_schema = None
if schema: if schema:
parameter_schema = schema.get_parameter(name=parameter.name.text, section=section.name.text) parameter_schema = schema.get_parameter(
name=parameter.name.text,
section=section.name.text)
if not (parameter_schema or unknown_section): if not (parameter_schema or unknown_section):
report_issue(MarkedIssue(Issue.WARNING, 'Unknown parameter: section "%s" name "%s"' % (section_name, parameter.name.text), parameter.start_mark)) report_issue(
MarkedIssue(
Issue.WARNING, 'Unknown parameter: section "%s" name "%s"' %
(section_name, parameter.name.text), parameter.start_mark))
continue continue
if parameter.name.text in seen_parameters: if parameter.name.text in seen_parameters:
report_issue(MarkedIssue(Issue.WARNING, 'Parameter "%s" in section "%s" redeclared' % (parameter.name.text, section_name), parameter.start_mark)) report_issue(
MarkedIssue(
Issue.WARNING, 'Parameter "%s" in section "%s" redeclared' %
(parameter.name.text, section_name), parameter.start_mark))
else: else:
seen_parameters.add(parameter.name.text) seen_parameters.add(parameter.name.text)
parameter_fullname = parameter.name.text parameter_fullname = parameter.name.text
if section_name != 'DEFAULT': if section_name != 'DEFAULT':
parameter_fullname = section_name + '.' + parameter_fullname parameter_fullname = section_name + \
'.' + parameter_fullname
if parameter_schema: if parameter_schema:
type_validator = TypeValidatorRegistry.get_validator(parameter_schema.type) type_validator = TypeValidatorRegistry.get_validator(
type_validation_result = type_validator.validate(parameter.value.text) parameter_schema.type)
type_validation_result = type_validator.validate(
parameter.value.text)
if isinstance(type_validation_result, Issue): if isinstance(type_validation_result, Issue):
type_validation_result.mark = parameter.value.start_mark.merge(type_validation_result.mark) type_validation_result.mark = parameter.value.start_mark.merge(
type_validation_result.message = 'Property "%s" in section "%s": %s' % (parameter.name.text, section_name, type_validation_result.message) type_validation_result.mark)
type_validation_result.message = 'Property "%s" in section "%s": %s' % (
parameter.name.text, section_name, type_validation_result.message)
report_issue(type_validation_result) report_issue(type_validation_result)
config.set(parameter_fullname, parameter.value.text) config.set(
parameter_fullname,
parameter.value.text)
else: else:
value = type_validation_result value = type_validation_result
@ -208,7 +258,10 @@ class OpenstackComponent(Service):
# if value == parameter_schema.default: # if value == parameter_schema.default:
# report_issue(MarkedIssue(Issue.INFO, 'Explicit value equals default: section "%s" parameter "%s"' % (section_name, parameter.name.text), parameter.start_mark)) # report_issue(MarkedIssue(Issue.INFO, 'Explicit value equals default: section "%s" parameter "%s"' % (section_name, parameter.name.text), parameter.start_mark))
if parameter_schema.deprecation_message: if parameter_schema.deprecation_message:
report_issue(MarkedIssue(Issue.WARNING, 'Deprecated parameter: section "%s" name "%s". %s' % (section_name, parameter.name.text, parameter_schema.deprecation_message), parameter.start_mark)) report_issue(
MarkedIssue(
Issue.WARNING, 'Deprecated parameter: section "%s" name "%s". %s' %
(section_name, parameter.name.text, parameter_schema.deprecation_message), parameter.start_mark))
else: else:
config.set(parameter_fullname, parameter.value.text) config.set(parameter_fullname, parameter.value.text)
@ -219,14 +272,17 @@ class KeystoneComponent(OpenstackComponent):
component = 'keystone' component = 'keystone'
name = 'keystone' name = 'keystone'
class GlanceApiComponent(OpenstackComponent): class GlanceApiComponent(OpenstackComponent):
component = 'glance' component = 'glance'
name = 'glance-api' name = 'glance-api'
class GlanceRegistryComponent(OpenstackComponent): class GlanceRegistryComponent(OpenstackComponent):
component = 'glance' component = 'glance'
name = 'glance-registry' name = 'glance-registry'
class NovaApiComponent(OpenstackComponent): class NovaApiComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-api' name = 'nova-api'
@ -245,34 +301,43 @@ class NovaApiComponent(OpenstackComponent):
return result return result
class NovaComputeComponent(OpenstackComponent): class NovaComputeComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-compute' name = 'nova-compute'
class NovaSchedulerComponent(OpenstackComponent): class NovaSchedulerComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-scheduler' name = 'nova-scheduler'
class CinderApiComponent(OpenstackComponent): class CinderApiComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-api' name = 'cinder-api'
class CinderVolumeComponent(OpenstackComponent): class CinderVolumeComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-volume' name = 'cinder-volume'
class CinderSchedulerComponent(OpenstackComponent): class CinderSchedulerComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-scheduler' name = 'cinder-scheduler'
class MysqlComponent(Service): class MysqlComponent(Service):
component = 'mysql' component = 'mysql'
name = 'mysql' name = 'mysql'
class RabbitMqComponent(Service): class RabbitMqComponent(Service):
name = 'rabbitmq' name = 'rabbitmq'
class FileResource(IssueReporter): class FileResource(IssueReporter):
def __init__(self, path, contents, owner, group, permissions): def __init__(self, path, contents, owner, group, permissions):
super(FileResource, self).__init__() super(FileResource, self).__init__()
self.path = path self.path = path
@ -283,4 +348,3 @@ class FileResource(IssueReporter):
def __str__(self): def __str__(self):
return 'File "%s"' % self.path return 'File "%s"' % self.path

View File

@ -4,6 +4,7 @@ import json
from ostack_validator.common import Error, Version from ostack_validator.common import Error, Version
class Resource(object): class Resource(object):
HOST = 'host' HOST = 'host'
FILE = 'file' FILE = 'file'
@ -21,29 +22,43 @@ class Resource(object):
return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name) return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name)
def get_contents(self): def get_contents(self):
raise Error, 'Not implemented' raise Error('Not implemented')
class ResourceLocator(object): class ResourceLocator(object):
def find_resource(self, resource_type, name, host=None, **kwargs): def find_resource(self, resource_type, name, host=None, **kwargs):
return None return None
class HostResource(Resource): class HostResource(Resource):
def __init__(self, name, resource_locator, interfaces=[]): def __init__(self, name, resource_locator, interfaces=[]):
super(HostResource, self).__init__(name) super(HostResource, self).__init__(name)
self.resource_locator = resource_locator self.resource_locator = resource_locator
self.interfaces = interfaces self.interfaces = interfaces
def find_resource(self, resource_type, name=None, **kwargs): def find_resource(self, resource_type, name=None, **kwargs):
return self.resource_locator.find_resource(resource_type, name, host=self, **kwargs) return (
self.resource_locator.find_resource(
resource_type,
name,
host=self,
**kwargs)
)
class DirectoryResource(Resource): class DirectoryResource(Resource):
def __init__(self, name, owner=None, group=None, permissions=None): def __init__(self, name, owner=None, group=None, permissions=None):
super(DirectoryResource, self).__init__(name) super(DirectoryResource, self).__init__(name)
self.owner = owner self.owner = owner
self.group = group self.group = group
self.permissions = permissions self.permissions = permissions
class FileResource(Resource): class FileResource(Resource):
def __init__(self, name, path, owner=None, group=None, permissions=None): def __init__(self, name, path, owner=None, group=None, permissions=None):
super(FileResource, self).__init__(name) super(FileResource, self).__init__(name)
self.path = path self.path = path
@ -55,7 +70,9 @@ class FileResource(Resource):
with open(self.path) as f: with open(self.path) as f:
return f.read() return f.read()
class ServiceResource(Resource): class ServiceResource(Resource):
def __init__(self, name, version, metadata={}): def __init__(self, name, version, metadata={}):
super(ServiceResource, self).__init__(name) super(ServiceResource, self).__init__(name)
self.version = Version(version) self.version = Version(version)
@ -63,6 +80,7 @@ class ServiceResource(Resource):
class FilesystemSnapshot(object): class FilesystemSnapshot(object):
def __init__(self, path): def __init__(self, path):
super(FilesystemSnapshot, self).__init__() super(FilesystemSnapshot, self).__init__()
self.path = path self.path = path
@ -77,28 +95,46 @@ class FilesystemSnapshot(object):
def _parse_snapshot(self): def _parse_snapshot(self):
self._resources = {} self._resources = {}
if not os.path.isfile(self.path): return if not os.path.isfile(self.path):
return
with open(self.path) as f: with open(self.path) as f:
for line in f.readlines(): for line in f.readlines():
line = line.lstrip() line = line.lstrip()
if line == '' or line.startswith('#'): continue if line == '' or line.startswith('#'):
continue
resource_type = line.split('|')[0] resource_type = line.split('|')[0]
if resource_type == 'dir': if resource_type == 'dir':
source_path, owner, group, permissions = line.split('|')[1:] source_path, owner, group, permissions = line.split(
self._resources[source_path] = DirectoryResource(source_path, owner=owner, group=group, permissions=permissions) '|')[1:]
self._resources[source_path] = DirectoryResource(
source_path,
owner=owner,
group=group,
permissions=permissions)
elif resource_type == 'file': elif resource_type == 'file':
source_path, local_path, owner, group, permissions = line.split('|')[1:] source_path, local_path, owner, group, permissions = line.split(
self._resources[source_path] = FileResource(os.path.basename(source_path), path=os.path.join(self.basedir, local_path), owner=owner, group=group, permissions=permissions) '|')[1:]
self._resources[source_path] = FileResource(
os.path.basename(source_path),
path=os.path.join(self.basedir,
local_path),
owner=owner,
group=group,
permissions=permissions)
else: else:
self.logger.warn('Unknown resource "%s" in line "%s"' % (resource_type, line)) self.logger.warn(
'Unknown resource "%s" in line "%s"' %
(resource_type, line))
class ConfigSnapshotResourceLocator(object): class ConfigSnapshotResourceLocator(object):
def __init__(self, basedir): def __init__(self, basedir):
super(ConfigSnapshotResourceLocator, self).__init__() super(ConfigSnapshotResourceLocator, self).__init__()
self.basedir = basedir self.basedir = basedir
if not os.path.isdir(self.basedir): if not os.path.isdir(self.basedir):
raise Error, 'Invalid argument: base directory does not exist' raise Error('Invalid argument: base directory does not exist')
self._services = None self._services = None
self._filesystem_snapshots = {} self._filesystem_snapshots = {}
@ -110,10 +146,13 @@ class ConfigSnapshotResourceLocator(object):
return None return None
return HostResource(name, self) return HostResource(name, self)
else: else:
return [HostResource(os.path.basename(host_path), self) for host_path in glob.glob(os.path.join(self.basedir, '*')) if os.path.isdir(host_path)] return (
[HostResource(os.path.basename(host_path), self)
for host_path in glob.glob(os.path.join(self.basedir, '*')) if os.path.isdir(host_path)]
)
if resource_type == Resource.FILE: if resource_type == Resource.FILE:
if not host: if not host:
raise Error, 'Invalid argument: "host" need to be specified' raise Error('Invalid argument: "host" need to be specified')
if isinstance(host, HostResource): if isinstance(host, HostResource):
host = host.name host = host.name
@ -124,7 +163,7 @@ class ConfigSnapshotResourceLocator(object):
return [] return []
elif resource_type == Resource.SERVICE: elif resource_type == Resource.SERVICE:
if not host: if not host:
raise Error, 'Invalid argument: "host" need to be specified' raise Error('Invalid argument: "host" need to be specified')
if isinstance(host, HostResource): if isinstance(host, HostResource):
host = host.name host = host.name
@ -142,24 +181,28 @@ class ConfigSnapshotResourceLocator(object):
return None return None
def _ensure_services_loaded(self): def _ensure_services_loaded(self):
if self._services: return if self._services:
return
self._services = {} self._services = {}
for host_path in glob.glob(os.path.join(self.basedir, '*')): for host_path in glob.glob(os.path.join(self.basedir, '*')):
if not os.path.isdir(host_path): continue if not os.path.isdir(host_path):
continue
services_json_path = os.path.join(host_path, 'services.json') services_json_path = os.path.join(host_path, 'services.json')
if not os.path.isfile(services_json_path): continue if not os.path.isfile(services_json_path):
continue
host_name = os.path.basename(host_path) host_name = os.path.basename(host_path)
self._services[host_name] = {} self._services[host_name] = {}
with open(services_json_path) as f: with open(services_json_path) as f:
for service_name, metadata in json.loads(f.read()).items(): for service_name, metadata in json.loads(f.read()).items():
version = metadata.pop('version') version = metadata.pop('version')
self._services[host_name][service_name] = ServiceResource(service_name, str(version), metadata) self._services[host_name][service_name] = ServiceResource(
service_name, str(version), metadata)
def _get_filesystem_snapshot(self, host): def _get_filesystem_snapshot(self, host):
if not host in self._filesystem_snapshots: if not host in self._filesystem_snapshots:
self._filesystem_snapshots[host] = FilesystemSnapshot(os.path.join(self.basedir, host, 'filesystem')) self._filesystem_snapshots[host] = FilesystemSnapshot(
os.path.join(self.basedir, host, 'filesystem'))
return self._filesystem_snapshots[host] return self._filesystem_snapshots[host]

View File

@ -2,21 +2,30 @@ import sys
from ostack_validator.common import Issue, MarkedIssue, Mark, Version, find, index from ostack_validator.common import Issue, MarkedIssue, Mark, Version, find, index
class SchemaUpdateRecord(object): class SchemaUpdateRecord(object):
# checkpoint's data is version number # checkpoint's data is version number
def __init__(self, version, operation, data=None): def __init__(self, version, operation, data=None):
super(SchemaUpdateRecord, self).__init__() super(SchemaUpdateRecord, self).__init__()
if not operation in ['checkpoint', 'add', 'remove']: if not operation in ['checkpoint', 'add', 'remove']:
raise Error, 'Unknown operation "%s"' % operation raise Error('Unknown operation "%s"' % operation)
version = Version(version) version = Version(version)
self.version = version self.version = version
self.operation = operation self.operation = operation
self.data = data self.data = data
def __repr__(self): def __repr__(self):
return '<SchemaUpdateRecord %s %s %s' % (self.version, self.operation, self.data) return (
'<SchemaUpdateRecord %s %s %s' % (
self.version,
self.operation,
self.data)
)
class SchemaBuilder(object): class SchemaBuilder(object):
def __init__(self, name, data): def __init__(self, name, data):
super(SchemaBuilder, self).__init__() super(SchemaBuilder, self).__init__()
self.name = name self.name = name
@ -29,7 +38,9 @@ class SchemaBuilder(object):
def __del__(self): def __del__(self):
if len(self.adds) > 0 or len(self.removals) > 0: if len(self.adds) > 0 or len(self.removals) > 0:
sys.stderr.write("WARNING: Uncommitted config schema \"%s\" version %s\n" % (self.name, self.current_version)) sys.stderr.write(
"WARNING: Uncommitted config schema \"%s\" version %s\n" %
(self.name, self.current_version))
def version(self, version, checkpoint=False): def version(self, version, checkpoint=False):
version = Version(version) version = Version(version)
@ -63,18 +74,29 @@ class SchemaBuilder(object):
self._ensure_version() self._ensure_version()
if len(self.removals) > 0: if len(self.removals) > 0:
self.data.append(SchemaUpdateRecord(self.current_version, 'remove', self.removals)) self.data.append(
SchemaUpdateRecord(
self.current_version,
'remove',
self.removals))
self.removals = [] self.removals = []
if len(self.adds) > 0: if len(self.adds) > 0:
self.data.append(SchemaUpdateRecord(self.current_version, 'add', self.adds)) self.data.append(
SchemaUpdateRecord(
self.current_version,
'add',
self.adds))
self.adds = [] self.adds = []
def _ensure_version(self): def _ensure_version(self):
if not self.current_version: if not self.current_version:
raise Error, 'Schema version is not specified. Please call version() method first' raise Error(
'Schema version is not specified. Please call version() method first')
class ConfigSchemaRegistry: class ConfigSchemaRegistry:
__schemas = {} __schemas = {}
@classmethod @classmethod
def register_schema(self, project, configname=None): def register_schema(self, project, configname=None):
if not configname: if not configname:
@ -96,7 +118,8 @@ class ConfigSchemaRegistry:
records = self.__schemas[fullname] records = self.__schemas[fullname]
i = len(records) - 1 i = len(records) - 1
# Find latest checkpoint prior given version # Find latest checkpoint prior given version
while i>=0 and not (records[i].operation == 'checkpoint' and records[i].version <= version): i-=1 while i >= 0 and not (records[i].operation == 'checkpoint' and records[i].version <= version):
i -= 1
if i < 0: if i < 0:
return None return None
@ -110,7 +133,9 @@ class ConfigSchemaRegistry:
if records[i].operation == 'add': if records[i].operation == 'add':
for param in records[i].data: for param in records[i].data:
if param.name in seen_parameters: if param.name in seen_parameters:
old_param_index = index(parameters, lambda p: p.name == param.name) old_param_index = index(
parameters,
lambda p: p.name == param.name)
if old_param_index != -1: if old_param_index != -1:
parameters[old_param_index] = param parameters[old_param_index] = param
else: else:
@ -118,7 +143,9 @@ class ConfigSchemaRegistry:
seen_parameters.add(param.name) seen_parameters.add(param.name)
elif records[i].operation == 'remove': elif records[i].operation == 'remove':
for param_name in records[i].data: for param_name in records[i].data:
param_index = index(parameters, lambda p: p.name == param_name) param_index = index(
parameters,
lambda p: p.name == param_name)
if index != -1: if index != -1:
parameters.pop(param_index) parameters.pop(param_index)
seen_parameters.remove(param_name) seen_parameters.remove(param_name)
@ -128,6 +155,7 @@ class ConfigSchemaRegistry:
class ConfigSchema: class ConfigSchema:
def __init__(self, name, version, format, parameters): def __init__(self, name, version, format, parameters):
self.name = name self.name = name
self.version = Version(version) self.version = Version(version)
@ -135,17 +163,36 @@ class ConfigSchema:
self.parameters = parameters self.parameters = parameters
def has_section(self, section): def has_section(self, section):
return find(self.parameters, lambda p: p.section == section) != None return (
find(self.parameters, lambda p: p.section == section) is not None
)
def get_parameter(self, name, section=None): def get_parameter(self, name, section=None):
# TODO: optimize this # TODO: optimize this
return find(self.parameters, lambda p: p.name == name and p.section == section) return (
find(
self.parameters,
lambda p: p.name == name and p.section == section)
)
def __repr__(self): def __repr__(self):
return '<ConfigSchema name=%s version=%s format=%s parameters=%s>' % (self.name, self.version, self.format, self.parameters) return (
'<ConfigSchema name=%s version=%s format=%s parameters=%s>' % (
self.name, self.version, self.format, self.parameters)
)
class ConfigParameterSchema: class ConfigParameterSchema:
def __init__(self, name, type, section=None, description=None, default=None, required=False, deprecation_message=None):
def __init__(
self,
name,
type,
section=None,
description=None,
default=None,
required=False,
deprecation_message=None):
self.section = section self.section = section
self.name = name self.name = name
self.type = type self.type = type
@ -155,11 +202,15 @@ class ConfigParameterSchema:
self.deprecation_message = deprecation_message self.deprecation_message = deprecation_message
def __repr__(self): def __repr__(self):
return '<ConfigParameterSchema %s>' % ' '.join(['%s=%s' % (attr, getattr(self, attr)) for attr in ['section', 'name', 'type', 'description', 'default', 'required']]) return (
'<ConfigParameterSchema %s>' % ' '.join(['%s=%s' % (attr, getattr(self, attr))
for attr in ['section', 'name', 'type', 'description', 'default', 'required']])
)
class TypeValidatorRegistry: class TypeValidatorRegistry:
__validators = {} __validators = {}
@classmethod @classmethod
def register_validator(self, type_name, type_validator): def register_validator(self, type_name, type_validator):
self.__validators[type_name] = type_validator self.__validators[type_name] = type_validator
@ -170,14 +221,25 @@ class TypeValidatorRegistry:
class SchemaError(Issue): class SchemaError(Issue):
def __init__(self, message): def __init__(self, message):
super(SchemaError, self).__init__(Issue.ERROR, message) super(SchemaError, self).__init__(Issue.ERROR, message)
class InvalidValueError(MarkedIssue): class InvalidValueError(MarkedIssue):
def __init__(self, message, mark=Mark('', 0, 0)): def __init__(self, message, mark=Mark('', 0, 0)):
super(InvalidValueError, self).__init__(Issue.ERROR, 'Invalid value: '+message, mark) super(
InvalidValueError,
self).__init__(
Issue.ERROR,
'Invalid value: ' +
message,
mark)
class TypeValidator(object): class TypeValidator(object):
def __init__(self, f): def __init__(self, f):
super(TypeValidator, self).__init__() super(TypeValidator, self).__init__()
self.f = f self.f = f
@ -195,9 +257,11 @@ def type_validator(name, **kwargs):
return fn return fn
return wrap return wrap
def isissue(o): def isissue(o):
return isinstance(o, Issue) return isinstance(o, Issue)
@type_validator('boolean') @type_validator('boolean')
def validate_boolean(s): def validate_boolean(s):
s = s.lower() s = s.lower()
@ -208,6 +272,7 @@ def validate_boolean(s):
else: else:
return InvalidValueError('Value should be "true" or "false"') return InvalidValueError('Value should be "true" or "false"')
def validate_enum(s, values=[]): def validate_enum(s, values=[]):
if s in values: if s in values:
return None return None
@ -216,9 +281,11 @@ def validate_enum(s, values=[]):
elif len(values) == 1: elif len(values) == 1:
message = 'The only valid value is %s' % values[0] message = 'The only valid value is %s' % values[0]
else: else:
message = 'Valid values are %s and %s' % (', '.join(values[:-1]), values[-1]) message = 'Valid values are %s and %s' % (
', '.join(values[:-1]), values[-1])
return InvalidValueError('%s' % message) return InvalidValueError('%s' % message)
def validate_ipv4_address(s): def validate_ipv4_address(s):
s = s.strip() s = s.strip()
parts = s.split('.') parts = s.split('.')
@ -230,11 +297,15 @@ def validate_ipv4_address(s):
return InvalidValueError('Value should be ipv4 address') return InvalidValueError('Value should be ipv4 address')
def validate_ipv4_network(s): def validate_ipv4_network(s):
s = s.strip() s = s.strip()
parts = s.split('/') parts = s.split('/')
if len(parts) != 2: if len(parts) != 2:
return InvalidValueError('Should have "/" character separating address and prefix length') return (
InvalidValueError(
'Should have "/" character separating address and prefix length')
)
address, prefix = parts address, prefix = parts
prefix = prefix.strip() prefix = prefix.strip()
@ -251,32 +322,51 @@ def validate_ipv4_network(s):
prefix = int(prefix) prefix = int(prefix)
if prefix > 32: if prefix > 32:
return InvalidValueError('Prefix length should be less than or equal to 32') return (
InvalidValueError(
'Prefix length should be less than or equal to 32')
)
return '%s/%d' % (address, prefix) return '%s/%d' % (address, prefix)
def validate_host_label(s): def validate_host_label(s):
if len(s) == 0: if len(s) == 0:
return InvalidValueError('Host label should have at least one character') return (
InvalidValueError('Host label should have at least one character')
)
if not s[0].isalpha(): if not s[0].isalpha():
return InvalidValueError('Host label should start with a letter, but it starts with "%s"' % s[0]) return (
InvalidValueError(
'Host label should start with a letter, but it starts with "%s"' %
s[0])
)
if len(s) == 1: return s if len(s) == 1:
return s
if not (s[-1].isdigit() or s[-1].isalpha()): if not (s[-1].isdigit() or s[-1].isalpha()):
return InvalidValueError('Host label should end with letter or digit, but it ends with "%s"' % s[-1], Mark('', 0, len(s)-1)) return (
InvalidValueError(
'Host label should end with letter or digit, but it ends with "%s"' %
s[-1], Mark('', 0, len(s) - 1))
)
if len(s) == 2: return s if len(s) == 2:
return s
for i, c in enumerate(s[1:-1]): for i, c in enumerate(s[1:-1]):
if not (c.isalpha() or c.isdigit() or c == '-'): if not (c.isalpha() or c.isdigit() or c == '-'):
return InvalidValueError('Host label should contain only letters, digits or hypens, but it contains "%s"' % c, Mark('', 0, i+1)) return (
InvalidValueError(
'Host label should contain only letters, digits or hypens, but it contains "%s"' %
c, Mark('', 0, i + 1))
)
return s return s
@type_validator('host') @type_validator('host')
@type_validator('host_address') @type_validator('host_address')
def validate_host_address(s): def validate_host_address(s):
@ -299,11 +389,13 @@ def validate_host_address(s):
return '.'.join(labels) return '.'.join(labels)
@type_validator('network') @type_validator('network')
@type_validator('network_address') @type_validator('network_address')
def validate_network_address(s): def validate_network_address(s):
return validate_ipv4_network(s) return validate_ipv4_network(s)
@type_validator('host_and_port') @type_validator('host_and_port')
def validate_host_and_port(s, default_port=None): def validate_host_and_port(s, default_port=None):
parts = s.strip().split(':', 2) parts = s.strip().split(':', 2)
@ -323,16 +415,19 @@ def validate_host_and_port(s, default_port=None):
return (host_address, port) return (host_address, port)
@type_validator('string') @type_validator('string')
@type_validator('list') @type_validator('list')
@type_validator('multi') @type_validator('multi')
def validate_string(s): def validate_string(s):
return s return s
@type_validator('integer') @type_validator('integer')
def validate_integer(s, min=None, max=None): def validate_integer(s, min=None, max=None):
leading_whitespace_len = 0 leading_whitespace_len = 0
while leading_whitespace_len < len(s) and s[leading_whitespace_len].isspace(): leading_whitespace_len += 1 while leading_whitespace_len < len(s) and s[leading_whitespace_len].isspace():
leading_whitespace_len += 1
s = s.strip() s = s.strip()
if s == '': if s == '':
@ -340,25 +435,40 @@ def validate_integer(s, min=None, max=None):
for i, c in enumerate(s): for i, c in enumerate(s):
if not c.isdigit() and not ((c == '-') and (i == 0)): if not c.isdigit() and not ((c == '-') and (i == 0)):
return InvalidValueError('Only digits are allowed, but found char "%s"' % c, Mark('', 1, i+1+leading_whitespace_len)) return (
InvalidValueError(
'Only digits are allowed, but found char "%s"' %
c, Mark('', 1, i + 1 + leading_whitespace_len))
)
v = int(s) v = int(s)
if min and v < min: if min and v < min:
return InvalidValueError('Should be greater than or equal to %d' % min, Mark('', 1, leading_whitespace_len)) return (
InvalidValueError(
'Should be greater than or equal to %d' %
min, Mark('', 1, leading_whitespace_len))
)
if max and v > max: if max and v > max:
return InvalidValueError('Should be less than or equal to %d' % max, Mark('', 1, leading_whitespace_len)) return (
InvalidValueError(
'Should be less than or equal to %d' %
max, Mark('', 1, leading_whitespace_len))
)
return v return v
@type_validator('float') @type_validator('float')
def validate_float(s): def validate_float(s):
# TODO: Implement proper validation # TODO: Implement proper validation
return float(s) return float(s)
@type_validator('port') @type_validator('port')
def validate_port(s, min=1, max=65535): def validate_port(s, min=1, max=65535):
return validate_integer(s, min=min, max=max) return validate_integer(s, min=min, max=max)
@type_validator('string_list') @type_validator('string_list')
def validate_list(s, element_type='string'): def validate_list(s, element_type='string'):
element_type_validator = TypeValidatorRegistry.get_validator(element_type) element_type_validator = TypeValidatorRegistry.get_validator(element_type)
@ -381,6 +491,7 @@ def validate_list(s, element_type='string'):
return result return result
@type_validator('string_dict') @type_validator('string_dict')
def validate_dict(s, element_type='string'): def validate_dict(s, element_type='string'):
element_type_validator = TypeValidatorRegistry.get_validator(element_type) element_type_validator = TypeValidatorRegistry.get_validator(element_type)
@ -397,7 +508,10 @@ def validate_dict(s, element_type='string'):
for pair in pairs: for pair in pairs:
key_value = pair.split(':', 2) key_value = pair.split(':', 2)
if len(key_value) < 2: if len(key_value) < 2:
return InvalidValueError('Value should be NAME:VALUE pairs separated by ","') return (
InvalidValueError(
'Value should be NAME:VALUE pairs separated by ","')
)
key, value = key_value key, value = key_value
key = key.strip() key = key.strip()
@ -413,4 +527,3 @@ def validate_dict(s, element_type='string'):
return validated_value return validated_value
result[key] = validated_value result[key] = validated_value
return result return result

View File

@ -1,4 +1,3 @@
import ostack_validator.schemas.keystone import ostack_validator.schemas.keystone
import ostack_validator.schemas.nova import ostack_validator.schemas.nova
import ostack_validator.schemas.cinder import ostack_validator.schemas.cinder

View File

@ -1,2 +1 @@
import ostack_validator.schemas.cinder.v2013_1_3 import ostack_validator.schemas.cinder.v2013_1_3

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1 @@
import ostack_validator.schemas.keystone.v2013_1_3 import ostack_validator.schemas.keystone.v2013_1_3

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,2 @@
import ostack_validator.schemas.nova.v2013_1 import ostack_validator.schemas.nova.v2013_1
import ostack_validator.schemas.nova.v2013_1_3 import ostack_validator.schemas.nova.v2013_1_3

View File

@ -11,7 +11,10 @@ nova.section('DEFAULT')
# availability_zone to show internal services under (string # availability_zone to show internal services under (string
# value) # value)
nova.param('internal_service_availability_zone', type='string', default='internal') nova.param(
'internal_service_availability_zone',
type='string',
default='internal')
# default compute node availability_zone (string value) # default compute node availability_zone (string value)
# default_availability_zone=nova # default_availability_zone=nova
@ -296,7 +299,8 @@ nova.param('use_ipv6', type='boolean')
# generate log lines. The following values can be formatted # generate log lines. The following values can be formatted
# into it: client_ip, date_time, request_line, status_code, # into it: client_ip, date_time, request_line, status_code,
# body_length, wall_seconds. (string value) # body_length, wall_seconds. (string value)
#wsgi_log_format=%(client_ip)s "%(request_line)s" status: %(status_code)s len: %(body_length)s time: %(wall_seconds).7f # wsgi_log_format=%(client_ip)s "%(request_line)s" status: %(status_code)s
# len: %(body_length)s time: %(wall_seconds).7f
# CA certificate file to use to verify connecting clients # CA certificate file to use to verify connecting clients
# (string value) # (string value)
@ -389,7 +393,8 @@ nova.param('use_ipv6', type='boolean')
# List of metadata versions to skip placing into the config # List of metadata versions to skip placing into the config
# drive (string value) # drive (string value)
#config_drive_skip_versions=1.0 2007-01-19 2007-03-01 2007-08-29 2007-10-10 2007-12-15 2008-02-01 2008-09-01 # config_drive_skip_versions=1.0 2007-01-19 2007-03-01 2007-08-29
# 2007-10-10 2007-12-15 2008-02-01 2008-09-01
# Driver to use for vendor data (string value) # Driver to use for vendor data (string value)
# vendordata_driver=nova.api.metadata.vendordata_json.JsonFileVendorData # vendordata_driver=nova.api.metadata.vendordata_json.JsonFileVendorData
@ -1376,19 +1381,30 @@ nova.param('use_ipv6', type='boolean')
# Print more verbose output (set logging level to INFO instead # Print more verbose output (set logging level to INFO instead
# of default WARNING level). (boolean value) # of default WARNING level). (boolean value)
# verbose=false # verbose=false
nova.param('verbose', type='boolean', default=False, description='Print more verbose output (set logging level to INFO instead of default WARNING level)') nova.param(
'verbose',
type='boolean',
default=False,
description='Print more verbose output (set logging level to INFO instead of default WARNING level)')
# Log output to standard error (boolean value) # Log output to standard error (boolean value)
# use_stderr=true # use_stderr=true
nova.param('use_stderr', type='boolean', default=True, description='Log output to standard error') nova.param(
'use_stderr',
type='boolean',
default=True,
description='Log output to standard error')
# format string to use for log messages with context (string # format string to use for log messages with context (string
# value) # value)
#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s%(message)s # logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d
# %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s]
# %(instance)s%(message)s
# format string to use for log messages without context # format string to use for log messages without context
# (string value) # (string value)
#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s # logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d
# %(levelname)s %(name)s [-] %(instance)s%(message)s
# data to append to log format when level is DEBUG (string # data to append to log format when level is DEBUG (string
# value) # value)
@ -1396,7 +1412,8 @@ nova.param('use_stderr', type='boolean', default=True, description='Log output t
# prefix each line of exception output with this format # prefix each line of exception output with this format
# (string value) # (string value)
#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s # logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE
# %(name)s %(instance)s
# list of logger=LEVEL pairs (list value) # list of logger=LEVEL pairs (list value)
# default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN # default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN
@ -3390,4 +3407,3 @@ nova.section('spice')
# keymap=en-us # keymap=en-us
nova.commit() nova.commit()

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import sys
class SchemaParser(object): class SchemaParser(object):
def parse_args(self, argv): def parse_args(self, argv):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--conf', dest='conf_file', default=None, parser.add_argument('--conf', dest='conf_file', default=None,

View File

@ -3,7 +3,9 @@ from ostack_validator.common import find
import unittest import unittest
class ConfigSchemaRegistryTests(unittest.TestCase): class ConfigSchemaRegistryTests(unittest.TestCase):
def test_sample(self): def test_sample(self):
nova = ConfigSchemaRegistry.register_schema(project='nova') nova = ConfigSchemaRegistry.register_schema(project='nova')
@ -19,30 +21,39 @@ class ConfigSchemaRegistryTests(unittest.TestCase):
nova.commit() nova.commit()
schema10 = ConfigSchemaRegistry.get_schema(project='nova', version='1.0.0') schema10 = ConfigSchemaRegistry.get_schema(
project='nova', version='1.0.0')
self.assertEqual(Version('1.0.0'), schema10.version) self.assertEqual(Version('1.0.0'), schema10.version)
self.assertEqual('ini', schema10.format) self.assertEqual('ini', schema10.format)
verbose_param = find(schema10.parameters, lambda p: p.name == 'verbose') verbose_param = find(
schema10.parameters,
lambda p: p.name == 'verbose')
self.assertIsNotNone(verbose_param) self.assertIsNotNone(verbose_param)
self.assertEqual('boolean', verbose_param.type) self.assertEqual('boolean', verbose_param.type)
self.assertEqual(None, verbose_param.default) self.assertEqual(None, verbose_param.default)
rabbit_host_param = find(schema10.parameters, lambda p: p.name == 'rabbit_host') rabbit_host_param = find(
schema10.parameters,
lambda p: p.name == 'rabbit_host')
self.assertIsNotNone(rabbit_host_param) self.assertIsNotNone(rabbit_host_param)
self.assertEqual('address', rabbit_host_param.type) self.assertEqual('address', rabbit_host_param.type)
schema11 = ConfigSchemaRegistry.get_schema(project='nova', version='1.1.0') schema11 = ConfigSchemaRegistry.get_schema(
project='nova', version='1.1.0')
verbose_param11 = find(schema11.parameters, lambda p: p.name == 'verbose') verbose_param11 = find(
schema11.parameters,
lambda p: p.name == 'verbose')
self.assertIsNotNone(verbose_param11) self.assertIsNotNone(verbose_param11)
self.assertEqual(False, verbose_param11.default) self.assertEqual(False, verbose_param11.default)
rabbit_host_param11 = find(schema11.parameters, lambda p: p.name == 'rabbit_host') rabbit_host_param11 = find(
schema11.parameters,
lambda p: p.name == 'rabbit_host')
self.assertIsNone(rabbit_host_param11) self.assertIsNone(rabbit_host_param11)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,6 +2,7 @@ import unittest
from ostack_validator.config_model import Configuration from ostack_validator.config_model import Configuration
class ConfigurationTests(unittest.TestCase): class ConfigurationTests(unittest.TestCase):
section = 'section1' section = 'section1'
param = 'param1' param = 'param1'
@ -23,7 +24,9 @@ class ConfigurationTests(unittest.TestCase):
c = Configuration() c = Configuration()
c.set(self.fullparam, self.value) c.set(self.fullparam, self.value)
self.assertEqual(self.value, c.get('%s.%s' % (self.section, self.param))) self.assertEqual(
self.value, c.get('%s.%s' %
(self.section, self.param)))
def test_parameter_with_default_section(self): def test_parameter_with_default_section(self):
c = Configuration() c = Configuration()
@ -35,7 +38,10 @@ class ConfigurationTests(unittest.TestCase):
c = Configuration() c = Configuration()
override_value = '12345' override_value = '12345'
self.assertEqual(override_value, c.get(self.fullparam, default=override_value)) self.assertEqual(
override_value,
c.get(self.fullparam,
default=override_value))
def test_default(self): def test_default(self):
c = Configuration() c = Configuration()
@ -68,7 +74,6 @@ class ConfigurationTests(unittest.TestCase):
self.assertTrue(c.contains(self.fullparam)) self.assertTrue(c.contains(self.fullparam))
self.assertTrue(c.contains(self.fullparam, ignoreDefault=True)) self.assertTrue(c.contains(self.fullparam, ignoreDefault=True))
def test_is_default_returns_false_if_param_missing(self): def test_is_default_returns_false_if_param_missing(self):
c = Configuration() c = Configuration()
self.assertFalse(c.is_default(self.fullparam)) self.assertFalse(c.is_default(self.fullparam))
@ -110,14 +115,16 @@ class ConfigurationTests(unittest.TestCase):
c.set_default('%s.param1' % self.section, '123') c.set_default('%s.param1' % self.section, '123')
c.set('%s.param2' % self.section, '456') c.set('%s.param2' % self.section, '456')
self.assertEqual(['param1', 'param2'], sorted(c.section(self.section).keys())) self.assertEqual(
['param1', 'param2'], sorted(c.section(self.section).keys()))
def test_subsection_items(self): def test_subsection_items(self):
c = Configuration() c = Configuration()
c.set('%s.param1' % self.section, 'value1') c.set('%s.param1' % self.section, 'value1')
c.set_default('%s.param2' % self.section, 'value2') c.set_default('%s.param2' % self.section, 'value2')
self.assertEqual([('param1', 'value1'), ('param2', 'value2')], sorted(c.section(self.section).items())) self.assertEqual(
[('param1', 'value1'), ('param2', 'value2')], sorted(c.section(self.section).items()))
def test_subsection_get(self): def test_subsection_get(self):
c = Configuration() c = Configuration()
@ -202,4 +209,3 @@ class ConfigurationTests(unittest.TestCase):
c.set('b', 'x') c.set('b', 'x')
self.assertEqual('$b', c.get('a', raw=True)) self.assertEqual('$b', c.get('a', raw=True))

View File

@ -2,7 +2,9 @@ from ostack_validator.common import Mark
import unittest import unittest
class MarkTests(unittest.TestCase): class MarkTests(unittest.TestCase):
def test_creation(self): def test_creation(self):
m = Mark('nova.conf', 3, 5) m = Mark('nova.conf', 3, 5)
self.assertEqual('nova.conf', m.source) self.assertEqual('nova.conf', m.source)
@ -18,4 +20,3 @@ class MarkTests(unittest.TestCase):
self.assertEqual(m1.source, m.source) self.assertEqual(m1.source, m.source)
self.assertEqual(m1.line + m2.line, m.line) self.assertEqual(m1.line + m2.line, m.line)
self.assertEqual(m1.column + m2.column, m.column) self.assertEqual(m1.column + m2.column, m.column)

View File

@ -3,7 +3,9 @@ from ostack_validator.schema import TypeValidatorRegistry
import unittest import unittest
class TypeValidatorTestHelper(object): class TypeValidatorTestHelper(object):
def setUp(self): def setUp(self):
super(TypeValidatorTestHelper, self).setUp() super(TypeValidatorTestHelper, self).setUp()
self.validator = TypeValidatorRegistry.get_validator(self.type_name) self.validator = TypeValidatorRegistry.get_validator(self.type_name)
@ -14,6 +16,7 @@ class TypeValidatorTestHelper(object):
def assertInvalid(self, value): def assertInvalid(self, value):
self.assertIsInstance(self.validator.validate(value), Issue) self.assertIsInstance(self.validator.validate(value), Issue)
class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string' type_name = 'string'
@ -27,6 +30,7 @@ class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
s = 'foo bar' s = 'foo bar'
self.assertEqual(s, self.validator.validate(s)) self.assertEqual(s, self.validator.validate(s))
class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'boolean' type_name = 'boolean'
@ -41,6 +45,7 @@ class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
def test_other_values_produce_error(self): def test_other_values_produce_error(self):
self.assertInvalid('foo') self.assertInvalid('foo')
class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'integer' type_name = 'integer'
@ -67,7 +72,8 @@ class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
self.assertIsInstance(error, MarkedIssue) self.assertIsInstance(error, MarkedIssue)
self.assertEqual(3, error.mark.column) self.assertEqual(3, error.mark.column)
def test_invalid_char_error_contains_proper_column_if_leading_whitespaces(self): def test_invalid_char_error_contains_proper_column_if_leading_whitespaces(
self):
error = self.validator.validate(' 12a45') error = self.validator.validate(' 12a45')
self.assertIsInstance(error, MarkedIssue) self.assertIsInstance(error, MarkedIssue)
self.assertEqual(5, error.mark.column) self.assertEqual(5, error.mark.column)
@ -76,6 +82,7 @@ class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
v = self.validator.validate('123') v = self.validator.validate('123')
self.assertEqual(123, v) self.assertEqual(123, v)
class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'host_address' type_name = 'host_address'
@ -127,6 +134,7 @@ class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
e = self.validator.validate('foo.bar.-baz') e = self.validator.validate('foo.bar.-baz')
self.assertEqual(8, e.mark.column) self.assertEqual(8, e.mark.column)
class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'network_address' type_name = 'network_address'
@ -154,6 +162,7 @@ class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCas
def test_prefix_greater_than_32(self): def test_prefix_greater_than_32(self):
self.assertInvalid('10.0.0.0/33') self.assertInvalid('10.0.0.0/33')
class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'port' type_name = 'port'
@ -190,6 +199,7 @@ class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
v = self.validator.validate('123') v = self.validator.validate('123')
self.assertEqual(123, v) self.assertEqual(123, v)
class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'host_and_port' type_name = 'host_and_port'
@ -217,6 +227,7 @@ class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
def test_port_is_greater_than_65535(self): def test_port_is_greater_than_65535(self):
self.assertInvalid('10.0.0.1:65536') self.assertInvalid('10.0.0.1:65536')
class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string_list' type_name = 'string_list'
@ -239,6 +250,7 @@ class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
self.assertEqual('baz', v[1]) self.assertEqual('baz', v[1])
self.assertEqual(2, len(v)) self.assertEqual(2, len(v))
class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string_dict' type_name = 'string_dict'
@ -264,4 +276,3 @@ class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,6 +2,7 @@ from ostack_validator.schema import Version
import unittest import unittest
class VersionTests(unittest.TestCase): class VersionTests(unittest.TestCase):
def test_creation_from_components(self): def test_creation_from_components(self):
@ -60,4 +61,3 @@ class VersionTests(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,14 +1,18 @@
import collections import collections
import functools import functools
class memoized(object): class memoized(object):
'''Decorator. Caches a function's return value each time it is called. '''Decorator. Caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned If called later with the same arguments, the cached value is returned
(not reevaluated). (not reevaluated).
''' '''
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
self.cache = {} self.cache = {}
def __call__(self, *args): def __call__(self, *args):
if not isinstance(args, collections.Hashable): if not isinstance(args, collections.Hashable):
# uncacheable. a list, for instance. # uncacheable. a list, for instance.
@ -20,10 +24,11 @@ class memoized(object):
value = self.func(*args) value = self.func(*args)
self.cache[args] = value self.cache[args] = value
return value return value
def __repr__(self): def __repr__(self):
'''Return the function's docstring.''' '''Return the function's docstring.'''
return self.func.__doc__ return self.func.__doc__
def __get__(self, obj, objtype): def __get__(self, obj, objtype):
'''Support instance methods.''' '''Support instance methods.'''
return functools.partial(self.__call__, obj) return functools.partial(self.__call__, obj)

View File

@ -21,7 +21,10 @@ app.secret_key = 'A0Zr98j/3fooN]LWX/,?RT'
class ValidationLaunchForm(Form): class ValidationLaunchForm(Form):
nodes = StringField('Nodes', validators=[DataRequired()]) nodes = StringField('Nodes', validators=[DataRequired()])
username = StringField('Username', default='root', validators=[DataRequired()]) username = StringField(
'Username',
default='root',
validators=[DataRequired()])
private_key = TextAreaField('Private Key', validators=[DataRequired()]) private_key = TextAreaField('Private Key', validators=[DataRequired()])
launch = SubmitField('Launch validation') launch = SubmitField('Launch validation')
@ -36,15 +39,21 @@ def to_label(s):
else: else:
return 'label-info' return 'label-info'
@app.route('/') @app.route('/')
def index(): def index():
return redirect('/validation') return redirect('/validation')
@app.route('/validation', methods=['GET', 'POST']) @app.route('/validation', methods=['GET', 'POST'])
def launch_validation(): def launch_validation():
form = ValidationLaunchForm() form = ValidationLaunchForm()
if form.validate_on_submit(): if form.validate_on_submit():
request = InspectionRequest(form.nodes.data.split(' '), form.username.data, private_key=form.private_key.data) request = InspectionRequest(
form.nodes.data.split(
' '),
form.username.data,
private_key=form.private_key.data)
job = ostack_inspect_task.delay(request) job = ostack_inspect_task.delay(request)
@ -52,6 +61,7 @@ def launch_validation():
else: else:
return render_template('validation_form.html', form=form) return render_template('validation_form.html', form=form)
@app.route('/validation/<id>') @app.route('/validation/<id>')
def job(id): def job(id):
job = celery.AsyncResult(id) job = celery.AsyncResult(id)
@ -66,15 +76,29 @@ def job(id):
openstack = job.result.value openstack = job.result.value
if isinstance(openstack, Openstack): if isinstance(openstack, Openstack):
issue_source_f = lambda i: i.mark.source if isinstance(i, MarkedIssue) else None issue_source_f = lambda i: i.mark.source if isinstance(
source_groupped_issues = groupby(sorted(openstack.issues, key=issue_source_f), key=issue_source_f) i, MarkedIssue) else None
source_groupped_issues = groupby(
sorted(openstack.issues,
key=issue_source_f),
key=issue_source_f)
return render_template('validation_result.html', form=form, openstack=openstack, grouped_issues=source_groupped_issues) return (
render_template(
'validation_result.html',
form=form,
openstack=openstack,
grouped_issues=source_groupped_issues)
)
else: else:
return render_template('validation_error.html', form=form, message=openstack) return (
render_template(
'validation_error.html',
form=form,
message=openstack)
)
else: else:
return render_template('validation_state.html', state=job.state) return render_template('validation_state.html', state=job.state)
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True) app.run(host='0.0.0.0', debug=True)