PEP8 fixes
This commit is contained in:
parent
8ac684d600
commit
a059946079
@ -3,4 +3,3 @@ if __name__ == '__main__':
|
||||
import sys
|
||||
from ostack_validator.main import main
|
||||
main(sys.argv[1:])
|
||||
|
||||
|
@ -18,7 +18,9 @@ app.conf.update(
|
||||
CELERY_TRACK_STARTED=True
|
||||
)
|
||||
|
||||
|
||||
class InspectionRequest(object):
|
||||
|
||||
def __init__(self, nodes, username, password=None, private_key=None):
|
||||
super(InspectionRequest, self).__init__()
|
||||
self.nodes = nodes
|
||||
@ -26,12 +28,15 @@ class InspectionRequest(object):
|
||||
self.password = password
|
||||
self.private_key = private_key
|
||||
|
||||
|
||||
class InspectionResult(object):
|
||||
|
||||
def __init__(self, request, value):
|
||||
super(InspectionResult, self).__init__()
|
||||
self.request = request
|
||||
self.value = value
|
||||
|
||||
|
||||
@app.task
|
||||
def ostack_inspect_task(request):
|
||||
logger = logging.getLogger('ostack_validator.task.inspect')
|
||||
@ -39,7 +44,8 @@ def ostack_inspect_task(request):
|
||||
discovery = OpenstackDiscovery()
|
||||
|
||||
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:
|
||||
message = traceback.format_exc()
|
||||
logger.error(message)
|
||||
@ -53,13 +59,17 @@ def ostack_inspect_task(request):
|
||||
except:
|
||||
message = traceback.format_exc()
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logging.getLogger('ostack_validator').setLevel(logging.DEBUG)
|
||||
|
||||
app.start()
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import copy
|
||||
|
||||
|
||||
def find(l, predicate):
|
||||
results = [x for x in l if predicate(x)]
|
||||
return results[0] if len(results) > 0 else None
|
||||
|
||||
|
||||
def index(l, predicate):
|
||||
i = 0
|
||||
while i < len(l):
|
||||
@ -12,19 +14,23 @@ def index(l, predicate):
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
|
||||
def all_subclasses(klass):
|
||||
subclasses = klass.__subclasses__()
|
||||
for d in list(subclasses):
|
||||
subclasses.extend(all_subclasses(d))
|
||||
return subclasses
|
||||
|
||||
|
||||
def path_relative_to(path, base_path):
|
||||
if not path.startswith('/'):
|
||||
path = os.path.join(base_path, paste_config_path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
class Version:
|
||||
|
||||
def __init__(self, major, minor=0, maintenance=0):
|
||||
"Create Version object by either passing 3 integers, one string or an another Version object"
|
||||
if isinstance(major, str):
|
||||
@ -77,33 +83,51 @@ class Version:
|
||||
|
||||
|
||||
class Mark(object):
|
||||
|
||||
def __init__(self, source, line=0, column=0):
|
||||
self.source = source
|
||||
self.line = line
|
||||
self.column = column
|
||||
|
||||
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):
|
||||
return not 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):
|
||||
return '%s line %d column %d' % (self.source, self.line, self.column)
|
||||
|
||||
|
||||
class Error:
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
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):
|
||||
return self.message
|
||||
|
||||
|
||||
class Issue(object):
|
||||
FATAL = 'FATAL'
|
||||
ERROR = 'ERROR'
|
||||
@ -115,12 +139,19 @@ class Issue(object):
|
||||
self.message = message
|
||||
|
||||
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):
|
||||
return '%s: %s' % (self.type, self.message)
|
||||
|
||||
|
||||
class MarkedIssue(Issue):
|
||||
|
||||
def __init__(self, type, message, mark):
|
||||
super(MarkedIssue, self).__init__(type, message)
|
||||
self.mark = mark
|
||||
@ -131,16 +162,27 @@ class MarkedIssue(Issue):
|
||||
return other
|
||||
|
||||
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):
|
||||
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):
|
||||
|
||||
@classmethod
|
||||
def all_inspections(klass):
|
||||
return [c for c in all_subclasses(klass)]
|
||||
|
||||
def inspect(self, openstack):
|
||||
pass
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
from common import *
|
||||
from ini import IniConfigParser
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
from ostack_validator.common import Mark, Issue, MarkedIssue
|
||||
|
||||
|
||||
class ParseError(MarkedIssue):
|
||||
|
||||
def __init__(self, message, mark):
|
||||
super(ParseError, self).__init__(Issue.ERROR, message, mark)
|
||||
|
||||
|
@ -4,6 +4,7 @@ from StringIO import StringIO
|
||||
from ostack_validator.config_model import *
|
||||
from ostack_validator.config_formats.common import *
|
||||
|
||||
|
||||
class IniConfigParser:
|
||||
key_value_re = re.compile("^(\S+?)\s*([:=])\s*('.*'|\".*\"|.*)\s*$")
|
||||
|
||||
@ -29,16 +30,23 @@ class IniConfigParser:
|
||||
line_number += 1
|
||||
|
||||
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)
|
||||
|
||||
current_param_name = None
|
||||
current_param_value = 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 current_param_name:
|
||||
@ -46,29 +54,38 @@ class IniConfigParser:
|
||||
current_param_value.text += line.lstrip()
|
||||
continue
|
||||
else:
|
||||
errors.append(ParseError('Unexpected multiline value continuation', mark(line_number)))
|
||||
errors.append(
|
||||
ParseError('Unexpected multiline value continuation', mark(line_number)))
|
||||
continue
|
||||
|
||||
if line[0] == '[':
|
||||
end_index = line.find(']')
|
||||
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)
|
||||
while line[end_index-1].isspace(): end_index -= 1
|
||||
while line[end_index - 1].isspace():
|
||||
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
|
||||
else:
|
||||
i = end_index + 1
|
||||
while i < len(line):
|
||||
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
|
||||
i += 1
|
||||
|
||||
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)
|
||||
parameters = []
|
||||
|
||||
@ -105,14 +122,25 @@ class IniConfigParser:
|
||||
quotechar=quotechar
|
||||
)
|
||||
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:
|
||||
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)
|
||||
|
||||
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)
|
||||
parameters = []
|
||||
|
||||
@ -123,4 +151,3 @@ class IniConfigParser:
|
||||
config = ComponentConfig(base_mark, end_mark, name, sections, errors)
|
||||
|
||||
return config
|
||||
|
||||
|
@ -2,7 +2,9 @@ import unittest
|
||||
|
||||
from ostack_validator.config_formats.ini import *
|
||||
|
||||
|
||||
class IniConfigParserTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.parser = IniConfigParser()
|
||||
|
||||
@ -12,7 +14,8 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
lines = lines[1:-1]
|
||||
first_line = lines[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]
|
||||
|
||||
@ -29,7 +32,10 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
|
||||
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))
|
||||
|
||||
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):
|
||||
c = self.parse('param1: value=123')
|
||||
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):
|
||||
c = self.parse('param1=value:123')
|
||||
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):
|
||||
c = self.parse("param = 'foo bar'")
|
||||
@ -146,7 +158,10 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
""", margin=True)
|
||||
|
||||
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):
|
||||
c = self.parse("""
|
||||
@ -190,12 +205,11 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
self.assertEqual(1, len(c.errors))
|
||||
self.assertParameter('param2', 'value2', c.sections[0].parameters[0])
|
||||
|
||||
|
||||
def _getattr(self, o, name):
|
||||
if name.find('.') != -1:
|
||||
parts = name.split('.')
|
||||
o = getattr(o, parts[0])
|
||||
if o == None:
|
||||
if o is None:
|
||||
return None
|
||||
return self._getattr(o, '.'.join(parts[1:]))
|
||||
else:
|
||||
@ -204,7 +218,9 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
def assertAttributes(self, attribute_values, subject):
|
||||
for attr, expected in attribute_values.items():
|
||||
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):
|
||||
self.assertAttributes({'name.text': name, 'value.text': value}, o)
|
||||
@ -212,4 +228,3 @@ class IniConfigParserTests(unittest.TestCase):
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -2,7 +2,9 @@ import string
|
||||
|
||||
from ostack_validator.common import Mark
|
||||
|
||||
|
||||
class ConfigurationSection(object):
|
||||
|
||||
def __init__(self, config, section):
|
||||
super(ConfigurationSection, self).__init__()
|
||||
self.config = config
|
||||
@ -15,19 +17,50 @@ class ConfigurationSection(object):
|
||||
return '%s.%s' % (section, param)
|
||||
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
return self.config.get(self._combine_names(self.section, key))
|
||||
@ -44,7 +77,9 @@ class ConfigurationSection(object):
|
||||
def items(self, *args, **kwargs):
|
||||
return self.config.items(self.section, *args, **kwargs)
|
||||
|
||||
|
||||
class ConfigurationWrapper(object):
|
||||
|
||||
def __init__(self, config, state):
|
||||
super(ConfigurationWrapper, self).__init__()
|
||||
self.config = config
|
||||
@ -58,6 +93,7 @@ class ConfigurationWrapper(object):
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
|
||||
def __init__(self):
|
||||
super(Configuration, self).__init__()
|
||||
self._defaults = dict()
|
||||
@ -94,7 +130,9 @@ class Configuration(object):
|
||||
return 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):
|
||||
section, name = self._normalize_name(name)
|
||||
@ -110,7 +148,10 @@ class Configuration(object):
|
||||
def is_default(self, 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):
|
||||
section, name = self._normalize_name(name)
|
||||
@ -163,23 +204,35 @@ class Configuration(object):
|
||||
|
||||
def items(self, section=None):
|
||||
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:
|
||||
return [(name, ConfigurationSection(self, name)) for name in self.keys()]
|
||||
return (
|
||||
[(name, ConfigurationSection(self, name))
|
||||
for name in self.keys()]
|
||||
)
|
||||
|
||||
|
||||
class Element(object):
|
||||
|
||||
def __init__(self, start_mark, end_mark):
|
||||
self.start_mark = start_mark
|
||||
self.end_mark = end_mark
|
||||
|
||||
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):
|
||||
return not self == other
|
||||
|
||||
|
||||
class ComponentConfig(Element):
|
||||
|
||||
def __init__(self, start_mark, end_mark, name, sections=[], errors=[]):
|
||||
super(ComponentConfig, self).__init__(start_mark, end_mark)
|
||||
self.name = name
|
||||
@ -189,12 +242,16 @@ class ComponentConfig(Element):
|
||||
|
||||
self.errors = errors
|
||||
|
||||
|
||||
class TextElement(Element):
|
||||
|
||||
def __init__(self, start_mark, end_mark, text):
|
||||
super(TextElement, self).__init__(start_mark, end_mark)
|
||||
self.text = text
|
||||
|
||||
|
||||
class ConfigSection(Element):
|
||||
|
||||
def __init__(self, start_mark, end_mark, name, parameters):
|
||||
super(ConfigSection, self).__init__(start_mark, end_mark)
|
||||
self.name = name
|
||||
@ -202,9 +259,13 @@ class ConfigSection(Element):
|
||||
for parameter in self.parameters:
|
||||
parameter.parent = self
|
||||
|
||||
class ConfigSectionName(TextElement): pass
|
||||
|
||||
class ConfigSectionName(TextElement):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigParameter(Element):
|
||||
|
||||
def __init__(self, start_mark, end_mark, name, value, delimiter):
|
||||
super(ConfigParameter, self).__init__(start_mark, end_mark)
|
||||
self.name = name
|
||||
@ -217,20 +278,30 @@ class ConfigParameter(Element):
|
||||
self.delimiter.parent = self
|
||||
|
||||
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):
|
||||
return not self == other
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, start_mark, end_mark, text, value=None, quotechar=None):
|
||||
super(ConfigParameterValue, self).__init__(start_mark, end_mark, text)
|
||||
self.value = value
|
||||
self.quotechar = quotechar
|
||||
|
||||
|
@ -11,9 +11,15 @@ from ostack_validator.model import *
|
||||
|
||||
|
||||
class NodeClient(object):
|
||||
|
||||
def __init__(self, node_address, username, private_key_file, ssh_port=22):
|
||||
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):
|
||||
return self.shell.run(command, allow_error=True, *args, **kwargs)
|
||||
@ -24,7 +30,9 @@ class NodeClient(object):
|
||||
python_re = re.compile('(/?([^/]*/)*)python[0-9.]*')
|
||||
host_port_re = re.compile('(\d+\.\d+\.\d+\.\d+):(\d+)')
|
||||
|
||||
|
||||
class OpenstackDiscovery(object):
|
||||
|
||||
def discover(self, initial_nodes, username, private_key):
|
||||
"Takes a list of node addresses and returns discovered openstack installation info"
|
||||
openstack = Openstack()
|
||||
@ -44,10 +52,18 @@ class OpenstackDiscovery(object):
|
||||
else:
|
||||
host = address
|
||||
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'])
|
||||
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
|
||||
|
||||
host = self._discover_node(client)
|
||||
@ -58,7 +74,8 @@ class OpenstackDiscovery(object):
|
||||
openstack.add_host(host)
|
||||
|
||||
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:
|
||||
private_key_file.close()
|
||||
@ -86,7 +103,6 @@ class OpenstackDiscovery(object):
|
||||
|
||||
return host
|
||||
|
||||
|
||||
def _find_process(self, client, name):
|
||||
processes = self._get_processes(client)
|
||||
for line in processes:
|
||||
@ -106,12 +122,15 @@ class OpenstackDiscovery(object):
|
||||
return None
|
||||
|
||||
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()
|
||||
parts = []
|
||||
for p in s.split('.'):
|
||||
if not p[0].isdigit(): break
|
||||
if not p[0].isdigit():
|
||||
break
|
||||
|
||||
parts.append(p)
|
||||
|
||||
@ -120,7 +139,10 @@ class OpenstackDiscovery(object):
|
||||
return version
|
||||
|
||||
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):
|
||||
ether_re = re.compile('link/ether (([0-9a-f]{2}:){5}([0-9a-f]{2})) ')
|
||||
@ -147,7 +169,8 @@ class OpenstackDiscovery(object):
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
with client.open(path) as f:
|
||||
@ -155,7 +178,6 @@ class OpenstackDiscovery(object):
|
||||
|
||||
return FileResource(path, contents, owner, group, permissions)
|
||||
|
||||
|
||||
def _get_keystone_db_data(self, client, command, env={}):
|
||||
result = client.run(['keystone', command], update_env=env)
|
||||
if result.return_code != 0:
|
||||
@ -201,7 +223,8 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/keystone/keystone.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
|
||||
@ -217,10 +240,14 @@ class OpenstackDiscovery(object):
|
||||
}
|
||||
|
||||
keystone.db = dict()
|
||||
keystone.db['tenants'] = self._get_keystone_db_data(client, 'tenant-list', env=keystone_env)
|
||||
keystone.db['users'] = self._get_keystone_db_data(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)
|
||||
keystone.db['tenants'] = self._get_keystone_db_data(
|
||||
client, 'tenant-list', env=keystone_env)
|
||||
keystone.db['users'] = self._get_keystone_db_data(
|
||||
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
|
||||
|
||||
@ -240,8 +267,11 @@ class OpenstackDiscovery(object):
|
||||
nova_api.config_files = []
|
||||
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))
|
||||
nova_api.paste_config_file = self._collect_file(client, paste_config_path)
|
||||
paste_config_path = path_relative_to(
|
||||
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
|
||||
|
||||
@ -257,9 +287,11 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/nova/nova.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
nova_compute.config_files.append(
|
||||
self._collect_file(client, config_path))
|
||||
|
||||
return nova_compute
|
||||
|
||||
@ -275,9 +307,11 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/nova/nova.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
nova_scheduler.config_files.append(
|
||||
self._collect_file(client, config_path))
|
||||
|
||||
return nova_scheduler
|
||||
|
||||
@ -293,7 +327,8 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/glance/glance-api.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
|
||||
@ -311,9 +346,11 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/glance/glance-registry.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
glance_registry.config_files.append(
|
||||
self._collect_file(client, config_path))
|
||||
|
||||
return glance_registry
|
||||
|
||||
@ -329,12 +366,16 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/cinder/cinder.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
|
||||
paste_config_path = path_relative_to(cinder_api.config['api_paste_config'], os.path.dirname(config_path))
|
||||
cinder_api.paste_config_file = self._collect_file(client, paste_config_path)
|
||||
paste_config_path = path_relative_to(
|
||||
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
|
||||
|
||||
@ -350,12 +391,17 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/cinder/cinder.conf'
|
||||
|
||||
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.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))
|
||||
cinder_volume.rootwrap_config = self._collect_file(client, rootwrap_config_path)
|
||||
rootwrap_config_path = path_relative_to(
|
||||
cinder_volume.config['rootwrap_config'],
|
||||
os.path.dirname(config_path))
|
||||
cinder_volume.rootwrap_config = self._collect_file(
|
||||
client, rootwrap_config_path)
|
||||
|
||||
return cinder_volume
|
||||
|
||||
@ -371,9 +417,11 @@ class OpenstackDiscovery(object):
|
||||
config_path = '/etc/cinder/cinder.conf'
|
||||
|
||||
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.append(self._collect_file(client, config_path))
|
||||
cinder_scheduler.config_files.append(
|
||||
self._collect_file(client, config_path))
|
||||
|
||||
return cinder_scheduler
|
||||
|
||||
@ -391,8 +439,10 @@ class OpenstackDiscovery(object):
|
||||
mysql.version = m.group(1) if m else 'unknown'
|
||||
|
||||
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 = config_locations_result.output.strip().split("\n")[-1].split()
|
||||
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 = config_locations_result.output.strip().split(
|
||||
"\n")[-1].split()
|
||||
for path in config_locations:
|
||||
f = self._collect_file(client, path)
|
||||
if f:
|
||||
@ -412,4 +462,3 @@ class OpenstackDiscovery(object):
|
||||
rabbitmq.version = 'unknown'
|
||||
|
||||
return rabbitmq
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
from ostack_validator.inspections.keystone_authtoken import KeystoneAuthtokenSettingsInspection
|
||||
from ostack_validator.inspections.keystone_endpoints import KeystoneEndpointsInspection
|
||||
from ostack_validator.inspections.lettuce_runner import LettuceRunnerInspection
|
||||
|
||||
|
@ -3,6 +3,7 @@ from ostack_validator.common import Inspection, Issue, find
|
||||
|
||||
KEYSTONE_AUTHTOKEN_FILTER_FACTORY = 'keystoneclient.middleware.auth_token:filter_factory'
|
||||
|
||||
|
||||
class KeystoneAuthtokenSettingsInspection(Inspection):
|
||||
name = 'Keystone auth'
|
||||
description = 'Validate correctness of keystone settings'
|
||||
@ -14,7 +15,8 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
|
||||
|
||||
keystones = [c for c in components if c.name == 'keystone']
|
||||
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
|
||||
|
||||
keystone = keystones[0]
|
||||
@ -28,16 +30,22 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
|
||||
|
||||
(authtoken_section, _) = find(
|
||||
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)
|
||||
|
||||
|
||||
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_port = get_value('auth_port')
|
||||
@ -47,37 +55,85 @@ class KeystoneAuthtokenSettingsInspection(Inspection):
|
||||
admin_tenant_name = get_value('admin_tenant_name')
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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']:
|
||||
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:
|
||||
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']:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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'))
|
||||
|
@ -2,6 +2,7 @@ from urlparse import urlparse
|
||||
|
||||
from ostack_validator.common import Inspection, Issue, find
|
||||
|
||||
|
||||
class KeystoneEndpointsInspection(Inspection):
|
||||
name = 'Keystone endpoints'
|
||||
description = 'Validate that each keystone endpoint leads to proper service'
|
||||
@ -13,29 +14,41 @@ class KeystoneEndpointsInspection(Inspection):
|
||||
|
||||
for service in keystone.db['services']:
|
||||
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:
|
||||
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
|
||||
|
||||
for url_attr in ['adminurl', 'publicurl', 'internalurl']:
|
||||
url = urlparse(endpoint[url_attr])
|
||||
|
||||
# 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:
|
||||
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
|
||||
|
||||
nova_compute = None
|
||||
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:
|
||||
nova_compute = c
|
||||
break
|
||||
|
||||
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)))
|
||||
|
@ -4,18 +4,21 @@ from lettuce import *
|
||||
from ostack_validator.common import Issue, Version, find
|
||||
from ostack_validator.model import *
|
||||
|
||||
|
||||
def get_variable(name):
|
||||
if not hasattr(world, 'variables'):
|
||||
return None
|
||||
|
||||
return world.variables.get(name)
|
||||
|
||||
|
||||
def set_variable(name, value):
|
||||
if not hasattr(world, 'variables'):
|
||||
world.variables = {}
|
||||
|
||||
world.variables[name] = value
|
||||
|
||||
|
||||
def subst(template):
|
||||
if not hasattr(world, 'variables'):
|
||||
return template
|
||||
@ -23,39 +26,49 @@ def subst(template):
|
||||
tmpl = string.Template(template)
|
||||
return tmpl.safe_substitute(world.variables)
|
||||
|
||||
|
||||
def stop():
|
||||
assert False, "stop"
|
||||
|
||||
|
||||
# Openstack general step description section
|
||||
@step(r'I use OpenStack (\w+)')
|
||||
def use_openstack_version(step, version):
|
||||
version = Version(version)
|
||||
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+)')
|
||||
def controller_addresses(self, variable):
|
||||
controller = find(world.openstack.components, lambda c: c.name == 'nova')
|
||||
|
||||
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:
|
||||
addresses = [controller.config['s3_host']]
|
||||
|
||||
set_variable(variable, addresses)
|
||||
|
||||
|
||||
# Keystone steps section
|
||||
@step(r'Keystone addresses are @(\w+)')
|
||||
def keystone_addresses(self, variable):
|
||||
keystone = find(world.openstack.components, lambda c: c.name == 'keystone')
|
||||
|
||||
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:
|
||||
addresses = [keystone.config['bind_host']]
|
||||
|
||||
set_variable(variable, addresses)
|
||||
|
||||
|
||||
# Nova steps section
|
||||
@step(r'Nova has "(.+)" equal to "(.*)"')
|
||||
def nova_has_property(step, name, value):
|
||||
@ -63,7 +76,9 @@ def nova_has_property(step, name, value):
|
||||
value = subst(value)
|
||||
|
||||
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 "(.*)"')
|
||||
def nova_property_assertion(self, name, values):
|
||||
@ -77,4 +92,6 @@ def nova_property_assertion(self, name, values):
|
||||
nova_value = nova.config[name]
|
||||
|
||||
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)))
|
||||
|
@ -3,7 +3,9 @@ import lettuce
|
||||
|
||||
from ostack_validator.common import Inspection, Issue
|
||||
|
||||
|
||||
class LettuceRunnerInspection(Inspection):
|
||||
|
||||
def inspect(self, openstack):
|
||||
runner = lettuce.Runner(
|
||||
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 scenario_result in [s for s in feature_result.scenario_results if not s.passed]:
|
||||
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))
|
||||
|
@ -5,9 +5,14 @@ import argparse
|
||||
from ostack_validator.model_parser import ModelParser
|
||||
from ostack_validator.inspection import MainConfigValidationInspection
|
||||
|
||||
|
||||
def main(args):
|
||||
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')
|
||||
|
||||
args = parser.parse_args(args)
|
||||
@ -39,4 +44,3 @@ def main(args):
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
|
||||
|
@ -10,7 +10,9 @@ import ostack_validator.schemas
|
||||
from ostack_validator.config_formats import IniConfigParser
|
||||
from ostack_validator.utils import memoized
|
||||
|
||||
|
||||
class IssueReporter(object):
|
||||
|
||||
def __init__(self):
|
||||
super(IssueReporter, self).__init__()
|
||||
self.issues = []
|
||||
@ -23,13 +25,16 @@ class IssueReporter(object):
|
||||
def all_issues(self):
|
||||
return list(self.issues)
|
||||
|
||||
|
||||
class Openstack(IssueReporter):
|
||||
|
||||
def __init__(self):
|
||||
super(Openstack, self).__init__()
|
||||
self.hosts = []
|
||||
|
||||
def add_host(self, host):
|
||||
if not host: return
|
||||
if not host:
|
||||
return
|
||||
|
||||
self.hosts.append(host)
|
||||
host.parent = self
|
||||
@ -53,6 +58,7 @@ class Openstack(IssueReporter):
|
||||
|
||||
|
||||
class Host(IssueReporter):
|
||||
|
||||
def __init__(self, name):
|
||||
super(Host, self).__init__()
|
||||
self.name = name
|
||||
@ -62,7 +68,8 @@ class Host(IssueReporter):
|
||||
return 'Host "%s"' % self.name
|
||||
|
||||
def add_component(self, component):
|
||||
if not component: return
|
||||
if not component:
|
||||
return
|
||||
|
||||
self.components.append(component)
|
||||
component.parent = self
|
||||
@ -82,6 +89,7 @@ class Host(IssueReporter):
|
||||
|
||||
|
||||
class Service(IssueReporter):
|
||||
|
||||
def __init__(self):
|
||||
super(Service, self).__init__()
|
||||
self.issues = []
|
||||
@ -102,7 +110,8 @@ class Service(IssueReporter):
|
||||
result = super(Service, self).all_issues
|
||||
|
||||
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
|
||||
|
||||
@ -119,7 +128,9 @@ class OpenstackComponent(Service):
|
||||
def config(self):
|
||||
schema = ConfigSchemaRegistry.get_schema(self.component, self.version)
|
||||
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 self._parse_config_resources(self.config_files, schema)
|
||||
@ -133,19 +144,33 @@ class OpenstackComponent(Service):
|
||||
if parameter.section == 'DEFAULT':
|
||||
config.set_default(parameter.name, parameter.default)
|
||||
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):
|
||||
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
|
||||
|
||||
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:
|
||||
def report_issue(issue):
|
||||
issue_reporter.report_issue(issue)
|
||||
else:
|
||||
def report_issue(issue): pass
|
||||
def report_issue(issue):
|
||||
pass
|
||||
|
||||
# Parse config file
|
||||
config_parser = IniConfigParser()
|
||||
@ -155,13 +180,21 @@ class OpenstackComponent(Service):
|
||||
|
||||
# Validate config parameters and store them
|
||||
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:
|
||||
sections = list(sections)
|
||||
|
||||
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()
|
||||
|
||||
@ -171,35 +204,52 @@ class OpenstackComponent(Service):
|
||||
unknown_section = not schema.has_section(section.name.text)
|
||||
|
||||
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
|
||||
|
||||
for parameter in section.parameters:
|
||||
parameter_schema = None
|
||||
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):
|
||||
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
|
||||
|
||||
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:
|
||||
seen_parameters.add(parameter.name.text)
|
||||
|
||||
parameter_fullname = parameter.name.text
|
||||
if section_name != 'DEFAULT':
|
||||
parameter_fullname = section_name + '.' + parameter_fullname
|
||||
parameter_fullname = section_name + \
|
||||
'.' + parameter_fullname
|
||||
|
||||
if parameter_schema:
|
||||
type_validator = TypeValidatorRegistry.get_validator(parameter_schema.type)
|
||||
type_validation_result = type_validator.validate(parameter.value.text)
|
||||
type_validator = TypeValidatorRegistry.get_validator(
|
||||
parameter_schema.type)
|
||||
type_validation_result = type_validator.validate(
|
||||
parameter.value.text)
|
||||
if isinstance(type_validation_result, Issue):
|
||||
type_validation_result.mark = parameter.value.start_mark.merge(type_validation_result.mark)
|
||||
type_validation_result.message = 'Property "%s" in section "%s": %s' % (parameter.name.text, section_name, type_validation_result.message)
|
||||
type_validation_result.mark = parameter.value.start_mark.merge(
|
||||
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)
|
||||
|
||||
config.set(parameter_fullname, parameter.value.text)
|
||||
config.set(
|
||||
parameter_fullname,
|
||||
parameter.value.text)
|
||||
else:
|
||||
value = type_validation_result
|
||||
|
||||
@ -208,7 +258,10 @@ class OpenstackComponent(Service):
|
||||
# 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))
|
||||
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:
|
||||
config.set(parameter_fullname, parameter.value.text)
|
||||
|
||||
@ -219,14 +272,17 @@ class KeystoneComponent(OpenstackComponent):
|
||||
component = 'keystone'
|
||||
name = 'keystone'
|
||||
|
||||
|
||||
class GlanceApiComponent(OpenstackComponent):
|
||||
component = 'glance'
|
||||
name = 'glance-api'
|
||||
|
||||
|
||||
class GlanceRegistryComponent(OpenstackComponent):
|
||||
component = 'glance'
|
||||
name = 'glance-registry'
|
||||
|
||||
|
||||
class NovaApiComponent(OpenstackComponent):
|
||||
component = 'nova'
|
||||
name = 'nova-api'
|
||||
@ -245,34 +301,43 @@ class NovaApiComponent(OpenstackComponent):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class NovaComputeComponent(OpenstackComponent):
|
||||
component = 'nova'
|
||||
name = 'nova-compute'
|
||||
|
||||
|
||||
class NovaSchedulerComponent(OpenstackComponent):
|
||||
component = 'nova'
|
||||
name = 'nova-scheduler'
|
||||
|
||||
|
||||
class CinderApiComponent(OpenstackComponent):
|
||||
component = 'cinder'
|
||||
name = 'cinder-api'
|
||||
|
||||
|
||||
class CinderVolumeComponent(OpenstackComponent):
|
||||
component = 'cinder'
|
||||
name = 'cinder-volume'
|
||||
|
||||
|
||||
class CinderSchedulerComponent(OpenstackComponent):
|
||||
component = 'cinder'
|
||||
name = 'cinder-scheduler'
|
||||
|
||||
|
||||
class MysqlComponent(Service):
|
||||
component = 'mysql'
|
||||
name = 'mysql'
|
||||
|
||||
|
||||
class RabbitMqComponent(Service):
|
||||
name = 'rabbitmq'
|
||||
|
||||
|
||||
class FileResource(IssueReporter):
|
||||
|
||||
def __init__(self, path, contents, owner, group, permissions):
|
||||
super(FileResource, self).__init__()
|
||||
self.path = path
|
||||
@ -283,4 +348,3 @@ class FileResource(IssueReporter):
|
||||
|
||||
def __str__(self):
|
||||
return 'File "%s"' % self.path
|
||||
|
||||
|
@ -4,6 +4,7 @@ import json
|
||||
|
||||
from ostack_validator.common import Error, Version
|
||||
|
||||
|
||||
class Resource(object):
|
||||
HOST = 'host'
|
||||
FILE = 'file'
|
||||
@ -21,29 +22,43 @@ class Resource(object):
|
||||
return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name)
|
||||
|
||||
def get_contents(self):
|
||||
raise Error, 'Not implemented'
|
||||
raise Error('Not implemented')
|
||||
|
||||
|
||||
class ResourceLocator(object):
|
||||
|
||||
def find_resource(self, resource_type, name, host=None, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
class HostResource(Resource):
|
||||
|
||||
def __init__(self, name, resource_locator, interfaces=[]):
|
||||
super(HostResource, self).__init__(name)
|
||||
self.resource_locator = resource_locator
|
||||
self.interfaces = interfaces
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, name, owner=None, group=None, permissions=None):
|
||||
super(DirectoryResource, self).__init__(name)
|
||||
self.owner = owner
|
||||
self.group = group
|
||||
self.permissions = permissions
|
||||
|
||||
|
||||
class FileResource(Resource):
|
||||
|
||||
def __init__(self, name, path, owner=None, group=None, permissions=None):
|
||||
super(FileResource, self).__init__(name)
|
||||
self.path = path
|
||||
@ -55,7 +70,9 @@ class FileResource(Resource):
|
||||
with open(self.path) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
class ServiceResource(Resource):
|
||||
|
||||
def __init__(self, name, version, metadata={}):
|
||||
super(ServiceResource, self).__init__(name)
|
||||
self.version = Version(version)
|
||||
@ -63,6 +80,7 @@ class ServiceResource(Resource):
|
||||
|
||||
|
||||
class FilesystemSnapshot(object):
|
||||
|
||||
def __init__(self, path):
|
||||
super(FilesystemSnapshot, self).__init__()
|
||||
self.path = path
|
||||
@ -77,28 +95,46 @@ class FilesystemSnapshot(object):
|
||||
|
||||
def _parse_snapshot(self):
|
||||
self._resources = {}
|
||||
if not os.path.isfile(self.path): return
|
||||
if not os.path.isfile(self.path):
|
||||
return
|
||||
with open(self.path) as f:
|
||||
for line in f.readlines():
|
||||
line = line.lstrip()
|
||||
if line == '' or line.startswith('#'): continue
|
||||
if line == '' or line.startswith('#'):
|
||||
continue
|
||||
|
||||
resource_type = line.split('|')[0]
|
||||
if resource_type == 'dir':
|
||||
source_path, owner, group, permissions = line.split('|')[1:]
|
||||
self._resources[source_path] = DirectoryResource(source_path, owner=owner, group=group, permissions=permissions)
|
||||
source_path, owner, group, permissions = line.split(
|
||||
'|')[1:]
|
||||
self._resources[source_path] = DirectoryResource(
|
||||
source_path,
|
||||
owner=owner,
|
||||
group=group,
|
||||
permissions=permissions)
|
||||
elif resource_type == 'file':
|
||||
source_path, local_path, owner, group, permissions = line.split('|')[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)
|
||||
source_path, local_path, owner, group, permissions = line.split(
|
||||
'|')[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:
|
||||
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):
|
||||
|
||||
def __init__(self, basedir):
|
||||
super(ConfigSnapshotResourceLocator, self).__init__()
|
||||
self.basedir = 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._filesystem_snapshots = {}
|
||||
|
||||
@ -110,10 +146,13 @@ class ConfigSnapshotResourceLocator(object):
|
||||
return None
|
||||
return HostResource(name, self)
|
||||
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 not host:
|
||||
raise Error, 'Invalid argument: "host" need to be specified'
|
||||
raise Error('Invalid argument: "host" need to be specified')
|
||||
|
||||
if isinstance(host, HostResource):
|
||||
host = host.name
|
||||
@ -124,7 +163,7 @@ class ConfigSnapshotResourceLocator(object):
|
||||
return []
|
||||
elif resource_type == Resource.SERVICE:
|
||||
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):
|
||||
host = host.name
|
||||
@ -142,24 +181,28 @@ class ConfigSnapshotResourceLocator(object):
|
||||
return None
|
||||
|
||||
def _ensure_services_loaded(self):
|
||||
if self._services: return
|
||||
if self._services:
|
||||
return
|
||||
|
||||
self._services = {}
|
||||
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')
|
||||
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)
|
||||
self._services[host_name] = {}
|
||||
with open(services_json_path) as f:
|
||||
for service_name, metadata in json.loads(f.read()).items():
|
||||
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):
|
||||
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]
|
||||
|
||||
|
@ -2,21 +2,30 @@ import sys
|
||||
|
||||
from ostack_validator.common import Issue, MarkedIssue, Mark, Version, find, index
|
||||
|
||||
|
||||
class SchemaUpdateRecord(object):
|
||||
# checkpoint's data is version number
|
||||
|
||||
def __init__(self, version, operation, data=None):
|
||||
super(SchemaUpdateRecord, self).__init__()
|
||||
if not operation in ['checkpoint', 'add', 'remove']:
|
||||
raise Error, 'Unknown operation "%s"' % operation
|
||||
raise Error('Unknown operation "%s"' % operation)
|
||||
version = Version(version)
|
||||
self.version = version
|
||||
self.operation = operation
|
||||
self.data = data
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, name, data):
|
||||
super(SchemaBuilder, self).__init__()
|
||||
self.name = name
|
||||
@ -29,7 +38,9 @@ class SchemaBuilder(object):
|
||||
|
||||
def __del__(self):
|
||||
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):
|
||||
version = Version(version)
|
||||
@ -63,18 +74,29 @@ class SchemaBuilder(object):
|
||||
self._ensure_version()
|
||||
|
||||
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 = []
|
||||
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 = []
|
||||
|
||||
def _ensure_version(self):
|
||||
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:
|
||||
__schemas = {}
|
||||
|
||||
@classmethod
|
||||
def register_schema(self, project, configname=None):
|
||||
if not configname:
|
||||
@ -96,7 +118,8 @@ class ConfigSchemaRegistry:
|
||||
records = self.__schemas[fullname]
|
||||
i = len(records) - 1
|
||||
# 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:
|
||||
return None
|
||||
@ -110,7 +133,9 @@ class ConfigSchemaRegistry:
|
||||
if records[i].operation == 'add':
|
||||
for param in records[i].data:
|
||||
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:
|
||||
parameters[old_param_index] = param
|
||||
else:
|
||||
@ -118,7 +143,9 @@ class ConfigSchemaRegistry:
|
||||
seen_parameters.add(param.name)
|
||||
elif records[i].operation == 'remove':
|
||||
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:
|
||||
parameters.pop(param_index)
|
||||
seen_parameters.remove(param_name)
|
||||
@ -128,6 +155,7 @@ class ConfigSchemaRegistry:
|
||||
|
||||
|
||||
class ConfigSchema:
|
||||
|
||||
def __init__(self, name, version, format, parameters):
|
||||
self.name = name
|
||||
self.version = Version(version)
|
||||
@ -135,17 +163,36 @@ class ConfigSchema:
|
||||
self.parameters = parameters
|
||||
|
||||
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):
|
||||
# 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):
|
||||
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:
|
||||
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.name = name
|
||||
self.type = type
|
||||
@ -155,11 +202,15 @@ class ConfigParameterSchema:
|
||||
self.deprecation_message = deprecation_message
|
||||
|
||||
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:
|
||||
__validators = {}
|
||||
|
||||
@classmethod
|
||||
def register_validator(self, type_name, type_validator):
|
||||
self.__validators[type_name] = type_validator
|
||||
@ -170,14 +221,25 @@ class TypeValidatorRegistry:
|
||||
|
||||
|
||||
class SchemaError(Issue):
|
||||
|
||||
def __init__(self, message):
|
||||
super(SchemaError, self).__init__(Issue.ERROR, message)
|
||||
|
||||
|
||||
class InvalidValueError(MarkedIssue):
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, f):
|
||||
super(TypeValidator, self).__init__()
|
||||
self.f = f
|
||||
@ -195,9 +257,11 @@ def type_validator(name, **kwargs):
|
||||
return fn
|
||||
return wrap
|
||||
|
||||
|
||||
def isissue(o):
|
||||
return isinstance(o, Issue)
|
||||
|
||||
|
||||
@type_validator('boolean')
|
||||
def validate_boolean(s):
|
||||
s = s.lower()
|
||||
@ -208,6 +272,7 @@ def validate_boolean(s):
|
||||
else:
|
||||
return InvalidValueError('Value should be "true" or "false"')
|
||||
|
||||
|
||||
def validate_enum(s, values=[]):
|
||||
if s in values:
|
||||
return None
|
||||
@ -216,9 +281,11 @@ def validate_enum(s, values=[]):
|
||||
elif len(values) == 1:
|
||||
message = 'The only valid value is %s' % values[0]
|
||||
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)
|
||||
|
||||
|
||||
def validate_ipv4_address(s):
|
||||
s = s.strip()
|
||||
parts = s.split('.')
|
||||
@ -230,11 +297,15 @@ def validate_ipv4_address(s):
|
||||
|
||||
return InvalidValueError('Value should be ipv4 address')
|
||||
|
||||
|
||||
def validate_ipv4_network(s):
|
||||
s = s.strip()
|
||||
parts = s.split('/')
|
||||
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
|
||||
prefix = prefix.strip()
|
||||
@ -251,32 +322,51 @@ def validate_ipv4_network(s):
|
||||
|
||||
prefix = int(prefix)
|
||||
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)
|
||||
|
||||
|
||||
def validate_host_label(s):
|
||||
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():
|
||||
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()):
|
||||
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]):
|
||||
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
|
||||
|
||||
|
||||
|
||||
@type_validator('host')
|
||||
@type_validator('host_address')
|
||||
def validate_host_address(s):
|
||||
@ -299,11 +389,13 @@ def validate_host_address(s):
|
||||
|
||||
return '.'.join(labels)
|
||||
|
||||
|
||||
@type_validator('network')
|
||||
@type_validator('network_address')
|
||||
def validate_network_address(s):
|
||||
return validate_ipv4_network(s)
|
||||
|
||||
|
||||
@type_validator('host_and_port')
|
||||
def validate_host_and_port(s, default_port=None):
|
||||
parts = s.strip().split(':', 2)
|
||||
@ -323,16 +415,19 @@ def validate_host_and_port(s, default_port=None):
|
||||
|
||||
return (host_address, port)
|
||||
|
||||
|
||||
@type_validator('string')
|
||||
@type_validator('list')
|
||||
@type_validator('multi')
|
||||
def validate_string(s):
|
||||
return s
|
||||
|
||||
|
||||
@type_validator('integer')
|
||||
def validate_integer(s, min=None, max=None):
|
||||
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()
|
||||
if s == '':
|
||||
@ -340,25 +435,40 @@ def validate_integer(s, min=None, max=None):
|
||||
|
||||
for i, c in enumerate(s):
|
||||
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)
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
@type_validator('float')
|
||||
def validate_float(s):
|
||||
# TODO: Implement proper validation
|
||||
return float(s)
|
||||
|
||||
|
||||
@type_validator('port')
|
||||
def validate_port(s, min=1, max=65535):
|
||||
return validate_integer(s, min=min, max=max)
|
||||
|
||||
|
||||
@type_validator('string_list')
|
||||
def validate_list(s, element_type='string'):
|
||||
element_type_validator = TypeValidatorRegistry.get_validator(element_type)
|
||||
@ -381,6 +491,7 @@ def validate_list(s, element_type='string'):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@type_validator('string_dict')
|
||||
def validate_dict(s, element_type='string'):
|
||||
element_type_validator = TypeValidatorRegistry.get_validator(element_type)
|
||||
@ -397,7 +508,10 @@ def validate_dict(s, element_type='string'):
|
||||
for pair in pairs:
|
||||
key_value = pair.split(':', 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 = key.strip()
|
||||
@ -413,4 +527,3 @@ def validate_dict(s, element_type='string'):
|
||||
return validated_value
|
||||
result[key] = validated_value
|
||||
return result
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import ostack_validator.schemas.keystone
|
||||
import ostack_validator.schemas.nova
|
||||
import ostack_validator.schemas.cinder
|
||||
|
||||
|
@ -1,2 +1 @@
|
||||
import ostack_validator.schemas.cinder.v2013_1_3
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +1 @@
|
||||
import ostack_validator.schemas.keystone.v2013_1_3
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,2 @@
|
||||
import ostack_validator.schemas.nova.v2013_1
|
||||
import ostack_validator.schemas.nova.v2013_1_3
|
||||
|
||||
|
@ -11,7 +11,10 @@ nova.section('DEFAULT')
|
||||
|
||||
# availability_zone to show internal services under (string
|
||||
# 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_availability_zone=nova
|
||||
@ -296,7 +299,8 @@ nova.param('use_ipv6', type='boolean')
|
||||
# generate log lines. The following values can be formatted
|
||||
# into it: client_ip, date_time, request_line, status_code,
|
||||
# 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
|
||||
# (string value)
|
||||
@ -389,7 +393,8 @@ nova.param('use_ipv6', type='boolean')
|
||||
|
||||
# List of metadata versions to skip placing into the config
|
||||
# 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)
|
||||
# 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
|
||||
# of default WARNING level). (boolean value)
|
||||
# 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)
|
||||
# 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
|
||||
# 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
|
||||
# (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
|
||||
# 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
|
||||
# (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)
|
||||
# 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
|
||||
|
||||
nova.commit()
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ import sys
|
||||
|
||||
|
||||
class SchemaParser(object):
|
||||
|
||||
def parse_args(self, argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--conf', dest='conf_file', default=None,
|
||||
|
@ -3,7 +3,9 @@ from ostack_validator.common import find
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class ConfigSchemaRegistryTests(unittest.TestCase):
|
||||
|
||||
def test_sample(self):
|
||||
nova = ConfigSchemaRegistry.register_schema(project='nova')
|
||||
|
||||
@ -19,30 +21,39 @@ class ConfigSchemaRegistryTests(unittest.TestCase):
|
||||
|
||||
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('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.assertEqual('boolean', verbose_param.type)
|
||||
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.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.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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -2,6 +2,7 @@ import unittest
|
||||
|
||||
from ostack_validator.config_model import Configuration
|
||||
|
||||
|
||||
class ConfigurationTests(unittest.TestCase):
|
||||
section = 'section1'
|
||||
param = 'param1'
|
||||
@ -23,7 +24,9 @@ class ConfigurationTests(unittest.TestCase):
|
||||
c = Configuration()
|
||||
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):
|
||||
c = Configuration()
|
||||
@ -35,7 +38,10 @@ class ConfigurationTests(unittest.TestCase):
|
||||
c = Configuration()
|
||||
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):
|
||||
c = Configuration()
|
||||
@ -68,7 +74,6 @@ class ConfigurationTests(unittest.TestCase):
|
||||
self.assertTrue(c.contains(self.fullparam))
|
||||
self.assertTrue(c.contains(self.fullparam, ignoreDefault=True))
|
||||
|
||||
|
||||
def test_is_default_returns_false_if_param_missing(self):
|
||||
c = Configuration()
|
||||
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('%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):
|
||||
c = Configuration()
|
||||
c.set('%s.param1' % self.section, 'value1')
|
||||
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):
|
||||
c = Configuration()
|
||||
@ -202,4 +209,3 @@ class ConfigurationTests(unittest.TestCase):
|
||||
c.set('b', 'x')
|
||||
|
||||
self.assertEqual('$b', c.get('a', raw=True))
|
||||
|
||||
|
@ -2,7 +2,9 @@ from ostack_validator.common import Mark
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class MarkTests(unittest.TestCase):
|
||||
|
||||
def test_creation(self):
|
||||
m = Mark('nova.conf', 3, 5)
|
||||
self.assertEqual('nova.conf', m.source)
|
||||
@ -18,4 +20,3 @@ class MarkTests(unittest.TestCase):
|
||||
self.assertEqual(m1.source, m.source)
|
||||
self.assertEqual(m1.line + m2.line, m.line)
|
||||
self.assertEqual(m1.column + m2.column, m.column)
|
||||
|
||||
|
@ -3,7 +3,9 @@ from ostack_validator.schema import TypeValidatorRegistry
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TypeValidatorTestHelper(object):
|
||||
|
||||
def setUp(self):
|
||||
super(TypeValidatorTestHelper, self).setUp()
|
||||
self.validator = TypeValidatorRegistry.get_validator(self.type_name)
|
||||
@ -14,6 +16,7 @@ class TypeValidatorTestHelper(object):
|
||||
def assertInvalid(self, value):
|
||||
self.assertIsInstance(self.validator.validate(value), Issue)
|
||||
|
||||
|
||||
class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'string'
|
||||
|
||||
@ -27,6 +30,7 @@ class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
s = 'foo bar'
|
||||
self.assertEqual(s, self.validator.validate(s))
|
||||
|
||||
|
||||
class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'boolean'
|
||||
|
||||
@ -41,6 +45,7 @@ class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
def test_other_values_produce_error(self):
|
||||
self.assertInvalid('foo')
|
||||
|
||||
|
||||
class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'integer'
|
||||
|
||||
@ -67,7 +72,8 @@ class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
self.assertIsInstance(error, MarkedIssue)
|
||||
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')
|
||||
self.assertIsInstance(error, MarkedIssue)
|
||||
self.assertEqual(5, error.mark.column)
|
||||
@ -76,6 +82,7 @@ class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
v = self.validator.validate('123')
|
||||
self.assertEqual(123, v)
|
||||
|
||||
|
||||
class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'host_address'
|
||||
|
||||
@ -127,6 +134,7 @@ class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
e = self.validator.validate('foo.bar.-baz')
|
||||
self.assertEqual(8, e.mark.column)
|
||||
|
||||
|
||||
class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'network_address'
|
||||
|
||||
@ -154,6 +162,7 @@ class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCas
|
||||
def test_prefix_greater_than_32(self):
|
||||
self.assertInvalid('10.0.0.0/33')
|
||||
|
||||
|
||||
class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'port'
|
||||
|
||||
@ -190,6 +199,7 @@ class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
v = self.validator.validate('123')
|
||||
self.assertEqual(123, v)
|
||||
|
||||
|
||||
class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'host_and_port'
|
||||
|
||||
@ -217,6 +227,7 @@ class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
def test_port_is_greater_than_65535(self):
|
||||
self.assertInvalid('10.0.0.1:65536')
|
||||
|
||||
|
||||
class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'string_list'
|
||||
|
||||
@ -239,6 +250,7 @@ class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
self.assertEqual('baz', v[1])
|
||||
self.assertEqual(2, len(v))
|
||||
|
||||
|
||||
class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
type_name = 'string_dict'
|
||||
|
||||
@ -264,4 +276,3 @@ class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -2,6 +2,7 @@ from ostack_validator.schema import Version
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class VersionTests(unittest.TestCase):
|
||||
|
||||
def test_creation_from_components(self):
|
||||
@ -60,4 +61,3 @@ class VersionTests(unittest.TestCase):
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -1,14 +1,18 @@
|
||||
import collections
|
||||
import functools
|
||||
|
||||
|
||||
class memoized(object):
|
||||
|
||||
'''Decorator. Caches a function's return value each time it is called.
|
||||
If called later with the same arguments, the cached value is returned
|
||||
(not reevaluated).
|
||||
'''
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
self.cache = {}
|
||||
|
||||
def __call__(self, *args):
|
||||
if not isinstance(args, collections.Hashable):
|
||||
# uncacheable. a list, for instance.
|
||||
@ -20,10 +24,11 @@ class memoized(object):
|
||||
value = self.func(*args)
|
||||
self.cache[args] = value
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
'''Return the function's docstring.'''
|
||||
return self.func.__doc__
|
||||
|
||||
def __get__(self, obj, objtype):
|
||||
'''Support instance methods.'''
|
||||
return functools.partial(self.__call__, obj)
|
||||
|
||||
|
@ -21,7 +21,10 @@ app.secret_key = 'A0Zr98j/3fooN]LWX/,?RT'
|
||||
|
||||
class ValidationLaunchForm(Form):
|
||||
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()])
|
||||
|
||||
launch = SubmitField('Launch validation')
|
||||
@ -36,15 +39,21 @@ def to_label(s):
|
||||
else:
|
||||
return 'label-info'
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return redirect('/validation')
|
||||
|
||||
|
||||
@app.route('/validation', methods=['GET', 'POST'])
|
||||
def launch_validation():
|
||||
form = ValidationLaunchForm()
|
||||
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)
|
||||
|
||||
@ -52,6 +61,7 @@ def launch_validation():
|
||||
else:
|
||||
return render_template('validation_form.html', form=form)
|
||||
|
||||
|
||||
@app.route('/validation/<id>')
|
||||
def job(id):
|
||||
job = celery.AsyncResult(id)
|
||||
@ -66,15 +76,29 @@ def job(id):
|
||||
openstack = job.result.value
|
||||
|
||||
if isinstance(openstack, Openstack):
|
||||
issue_source_f = lambda i: i.mark.source if isinstance(i, MarkedIssue) else None
|
||||
source_groupped_issues = groupby(sorted(openstack.issues, key=issue_source_f), key=issue_source_f)
|
||||
issue_source_f = lambda i: i.mark.source if isinstance(
|
||||
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:
|
||||
return render_template('validation_error.html', form=form, message=openstack)
|
||||
return (
|
||||
render_template(
|
||||
'validation_error.html',
|
||||
form=form,
|
||||
message=openstack)
|
||||
)
|
||||
else:
|
||||
return render_template('validation_state.html', state=job.state)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', debug=True)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user