PEP8 fixes

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

View File

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

View File

@ -15,51 +15,61 @@ backend_url = os.getenv('CELERY_RESULT_BACKEND', broker_url)
app = Celery('ostack_validator', broker=broker_url, backend=backend_url) app = Celery('ostack_validator', broker=broker_url, backend=backend_url)
app.conf.update( app.conf.update(
CELERY_TRACK_STARTED=True CELERY_TRACK_STARTED=True
) )
class InspectionRequest(object): class InspectionRequest(object):
def __init__(self, nodes, username, password=None, private_key=None):
super(InspectionRequest, self).__init__() def __init__(self, nodes, username, password=None, private_key=None):
self.nodes = nodes super(InspectionRequest, self).__init__()
self.username = username self.nodes = nodes
self.password = password self.username = username
self.private_key = private_key self.password = password
self.private_key = private_key
class InspectionResult(object): class InspectionResult(object):
def __init__(self, request, value):
super(InspectionResult, self).__init__() def __init__(self, request, value):
self.request = request super(InspectionResult, self).__init__()
self.value = value self.request = request
self.value = value
@app.task @app.task
def ostack_inspect_task(request): def ostack_inspect_task(request):
logger = logging.getLogger('ostack_validator.task.inspect') logger = logging.getLogger('ostack_validator.task.inspect')
discovery = OpenstackDiscovery() discovery = OpenstackDiscovery()
try: try:
openstack = discovery.discover(request.nodes, request.username, private_key=request.private_key) openstack = discovery.discover(request.nodes, request.username,
except: private_key=request.private_key)
message = traceback.format_exc() except:
logger.error(message) message = traceback.format_exc()
logger.error(message)
return InspectionResult(request, message) return InspectionResult(request, message)
all_inspections = [KeystoneAuthtokenSettingsInspection] all_inspections = [KeystoneAuthtokenSettingsInspection]
for inspection in all_inspections: for inspection in all_inspections:
try: try:
x = inspection() x = inspection()
x.inspect(openstack) x.inspect(openstack)
except: except:
message = traceback.format_exc() message = traceback.format_exc()
logger.error(message) logger.error(message)
openstack.report_issue(Issue(Issue.ERROR, 'Unexpected error running inspection "%s". See log for details' % inspection.name)) openstack.report_issue(
Issue(
Issue.ERROR,
'Unexpected error running inspection "%s". See log for details' %
inspection.name))
return InspectionResult(request, openstack)
return InspectionResult(request, openstack)
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
logging.getLogger('ostack_validator').setLevel(logging.DEBUG) logging.getLogger('ostack_validator').setLevel(logging.DEBUG)
app.start()
app.start()

View File

@ -1,146 +1,188 @@
import copy import copy
def find(l, predicate): def find(l, predicate):
results = [x for x in l if predicate(x)] results = [x for x in l if predicate(x)]
return results[0] if len(results) > 0 else None return results[0] if len(results) > 0 else None
def index(l, predicate): def index(l, predicate):
i = 0 i = 0
while i<len(l): while i < len(l):
if predicate(l[i]): if predicate(l[i]):
return i return i
i += 1 i += 1
return -1 return -1
def all_subclasses(klass): def all_subclasses(klass):
subclasses = klass.__subclasses__() subclasses = klass.__subclasses__()
for d in list(subclasses): for d in list(subclasses):
subclasses.extend(all_subclasses(d)) subclasses.extend(all_subclasses(d))
return subclasses return subclasses
def path_relative_to(path, base_path): def path_relative_to(path, base_path):
if not path.startswith('/'): if not path.startswith('/'):
path = os.path.join(base_path, paste_config_path) path = os.path.join(base_path, paste_config_path)
return path
return path
class Version: class Version:
def __init__(self, major, minor=0, maintenance=0):
"Create Version object by either passing 3 integers, one string or an another Version object"
if isinstance(major, str):
self.parts = [int(x) for x in major.split('.', 3)]
while len(self.parts) < 3:
self.parts.append(0)
elif isinstance(major, Version): def __init__(self, major, minor=0, maintenance=0):
self.parts = major.parts "Create Version object by either passing 3 integers, one string or an another Version object"
else: if isinstance(major, str):
self.parts = [int(major), int(minor), int(maintenance)] self.parts = [int(x) for x in major.split('.', 3)]
while len(self.parts) < 3:
self.parts.append(0)
@property elif isinstance(major, Version):
def major(self): self.parts = major.parts
return self.parts[0] else:
self.parts = [int(major), int(minor), int(maintenance)]
@major.setter @property
def major(self, value): def major(self):
self.parts[0] = int(value) return self.parts[0]
@property @major.setter
def minor(self): def major(self, value):
return self.parts[1] self.parts[0] = int(value)
@minor.setter @property
def minor(self, value): def minor(self):
self.parts[1] = int(value) return self.parts[1]
@property @minor.setter
def maintenance(self): def minor(self, value):
return self.parts[2] self.parts[1] = int(value)
@maintenance.setter @property
def maintenance(self, value): def maintenance(self):
self.parts[2] = value return self.parts[2]
def __str__(self): @maintenance.setter
return '.'.join([str(p) for p in self.parts]) def maintenance(self, value):
self.parts[2] = value
def __repr__(self): def __str__(self):
return '<Version %s>' % str(self) return '.'.join([str(p) for p in self.parts])
def __cmp__(self, other): def __repr__(self):
for i in xrange(0, 3): return '<Version %s>' % str(self)
x = self.parts[i] - other.parts[i]
if x != 0:
return -1 if x < 0 else 1
return 0 def __cmp__(self, other):
for i in xrange(0, 3):
x = self.parts[i] - other.parts[i]
if x != 0:
return -1 if x < 0 else 1
return 0
class Mark(object): class Mark(object):
def __init__(self, source, line=0, column=0):
self.source = source
self.line = line
self.column = column
def __eq__(self, other): def __init__(self, source, line=0, column=0):
return (self.source == source) and (self.line == other.line) and (self.column == other.column) self.source = source
self.line = line
self.column = column
def __ne__(self, other): def __eq__(self, other):
return not self == other return (
(self.source == source) and (
self.line == other.line) and (self.column == other.column)
)
def merge(self, other): def __ne__(self, other):
return Mark(self.source, self.line + other.line, self.column + other.column) return not self == other
def merge(self, other):
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)
def __repr__(self):
return '%s line %d column %d' % (self.source, self.line, self.column)
class Error: class Error:
def __init__(self, message):
self.message = message
def __repr__(self): def __init__(self, message):
return '<%s "%s">' % (str(self.__class__).split('.')[-1][:-2], self.message) self.message = message
def __repr__(self):
return (
'<%s "%s">' % (
str(self.__class__).split('.')[-1][:-2],
self.message)
)
def __str__(self):
return self.message
def __str__(self):
return self.message
class Issue(object): class Issue(object):
FATAL = 'FATAL' FATAL = 'FATAL'
ERROR = 'ERROR' ERROR = 'ERROR'
WARNING = 'WARNING' WARNING = 'WARNING'
INFO = 'INFO' INFO = 'INFO'
def __init__(self, type, message): def __init__(self, type, message):
self.type = type self.type = type
self.message = message self.message = message
def __repr__(self): def __repr__(self):
return '<%s type=%s message=%s>' % (str(self.__class__).split('.')[-1][:-2], self.type, self.message) return (
'<%s type=%s message=%s>' % (
str(self.__class__).split('.')[-1][:-2],
self.type,
self.message)
)
def __str__(self):
return '%s: %s' % (self.type, self.message)
def __str__(self):
return '%s: %s' % (self.type, self.message)
class MarkedIssue(Issue): class MarkedIssue(Issue):
def __init__(self, type, message, mark):
super(MarkedIssue, self).__init__(type, message)
self.mark = mark
def offset_by(self, base_mark): def __init__(self, type, message, mark):
other = copy.copy(self) super(MarkedIssue, self).__init__(type, message)
other.mark = base_mark.merge(self.mark) self.mark = mark
return other
def __repr__(self): def offset_by(self, base_mark):
return '<%s type=%s message=%s mark=%s>' % (str(self.__class__).split('.')[-1][:-2], self.type, self.message, self.mark) other = copy.copy(self)
other.mark = base_mark.merge(self.mark)
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)
)
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))
)
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))
class Inspection(object): class Inspection(object):
@classmethod
def all_inspections(klass):
return [c for c in all_subclasses(klass)]
def inspect(self, openstack): @classmethod
pass def all_inspections(klass):
return [c for c in all_subclasses(klass)]
def inspect(self, openstack):
pass

View File

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

View File

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

View File

@ -4,123 +4,150 @@ from StringIO import StringIO
from ostack_validator.config_model import * from ostack_validator.config_model import *
from ostack_validator.config_formats.common import * from ostack_validator.config_formats.common import *
class IniConfigParser: class IniConfigParser:
key_value_re = re.compile("^(\S+?)\s*([:=])\s*('.*'|\".*\"|.*)\s*$") key_value_re = re.compile("^(\S+?)\s*([:=])\s*('.*'|\".*\"|.*)\s*$")
def parse(self, name, base_mark, io): def parse(self, name, base_mark, io):
if not hasattr(io, 'readlines'): if not hasattr(io, 'readlines'):
io = StringIO(io) io = StringIO(io)
def mark(line, column=0): def mark(line, column=0):
return base_mark.merge(Mark('', line, column)) return base_mark.merge(Mark('', line, column))
errors = []
current_section_name = ConfigSectionName(mark(0), mark(0), '')
current_param_name = None
current_param_value = None
current_param_delimiter = None
sections = []
parameters = []
line_number = -1
for line in io.readlines():
line = line.rstrip()
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)
parameters.append(param)
errors = []
current_section_name = ConfigSectionName(mark(0), mark(0), '')
current_param_name = None current_param_name = None
current_param_value = None current_param_value = None
current_param_delimiter = None current_param_delimiter = None
sections = []
parameters = []
if line == '': continue line_number = -1
for line in io.readlines():
line = line.rstrip()
if line[0] in '#;': continue 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)
parameters.append(param)
current_param_name = None
current_param_value = None
current_param_delimiter = None
if line == '':
continue
if line[0] in '#;':
continue
if line[0].isspace():
if current_param_name:
current_param_value.end_mark = mark(line_number, len(line))
current_param_value.text += line.lstrip()
continue
else:
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))))
end_index = len(line)
while line[end_index - 1].isspace():
end_index -= 1
if end_index <= 1:
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)))
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)
sections.append(section)
parameters = []
current_section_name = ConfigSectionName(
mark(line_number, 0),
mark(line_number, end_index),
line[1:end_index]
)
else:
m = self.key_value_re.match(line)
if m:
current_param_name = ConfigParameterName(
mark(line_number, m.start(1)),
mark(line_number, m.end(1)),
m.group(1)
)
current_param_delimiter = TextElement(
mark(line_number, m.start(2)),
mark(line_number, m.end(2)),
m.group(2)
)
# Unquote value
value = m.group(3)
quotechar = None
if len(value) > 0 and (value[0] == value[-1] and value[0] in "\"'"):
quotechar = value[0]
value = value[1:-1]
current_param_value = ConfigParameterValue(
mark(line_number, m.start(3)),
mark(line_number, m.end(3)),
value,
quotechar=quotechar
)
else:
errors.append(
ParseError('Syntax error in line "%s"' %
line, mark(line_number)))
if line[0].isspace():
if current_param_name: if current_param_name:
current_param_value.end_mark = mark(line_number, len(line)) param = ConfigParameter(
current_param_value.text += line.lstrip() current_param_name.start_mark,
continue current_param_value.end_mark,
else: current_param_name,
errors.append(ParseError('Unexpected multiline value continuation', mark(line_number))) current_param_value,
continue current_param_delimiter)
parameters.append(param)
if line[0] == '[':
end_index = line.find(']')
if end_index == -1:
errors.append(ParseError('Unclosed section', mark(line_number, len(line))))
end_index = len(line)
while line[end_index-1].isspace(): end_index -= 1
if end_index <= 1:
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)))
break
i += 1
if current_section_name.text != '' or len(parameters) > 0: if current_section_name.text != '' or len(parameters) > 0:
section = ConfigSection(current_section_name.start_mark, mark(line_number), current_section_name, parameters) section = ConfigSection(
sections.append(section) current_section_name.start_mark,
parameters = [] mark(line_number),
current_section_name,
parameters)
sections.append(section)
parameters = []
current_section_name = ConfigSectionName( end_mark = base_mark
mark(line_number, 0), if len(sections) > 0:
mark(line_number, end_index), end_mark = base_mark.merge(sections[-1].end_mark)
line[1:end_index]
)
else:
m = self.key_value_re.match(line)
if m:
current_param_name = ConfigParameterName(
mark(line_number, m.start(1)),
mark(line_number, m.end(1)),
m.group(1)
)
current_param_delimiter = TextElement(
mark(line_number, m.start(2)),
mark(line_number, m.end(2)),
m.group(2)
)
# Unquote value config = ComponentConfig(base_mark, end_mark, name, sections, errors)
value = m.group(3)
quotechar = None
if len(value) > 0 and (value[0] == value[-1] and value[0] in "\"'"):
quotechar = value[0]
value = value[1:-1]
current_param_value = ConfigParameterValue(
mark(line_number, m.start(3)),
mark(line_number, m.end(3)),
value,
quotechar=quotechar
)
else:
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)
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)
sections.append(section)
parameters = []
end_mark = base_mark
if len(sections) > 0:
end_mark = base_mark.merge(sections[-1].end_mark)
config = ComponentConfig(base_mark, end_mark, name, sections, errors)
return config
return config

View File

@ -2,214 +2,229 @@ import unittest
from ostack_validator.config_formats.ini import * from ostack_validator.config_formats.ini import *
class IniConfigParserTests(unittest.TestCase): class IniConfigParserTests(unittest.TestCase):
def setUp(self):
self.parser = IniConfigParser()
def _strip_margin(self, content): def setUp(self):
lines = content.split("\n") self.parser = IniConfigParser()
if lines[0] == '' and lines[-1].strip() == '':
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
stripped_lines = [line[margin_size:] for line in lines] def _strip_margin(self, content):
lines = content.split("\n")
if lines[0] == '' and lines[-1].strip() == '':
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
return "\n".join(stripped_lines) stripped_lines = [line[margin_size:] for line in lines]
def parse(self, content, margin=False): return "\n".join(stripped_lines)
if margin:
content = self._strip_margin(content)
return self.parser.parse('test.conf', Mark(''), content) def parse(self, content, margin=False):
if margin:
content = self._strip_margin(content)
def test_parsing(self): return self.parser.parse('test.conf', Mark(''), content)
config = self.parse("param1 = value1")
self.assertEqual(0, len(config.errors)) def test_parsing(self):
config = self.parse("param1 = value1")
self.assertParameter('param1', 'value1', config.sections[0].parameters[0]) self.assertEqual(0, len(config.errors))
self.assertEqual(1, len(config.sections[0].parameters))
def test_colon_as_delimiter(self): self.assertParameter(
c = self.parse('param1 : value1') 'param1',
'value1',
config.sections[0].parameters[0])
self.assertEqual(1, len(config.sections[0].parameters))
self.assertEqual(0, len(c.errors)) def test_colon_as_delimiter(self):
self.assertParameter('param1', 'value1', c.sections[0].parameters[0]) c = self.parse('param1 : value1')
def test_use_colon_delimiter_if_it_comes_before_equals_sign(self): self.assertEqual(0, len(c.errors))
c = self.parse('param1: value=123') self.assertParameter('param1', 'value1', c.sections[0].parameters[0])
self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'value=123', c.sections[0].parameters[0])
def test_use_equals_delimiter_if_it_comes_before_colon(self): def test_use_colon_delimiter_if_it_comes_before_equals_sign(self):
c = self.parse('param1=value:123') c = self.parse('param1: value=123')
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'value:123', c.sections[0].parameters[0]) self.assertParameter(
'param1',
'value=123',
c.sections[0].parameters[0])
def test_wrapping_value_with_single_quotes(self): def test_use_equals_delimiter_if_it_comes_before_colon(self):
c = self.parse("param = 'foo bar'") c = self.parse('param1=value:123')
self.assertEqual(0, len(c.errors))
self.assertParameter(
'param1',
'value:123',
c.sections[0].parameters[0])
self.assertEqual(0, len(c.errors)) def test_wrapping_value_with_single_quotes(self):
self.assertParameter('param', 'foo bar', c.sections[0].parameters[0]) c = self.parse("param = 'foo bar'")
self.assertEqual("'", c.sections[0].parameters[0].value.quotechar)
def test_wrapping_value_with_single_quotes_and_trailing_whitespace(self): self.assertEqual(0, len(c.errors))
c = self.parse("param = 'foo bar' ") self.assertParameter('param', 'foo bar', c.sections[0].parameters[0])
self.assertEqual("'", c.sections[0].parameters[0].value.quotechar)
self.assertEqual(0, len(c.errors)) def test_wrapping_value_with_single_quotes_and_trailing_whitespace(self):
self.assertParameter('param', 'foo bar', c.sections[0].parameters[0]) c = self.parse("param = 'foo bar' ")
def test_wrapping_value_with_double_quotes(self): self.assertEqual(0, len(c.errors))
c = self.parse("param = \"foo bar\"") self.assertParameter('param', 'foo bar', c.sections[0].parameters[0])
self.assertEqual(0, len(c.errors)) def test_wrapping_value_with_double_quotes(self):
self.assertParameter('param', 'foo bar', c.sections[0].parameters[0]) c = self.parse("param = \"foo bar\"")
self.assertEqual('"', c.sections[0].parameters[0].value.quotechar)
def test_wrapping_value_with_double_quotes_and_trailing_whitespace(self): self.assertEqual(0, len(c.errors))
c = self.parse("param = \"foo bar\" ") self.assertParameter('param', 'foo bar', c.sections[0].parameters[0])
self.assertEqual('"', c.sections[0].parameters[0].value.quotechar)
self.assertEqual(0, len(c.errors)) def test_wrapping_value_with_double_quotes_and_trailing_whitespace(self):
self.assertParameter('param', 'foo bar', c.sections[0].parameters[0]) c = self.parse("param = \"foo bar\" ")
def test_parsing_iolike_source(self): self.assertEqual(0, len(c.errors))
c = self.parse(StringIO("param1 = value1")) self.assertParameter('param', 'foo bar', c.sections[0].parameters[0])
self.assertEqual(0, len(c.errors)) def test_parsing_iolike_source(self):
c = self.parse(StringIO("param1 = value1"))
self.assertParameter('param1', 'value1', c.sections[0].parameters[0]) self.assertEqual(0, len(c.errors))
self.assertEqual(1, len(c.sections[0].parameters))
def test_default_section_name(self): self.assertParameter('param1', 'value1', c.sections[0].parameters[0])
c = self.parse("param1 = value1") self.assertEqual(1, len(c.sections[0].parameters))
self.assertEqual('', c.sections[0].name.text) def test_default_section_name(self):
c = self.parse("param1 = value1")
def test_parsing_with_section(self): self.assertEqual('', c.sections[0].name.text)
c = self.parse("""
def test_parsing_with_section(self):
c = self.parse("""
[section1] [section1]
param1 = value1 param1 = value1
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertEqual('section1', c.sections[0].name.text) self.assertEqual('section1', c.sections[0].name.text)
self.assertEqual(1, len(c.sections[0].parameters)) self.assertEqual(1, len(c.sections[0].parameters))
def test_parsing_with_same_section(self): def test_parsing_with_same_section(self):
c = self.parse(""" c = self.parse("""
[section1] [section1]
param1 = value1 param1 = value1
param2 = value2 param2 = value2
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertEqual(2, len(c.sections[0].parameters)) self.assertEqual(2, len(c.sections[0].parameters))
def test_parsing_with_different_sections(self): def test_parsing_with_different_sections(self):
c = self.parse(""" c = self.parse("""
[section1] [section1]
param1 = value1 param1 = value1
[section2] [section2]
param2 = value2 param2 = value2
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertEqual('section1', c.sections[0].name.text) self.assertEqual('section1', c.sections[0].name.text)
self.assertParameter('param1', 'value1', c.sections[0].parameters[0]) self.assertParameter('param1', 'value1', c.sections[0].parameters[0])
self.assertEqual(1, len(c.sections[0].parameters)) self.assertEqual(1, len(c.sections[0].parameters))
self.assertEqual('section2', c.sections[1].name.text) self.assertEqual('section2', c.sections[1].name.text)
self.assertParameter('param2', 'value2', c.sections[1].parameters[0]) self.assertParameter('param2', 'value2', c.sections[1].parameters[0])
self.assertEqual(1, len(c.sections[1].parameters)) self.assertEqual(1, len(c.sections[1].parameters))
def test_whole_line_comments_starting_with_hash(self): def test_whole_line_comments_starting_with_hash(self):
c = self.parse("#param=value") c = self.parse("#param=value")
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertEqual(0, len(c.sections)) self.assertEqual(0, len(c.sections))
def test_whole_line_comments_starting_with_semicolon(self): def test_whole_line_comments_starting_with_semicolon(self):
c = self.parse(";param=value") c = self.parse(";param=value")
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertEqual(0, len(c.sections)) self.assertEqual(0, len(c.sections))
def test_hash_in_value_is_part_of_the_value(self): def test_hash_in_value_is_part_of_the_value(self):
c = self.parse("param=value#123") c = self.parse("param=value#123")
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter("param", "value#123", c.sections[0].parameters[0]) self.assertParameter("param", "value#123", c.sections[0].parameters[0])
def test_multiline_value(self): def test_multiline_value(self):
c = self.parse(""" c = self.parse("""
param1 = line1 param1 = line1
line2 line2
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'line1line2', c.sections[0].parameters[0]) self.assertParameter(
'param1',
'line1line2',
c.sections[0].parameters[0])
def test_multiline_value_finished_by_other_parameter(self): def test_multiline_value_finished_by_other_parameter(self):
c = self.parse(""" c = self.parse("""
param1 = foo param1 = foo
bar bar
param2 = baz param2 = baz
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'foobar', c.sections[0].parameters[0]) self.assertParameter('param1', 'foobar', c.sections[0].parameters[0])
def test_multiline_value_finished_by_empty_line(self): def test_multiline_value_finished_by_empty_line(self):
c = self.parse(""" c = self.parse("""
param1 = foo param1 = foo
bar bar
param2 = baz param2 = baz
""", margin=True) """, margin=True)
self.assertEqual(0, len(c.errors)) self.assertEqual(0, len(c.errors))
self.assertParameter('param1', 'foobar', c.sections[0].parameters[0]) self.assertParameter('param1', 'foobar', c.sections[0].parameters[0])
def test_unclosed_section_causes_error(self): def test_unclosed_section_causes_error(self):
c = self.parse("[section1\nparam1=123") c = self.parse("[section1\nparam1=123")
self.assertEqual(1, len(c.errors)) self.assertEqual(1, len(c.errors))
def test_missing_equals_sign_or_colon_causes_error(self):
c = self.parse("param1 value1")
self.assertEqual(1, len(c.errors))
def test_spaces_in_key_causes_error(self): def test_missing_equals_sign_or_colon_causes_error(self):
c = self.parse("param 1 = value1") c = self.parse("param1 value1")
self.assertEqual(1, len(c.errors)) self.assertEqual(1, len(c.errors))
def test_returning_multiple_errors(self): def test_spaces_in_key_causes_error(self):
c = self.parse("[unclosed section\npararm 1 = value1") c = self.parse("param 1 = value1")
self.assertEqual(2, len(c.errors)) self.assertEqual(1, len(c.errors))
def test_errors_doesnt_affect_valid_parameters(self): def test_returning_multiple_errors(self):
c = self.parse('param1 value1\nparam2 = value2') c = self.parse("[unclosed section\npararm 1 = value1")
self.assertEqual(1, len(c.errors)) self.assertEqual(2, len(c.errors))
self.assertParameter('param2', 'value2', c.sections[0].parameters[0])
def test_errors_doesnt_affect_valid_parameters(self):
c = self.parse('param1 value1\nparam2 = value2')
self.assertEqual(1, len(c.errors))
self.assertParameter('param2', 'value2', c.sections[0].parameters[0])
def _getattr(self, o, name): def _getattr(self, o, name):
if name.find('.') != -1: if name.find('.') != -1:
parts = name.split('.') parts = name.split('.')
o = getattr(o, parts[0]) o = getattr(o, parts[0])
if o == None: if o is None:
return None return None
return self._getattr(o, '.'.join(parts[1:])) return self._getattr(o, '.'.join(parts[1:]))
else: else:
return getattr(o, name) return getattr(o, name)
def assertAttributes(self, attribute_values, subject): def assertAttributes(self, attribute_values, subject):
for attr, expected in attribute_values.items(): for attr, expected in attribute_values.items():
actual = self._getattr(subject, attr) actual = self._getattr(subject, attr)
self.assertEqual(expected, actual, "%s expected to have %s = %s, but the value was %s" % (subject, attr, expected, actual)) self.assertEqual(
expected, actual, "%s expected to have %s = %s, but the value was %s" %
def assertParameter(self, name, value, o): (subject, attr, expected, actual))
self.assertAttributes({'name.text': name, 'value.text': value}, o)
def assertParameter(self, name, value, o):
self.assertAttributes({'name.text': name, 'value.text': value}, o)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,235 +2,306 @@ import string
from ostack_validator.common import Mark from ostack_validator.common import Mark
class ConfigurationSection(object): class ConfigurationSection(object):
def __init__(self, config, section):
super(ConfigurationSection, self).__init__()
self.config = config
self.section = section
def _combine_names(self, section, param): def __init__(self, config, section):
if section == 'DEFAULT': super(ConfigurationSection, self).__init__()
return param self.config = config
self.section = section
return '%s.%s' % (section, param) def _combine_names(self, section, param):
if section == 'DEFAULT':
return param
def get(self, name, *args, **kwargs): return '%s.%s' % (section, param)
return self.config.get(self._combine_names(self.section, name), *args, **kwargs)
def set(self, name, *args, **kwargs): def get(self, name, *args, **kwargs):
self.config.set(self._combine_names(self.section, name), *args, **kwargs) return (
self.config.get(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def set_default(self, name, *args, **kwargs): def set(self, name, *args, **kwargs):
self.config.set_default(self._combine_names(self.section, name), *args, **kwargs) self.config.set(
self._combine_names(
self.section,
name),
*args,
**kwargs)
def contains(self, name, *args, **kwargs): def set_default(self, name, *args, **kwargs):
return self.config.contains(self._combine_names(self.section, name), *args, **kwargs) self.config.set_default(
self._combine_names(
self.section,
name),
*args,
**kwargs)
def is_default(self, name, *args, **kwargs): def contains(self, name, *args, **kwargs):
return self.config.is_default(self._combine_names(self.section, name), *args, **kwargs) return (
self.config.contains(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def __getitem__(self, key): def is_default(self, name, *args, **kwargs):
return self.config.get(self._combine_names(self.section, key)) return (
self.config.is_default(
self._combine_names(
self.section,
name),
*args,
**kwargs)
)
def __setitem__(self, key, value): def __getitem__(self, key):
return self.config.set(self._combine_names(self.section, key), value) return self.config.get(self._combine_names(self.section, key))
def __contains__(self, key): def __setitem__(self, key, value):
return self.config.contains(self._combine_names(self.section, key)) return self.config.set(self._combine_names(self.section, key), value)
def keys(self): def __contains__(self, key):
return self.config.keys(self.section) return self.config.contains(self._combine_names(self.section, key))
def keys(self):
return self.config.keys(self.section)
def items(self, *args, **kwargs):
return self.config.items(self.section, *args, **kwargs)
def items(self, *args, **kwargs):
return self.config.items(self.section, *args, **kwargs)
class ConfigurationWrapper(object): class ConfigurationWrapper(object):
def __init__(self, config, state):
super(ConfigurationWrapper, self).__init__()
self.config = config
self.state = state
def __getitem__(self, key): def __init__(self, config, state):
if key in self.state: super(ConfigurationWrapper, self).__init__()
return '' self.config = config
self.state = state
return self.config.get(key, _state=self.state) def __getitem__(self, key):
if key in self.state:
return ''
return self.config.get(key, _state=self.state)
class Configuration(object): class Configuration(object):
def __init__(self):
super(Configuration, self).__init__()
self._defaults = dict()
self._normal = dict()
def _normalize_name(self, name): def __init__(self):
if name.find('.') == -1: super(Configuration, self).__init__()
section = 'DEFAULT' self._defaults = dict()
else: self._normal = dict()
section, name = name.split('.', 1)
return (section, name) def _normalize_name(self, name):
if name.find('.') == -1:
section = 'DEFAULT'
else:
section, name = name.split('.', 1)
def _combine_names(self, section, param): return (section, name)
if section == 'DEFAULT':
return param
return '%s.%s' % (section, param) def _combine_names(self, section, param):
if section == 'DEFAULT':
return param
def get(self, name, default=None, raw=False, _state=[]): return '%s.%s' % (section, param)
section, name = self._normalize_name(name)
if section in self._normal and name in self._normal[section]: def get(self, name, default=None, raw=False, _state=[]):
value = self._normal[section][name] section, name = self._normalize_name(name)
elif section in self._defaults and name in self._defaults[section]:
value = self._defaults[section][name]
else:
value = default
if not isinstance(value, str): if section in self._normal and name in self._normal[section]:
return value value = self._normal[section][name]
elif section in self._defaults and name in self._defaults[section]:
value = self._defaults[section][name]
else:
value = default
if raw: if not isinstance(value, str):
return value return value
tmpl = string.Template(value) if raw:
return tmpl.safe_substitute(ConfigurationWrapper(self, _state + [name])) return value
def contains(self, name, ignoreDefault=False): tmpl = string.Template(value)
section, name = self._normalize_name(name) return (
tmpl.safe_substitute(ConfigurationWrapper(self, _state + [name]))
)
if section in self._normal and name in self._normal[section]: def contains(self, name, ignoreDefault=False):
return True section, name = self._normalize_name(name)
if not ignoreDefault and section in self._defaults and name in self._defaults[section]: if section in self._normal and name in self._normal[section]:
return True return True
return False if not ignoreDefault and section in self._defaults and name in self._defaults[section]:
return True
def is_default(self, name): return False
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]) def is_default(self, name):
section, name = self._normalize_name(name)
def set_default(self, name, value): return (
section, name = self._normalize_name(name) not (section in self._normal and name in self._normal[section]) and (
section in self._defaults and name in self._defaults[section])
)
if not section in self._defaults: def set_default(self, name, value):
self._defaults[section] = dict() section, name = self._normalize_name(name)
self._defaults[section][name] = value if not section in self._defaults:
self._defaults[section] = dict()
def set(self, name, value): self._defaults[section][name] = value
section, name = self._normalize_name(name)
if not section in self._normal: def set(self, name, value):
self._normal[section] = dict() section, name = self._normalize_name(name)
self._normal[section][name] = value if not section in self._normal:
self._normal[section] = dict()
def section(self, section): self._normal[section][name] = value
return ConfigurationSection(self, section)
def __getitem__(self, key): def section(self, section):
return self.get(key) return ConfigurationSection(self, section)
def __setitem__(self, key, value): def __getitem__(self, key):
self.set(key, value) return self.get(key)
def __contains__(self, section): def __setitem__(self, key, value):
return (section in self._defaults) or (section in self._normal) self.set(key, value)
def keys(self, section=None): def __contains__(self, section):
if section: return (section in self._defaults) or (section in self._normal)
names = set()
if section in self._defaults:
for param in self._defaults[section].keys():
names.add(param)
if section in self._normal:
for param in self._normal[section].keys():
names.add(param)
return list(names) def keys(self, section=None):
else: if section:
sections = set() names = set()
for section in self._defaults.keys(): if section in self._defaults:
sections.add(section) for param in self._defaults[section].keys():
names.add(param)
if section in self._normal:
for param in self._normal[section].keys():
names.add(param)
for section in self._normal.keys(): return list(names)
sections.add(section) else:
sections = set()
for section in self._defaults.keys():
sections.add(section)
return list(sections) for section in self._normal.keys():
sections.add(section)
def items(self, section=None): return list(sections)
if section:
return [(name, self.get(self._combine_names(section, name))) for name in self.keys(section)] def items(self, section=None):
else: if section:
return [(name, ConfigurationSection(self, name)) for name in self.keys()] 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()]
)
class Element(object): class Element(object):
def __init__(self, start_mark, end_mark):
self.start_mark = start_mark
self.end_mark = end_mark
def __eq__(self, other): def __init__(self, start_mark, end_mark):
return (self.__class__ == other.__class__) and (self.start_mark == other.start_mark) and (self.end_mark == other.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)
)
def __ne__(self, other):
return not self == other
def __ne__(self, other):
return not self == other
class ComponentConfig(Element): class ComponentConfig(Element):
def __init__(self, start_mark, end_mark, name, sections=[], errors=[]):
super(ComponentConfig, self).__init__(start_mark, end_mark)
self.name = name
self.sections = sections
for section in self.sections:
section.parent = self
self.errors = errors def __init__(self, start_mark, end_mark, name, sections=[], errors=[]):
super(ComponentConfig, self).__init__(start_mark, end_mark)
self.name = name
self.sections = sections
for section in self.sections:
section.parent = self
self.errors = errors
class TextElement(Element): class TextElement(Element):
def __init__(self, start_mark, end_mark, text):
super(TextElement, self).__init__(start_mark, end_mark) def __init__(self, start_mark, end_mark, text):
self.text = text super(TextElement, self).__init__(start_mark, end_mark)
self.text = text
class ConfigSection(Element): class ConfigSection(Element):
def __init__(self, start_mark, end_mark, name, parameters):
super(ConfigSection, self).__init__(start_mark, end_mark)
self.name = name
self.parameters = parameters
for parameter in self.parameters:
parameter.parent = self
class ConfigSectionName(TextElement): pass def __init__(self, start_mark, end_mark, name, parameters):
super(ConfigSection, self).__init__(start_mark, end_mark)
self.name = name
self.parameters = parameters
for parameter in self.parameters:
parameter.parent = self
class ConfigSectionName(TextElement):
pass
class ConfigParameter(Element): class ConfigParameter(Element):
def __init__(self, start_mark, end_mark, name, value, delimiter):
super(ConfigParameter, self).__init__(start_mark, end_mark)
self.name = name
self.name.parent = self
self.value = value def __init__(self, start_mark, end_mark, name, value, delimiter):
self.value.parent = self super(ConfigParameter, self).__init__(start_mark, end_mark)
self.name = name
self.name.parent = self
self.delimiter = delimiter self.value = value
self.delimiter.parent = self self.value.parent = self
def __eq__(self, other): self.delimiter = delimiter
return (self.name.text == other.name.text) and (self.value.text == other.value.text) self.delimiter.parent = self
def __ne__(self, other): def __eq__(self, other):
return not self == other return (
(self.name.text == other.name.text) and (
def __repr__(self): self.value.text == other.value.text)
return "<ConfigParameter %s=%s delimiter=%s>" % (self.name.text, self.value.text, self.delimiter.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)
)
class ConfigParameterName(TextElement): pass class ConfigParameterName(TextElement):
pass
class ConfigParameterValue(TextElement): class ConfigParameterValue(TextElement):
def __init__(self, start_mark, end_mark, text, value=None, quotechar=None):
super(ConfigParameterValue, self).__init__(start_mark, end_mark, text)
self.value = value
self.quotechar = quotechar
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

View File

@ -11,405 +11,454 @@ from ostack_validator.model import *
class NodeClient(object): 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)
def run(self, command, *args, **kwargs): def __init__(self, node_address, username, private_key_file, ssh_port=22):
return self.shell.run(command, allow_error=True, *args, **kwargs) 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)
def open(self, path, mode='r'): def run(self, command, *args, **kwargs):
return self.shell.open(path, mode) return self.shell.run(command, allow_error=True, *args, **kwargs)
def open(self, path, mode='r'):
return self.shell.open(path, mode)
python_re = re.compile('(/?([^/]*/)*)python[0-9.]*') python_re = re.compile('(/?([^/]*/)*)python[0-9.]*')
host_port_re = re.compile('(\d+\.\d+\.\d+\.\d+):(\d+)') host_port_re = re.compile('(\d+\.\d+\.\d+\.\d+):(\d+)')
class OpenstackDiscovery(object): class OpenstackDiscovery(object):
def discover(self, initial_nodes, username, private_key):
"Takes a list of node addresses and returns discovered openstack installation info"
openstack = Openstack()
private_key_file = None def discover(self, initial_nodes, username, private_key):
if private_key: "Takes a list of node addresses and returns discovered openstack installation info"
private_key_file = tempfile.NamedTemporaryFile(suffix='.key') openstack = Openstack()
private_key_file.write(private_key)
private_key_file.flush()
for address in initial_nodes: private_key_file = None
try: if private_key:
m = host_port_re.match(address) private_key_file = tempfile.NamedTemporaryFile(suffix='.key')
if m: private_key_file.write(private_key)
host = m.group(1) private_key_file.flush()
port = int(m.group(2))
for address in initial_nodes:
try:
m = host_port_re.match(address)
if m:
host = m.group(1)
port = int(m.group(2))
else:
host = address
port = 22
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))
continue
host = self._discover_node(client)
if len(host.components) == 0:
continue
openstack.add_host(host)
if len(openstack.hosts) == 0:
openstack.report_issue(
Issue(Issue.FATAL, "No OpenStack nodes were discovered"))
if private_key_file:
private_key_file.close()
return openstack
def _discover_node(self, client):
hostname = client.run(['hostname']).output.strip()
host = Host(name=hostname)
host.id = self._collect_host_id(client)
host.network_addresses = self._collect_host_network_addresses(client)
host.add_component(self._collect_keystone_data(client))
host.add_component(self._collect_nova_api_data(client))
host.add_component(self._collect_nova_compute_data(client))
host.add_component(self._collect_nova_scheduler_data(client))
host.add_component(self._collect_glance_api_data(client))
host.add_component(self._collect_glance_registry_data(client))
host.add_component(self._collect_cinder_api_data(client))
host.add_component(self._collect_cinder_volume_data(client))
host.add_component(self._collect_cinder_scheduler_data(client))
host.add_component(self._collect_mysql_data(client))
host.add_component(self._collect_rabbitmq_data(client))
return host
def _find_process(self, client, name):
processes = self._get_processes(client)
for line in processes:
if len(line) > 0 and os.path.basename(line[0]) == name:
return line
return None
def _find_python_process(self, client, name):
processes = self._get_processes(client)
for line in processes:
if len(line) > 0 and (line[0] == name or line[0].endswith('/' + name)):
return line
if len(line) > 1 and python_re.match(line[0]) and (line[1] == name or line[1].endswith('/' + name)):
return line
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])
s = result.output.strip()
parts = []
for p in s.split('.'):
if not p[0].isdigit():
break
parts.append(p)
version = '.'.join(parts)
return version
def _get_processes(self, client):
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})) ')
result = client.run(['bash', '-c', 'ip link | grep "link/ether "'])
macs = []
for match in ether_re.finditer(result.output):
macs.append(match.group(1).replace(':', ''))
return ''.join(macs)
def _collect_host_network_addresses(self, client):
ipaddr_re = re.compile('inet (\d+\.\d+\.\d+\.\d+)/\d+')
addresses = []
result = client.run(['bash', '-c', 'ip address list | grep "inet "'])
for match in ipaddr_re.finditer(result.output):
addresses.append(match.group(1))
return addresses
def _permissions_string_to_number(self, s):
return 0
def _collect_file(self, client, path):
ls = client.run(['ls', '-l', '--time-style=full-iso', path])
if ls.return_code != 0:
return None
line = ls.output.split("\n")[0]
perm, links, owner, group, size, date, time, timezone, name = line.split(
)
permissions = self._permissions_string_to_number(perm)
with client.open(path) as f:
contents = f.read()
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:
return []
lines = result.output.strip().split("\n")
columns = []
last_pos = 0
l = lines[0]
while True:
pos = l.find('+', last_pos + 1)
if pos == -1:
break
columns.append({'start': last_pos + 1, 'end': pos - 1})
last_pos = pos
l = lines[1]
for c in columns:
c['name'] = l[c['start']:c['end']].strip()
data = []
for l in lines[3:-1]:
d = dict()
for c in columns:
d[c['name']] = l[c['start']:c['end']].strip()
data.append(d)
return data
def _collect_keystone_data(self, client):
keystone_process = self._find_python_process(client, 'keystone-all')
if not keystone_process:
return None
p = index(keystone_process, lambda s: s == '--config-file')
if p != -1 and p + 1 < len(keystone_process):
config_path = keystone_process[p + 1]
else: else:
host = address config_path = '/etc/keystone/keystone.conf'
port = 22
client = NodeClient(host, ssh_port=port, username=username, private_key_file=private_key_file.name) keystone = KeystoneComponent()
client.run(['echo', 'test']) keystone.version = self._find_python_package_version(
except: client, 'keystone')
openstack.report_issue(Issue(Issue.WARNING, "Can't connect to node %s" % address)) keystone.config_files = []
continue keystone.config_files.append(self._collect_file(client, config_path))
host = self._discover_node(client) token = keystone.config['admin_token']
host = keystone.config['bind_host']
if len(host.components) == 0: if host == '0.0.0.0':
continue host = '127.0.0.1'
port = int(keystone.config['admin_port'])
openstack.add_host(host)
keystone_env = {
if len(openstack.hosts) == 0: 'OS_SERVICE_TOKEN': token,
openstack.report_issue(Issue(Issue.FATAL, "No OpenStack nodes were discovered")) 'OS_SERVICE_ENDPOINT': 'http://%s:%d/v2.0' % (host, port)
}
if private_key_file:
private_key_file.close() keystone.db = dict()
keystone.db['tenants'] = self._get_keystone_db_data(
return openstack client, 'tenant-list', env=keystone_env)
keystone.db['users'] = self._get_keystone_db_data(
def _discover_node(self, client): client, 'user-list', env=keystone_env)
hostname = client.run(['hostname']).output.strip() keystone.db['services'] = self._get_keystone_db_data(
client, 'service-list', env=keystone_env)
host = Host(name=hostname) keystone.db['endpoints'] = self._get_keystone_db_data(
host.id = self._collect_host_id(client) client, 'endpoint-list', env=keystone_env)
host.network_addresses = self._collect_host_network_addresses(client)
return keystone
host.add_component(self._collect_keystone_data(client))
host.add_component(self._collect_nova_api_data(client)) def _collect_nova_api_data(self, client):
host.add_component(self._collect_nova_compute_data(client)) process = self._find_python_process(client, 'nova-api')
host.add_component(self._collect_nova_scheduler_data(client)) if not process:
host.add_component(self._collect_glance_api_data(client)) return None
host.add_component(self._collect_glance_registry_data(client))
host.add_component(self._collect_cinder_api_data(client)) p = index(process, lambda s: s == '--config-file')
host.add_component(self._collect_cinder_volume_data(client)) if p != -1 and p + 1 < len(process):
host.add_component(self._collect_cinder_scheduler_data(client)) config_path = process[p + 1]
host.add_component(self._collect_mysql_data(client)) else:
host.add_component(self._collect_rabbitmq_data(client)) config_path = '/etc/nova/nova.conf'
return host nova_api = NovaApiComponent()
nova_api.version = self._find_python_package_version(client, 'nova')
nova_api.config_files = []
def _find_process(self, client, name): nova_api.config_files.append(self._collect_file(client, config_path))
processes = self._get_processes(client)
for line in processes: paste_config_path = path_relative_to(
if len(line) > 0 and os.path.basename(line[0]) == name: nova_api.config['api_paste_config'],
return line os.path.dirname(config_path))
nova_api.paste_config_file = self._collect_file(
return None client, paste_config_path)
def _find_python_process(self, client, name): return nova_api
processes = self._get_processes(client)
for line in processes: def _collect_nova_compute_data(self, client):
if len(line) > 0 and (line[0] == name or line[0].endswith('/'+name)): process = self._find_python_process(client, 'nova-compute')
return line if not process:
if len(line) > 1 and python_re.match(line[0]) and (line[1] == name or line[1].endswith('/'+name)): return None
return line
p = index(process, lambda s: s == '--config-file')
return None if p != -1 and p + 1 < len(process):
config_path = process[p + 1]
def _find_python_package_version(self, client, package): else:
result = client.run(['python', '-c', 'import pkg_resources; version = pkg_resources.get_provider(pkg_resources.Requirement.parse("%s")).version; print(version)' % package]) config_path = '/etc/nova/nova.conf'
s = result.output.strip() nova_compute = NovaComputeComponent()
parts = [] nova_compute.version = self._find_python_package_version(
for p in s.split('.'): client, 'nova')
if not p[0].isdigit(): break nova_compute.config_files = []
nova_compute.config_files.append(
parts.append(p) self._collect_file(client, config_path))
version = '.'.join(parts) return nova_compute
return version def _collect_nova_scheduler_data(self, client):
process = self._find_python_process(client, 'nova-scheduler')
def _get_processes(self, client): if not process:
return [line.split() for line in client.run(['ps', '-Ao', 'cmd', '--no-headers']).output.split("\n")] return None
def _collect_host_id(self, client): p = index(process, lambda s: s == '--config-file')
ether_re = re.compile('link/ether (([0-9a-f]{2}:){5}([0-9a-f]{2})) ') if p != -1 and p + 1 < len(process):
result = client.run(['bash', '-c', 'ip link | grep "link/ether "']) config_path = process[p + 1]
macs = [] else:
for match in ether_re.finditer(result.output): config_path = '/etc/nova/nova.conf'
macs.append(match.group(1).replace(':', ''))
return ''.join(macs) nova_scheduler = NovaSchedulerComponent()
nova_scheduler.version = self._find_python_package_version(
def _collect_host_network_addresses(self, client): client, 'nova')
ipaddr_re = re.compile('inet (\d+\.\d+\.\d+\.\d+)/\d+') nova_scheduler.config_files = []
addresses = [] nova_scheduler.config_files.append(
result = client.run(['bash', '-c', 'ip address list | grep "inet "']) self._collect_file(client, config_path))
for match in ipaddr_re.finditer(result.output):
addresses.append(match.group(1)) return nova_scheduler
return addresses
def _collect_glance_api_data(self, client):
def _permissions_string_to_number(self, s): process = self._find_python_process(client, 'glance-api')
return 0 if not process:
return None
def _collect_file(self, client, path):
ls = client.run(['ls', '-l', '--time-style=full-iso', path]) p = index(process, lambda s: s == '--config-file')
if ls.return_code != 0: if p != -1 and p + 1 < len(process):
return None config_path = process[p + 1]
else:
line = ls.output.split("\n")[0] config_path = '/etc/glance/glance-api.conf'
perm, links, owner, group, size, date, time, timezone, name = line.split()
permissions = self._permissions_string_to_number(perm) glance_api = GlanceApiComponent()
glance_api.version = self._find_python_package_version(
with client.open(path) as f: client, 'glance')
contents = f.read() glance_api.config_files = []
glance_api.config_files.append(self._collect_file(client, config_path))
return FileResource(path, contents, owner, group, permissions)
return glance_api
def _get_keystone_db_data(self, client, command, env={}): def _collect_glance_registry_data(self, client):
result = client.run(['keystone', command], update_env=env) process = self._find_python_process(client, 'glance-registry')
if result.return_code != 0: if not process:
return [] return None
lines = result.output.strip().split("\n") p = index(process, lambda s: s == '--config-file')
if p != -1 and p + 1 < len(process):
columns = [] config_path = process[p + 1]
last_pos = 0 else:
l = lines[0] config_path = '/etc/glance/glance-registry.conf'
while True:
pos = l.find('+', last_pos+1) glance_registry = GlanceRegistryComponent()
if pos == -1: glance_registry.version = self._find_python_package_version(
break client, 'glance')
glance_registry.config_files = []
columns.append({'start': last_pos+1, 'end': pos-1}) glance_registry.config_files.append(
self._collect_file(client, config_path))
last_pos = pos
return glance_registry
l = lines[1]
for c in columns: def _collect_cinder_api_data(self, client):
c['name'] = l[c['start']:c['end']].strip() process = self._find_python_process(client, 'cinder-api')
if not process:
data = [] return None
for l in lines[3:-1]:
d = dict() p = index(process, lambda s: s == '--config-file')
for c in columns: if p != -1 and p + 1 < len(process):
d[c['name']] = l[c['start']:c['end']].strip() config_path = process[p + 1]
else:
data.append(d) config_path = '/etc/cinder/cinder.conf'
return data cinder_api = CinderApiComponent()
cinder_api.version = self._find_python_package_version(
def _collect_keystone_data(self, client): client, 'cinder')
keystone_process = self._find_python_process(client, 'keystone-all') cinder_api.config_files = []
if not keystone_process: cinder_api.config_files.append(self._collect_file(client, config_path))
return None
paste_config_path = path_relative_to(
p = index(keystone_process, lambda s: s == '--config-file') cinder_api.config['api_paste_config'],
if p != -1 and p+1 < len(keystone_process): os.path.dirname(config_path))
config_path = keystone_process[p+1] cinder_api.paste_config_file = self._collect_file(
else: client, paste_config_path)
config_path = '/etc/keystone/keystone.conf'
return cinder_api
keystone = KeystoneComponent()
keystone.version = self._find_python_package_version(client, 'keystone') def _collect_cinder_volume_data(self, client):
keystone.config_files = [] process = self._find_python_process(client, 'cinder-volume')
keystone.config_files.append(self._collect_file(client, config_path)) if not process:
return None
token = keystone.config['admin_token']
host = keystone.config['bind_host'] p = index(process, lambda s: s == '--config-file')
if host == '0.0.0.0': if p != -1 and p + 1 < len(process):
host = '127.0.0.1' config_path = process[p + 1]
port = int(keystone.config['admin_port']) else:
config_path = '/etc/cinder/cinder.conf'
keystone_env = {
'OS_SERVICE_TOKEN': token, cinder_volume = CinderVolumeComponent()
'OS_SERVICE_ENDPOINT': 'http://%s:%d/v2.0' % (host, port) cinder_volume.version = self._find_python_package_version(
} client, 'cinder')
cinder_volume.config_files = []
keystone.db = dict() cinder_volume.config_files.append(
keystone.db['tenants'] = self._get_keystone_db_data(client, 'tenant-list', env=keystone_env) self._collect_file(client, config_path))
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) rootwrap_config_path = path_relative_to(
keystone.db['endpoints'] = self._get_keystone_db_data(client, 'endpoint-list', env=keystone_env) cinder_volume.config['rootwrap_config'],
os.path.dirname(config_path))
return keystone cinder_volume.rootwrap_config = self._collect_file(
client, rootwrap_config_path)
def _collect_nova_api_data(self, client):
process = self._find_python_process(client, 'nova-api') return cinder_volume
if not process:
return None def _collect_cinder_scheduler_data(self, client):
process = self._find_python_process(client, 'cinder-scheduler')
p = index(process, lambda s: s == '--config-file') if not process:
if p != -1 and p+1 < len(process): return None
config_path = process[p+1]
else: p = index(process, lambda s: s == '--config-file')
config_path = '/etc/nova/nova.conf' if p != -1 and p + 1 < len(process):
config_path = process[p + 1]
nova_api = NovaApiComponent() else:
nova_api.version = self._find_python_package_version(client, 'nova') config_path = '/etc/cinder/cinder.conf'
nova_api.config_files = []
nova_api.config_files.append(self._collect_file(client, config_path)) cinder_scheduler = CinderSchedulerComponent()
cinder_scheduler.version = self._find_python_package_version(
paste_config_path = path_relative_to(nova_api.config['api_paste_config'], os.path.dirname(config_path)) client, 'cinder')
nova_api.paste_config_file = self._collect_file(client, paste_config_path) cinder_scheduler.config_files = []
cinder_scheduler.config_files.append(
return nova_api self._collect_file(client, config_path))
def _collect_nova_compute_data(self, client): return cinder_scheduler
process = self._find_python_process(client, 'nova-compute')
if not process: def _collect_mysql_data(self, client):
return None process = self._find_process(client, 'mysqld')
if not process:
p = index(process, lambda s: s == '--config-file') return None
if p != -1 and p+1 < len(process):
config_path = process[p+1] mysqld_version_re = re.compile('mysqld\s+Ver\s(\S+)\s')
else:
config_path = '/etc/nova/nova.conf' mysql = MysqlComponent()
nova_compute = NovaComputeComponent() version_result = client.run(['mysqld', '--version'])
nova_compute.version = self._find_python_package_version(client, 'nova') m = mysqld_version_re.match(version_result.output)
nova_compute.config_files = [] mysql.version = m.group(1) if m else 'unknown'
nova_compute.config_files.append(self._collect_file(client, config_path))
mysql.config_files = []
return nova_compute 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'])
def _collect_nova_scheduler_data(self, client): config_locations = config_locations_result.output.strip().split(
process = self._find_python_process(client, 'nova-scheduler') "\n")[-1].split()
if not process: for path in config_locations:
return None f = self._collect_file(client, path)
if f:
p = index(process, lambda s: s == '--config-file') mysql.config_files.append(f)
if p != -1 and p+1 < len(process):
config_path = process[p+1] return mysql
else:
config_path = '/etc/nova/nova.conf' def _collect_rabbitmq_data(self, client):
process = self._find_process(client, 'beam.smp')
nova_scheduler = NovaSchedulerComponent() if not process:
nova_scheduler.version = self._find_python_package_version(client, 'nova') return None
nova_scheduler.config_files = []
nova_scheduler.config_files.append(self._collect_file(client, config_path)) if ' '.join(process).find('rabbit') == -1:
return None
return nova_scheduler
rabbitmq = RabbitMqComponent()
def _collect_glance_api_data(self, client): rabbitmq.version = 'unknown'
process = self._find_python_process(client, 'glance-api')
if not process: return rabbitmq
return None
p = index(process, lambda s: s == '--config-file')
if p != -1 and p+1 < len(process):
config_path = process[p+1]
else:
config_path = '/etc/glance/glance-api.conf'
glance_api = GlanceApiComponent()
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))
return glance_api
def _collect_glance_registry_data(self, client):
process = self._find_python_process(client, 'glance-registry')
if not process:
return None
p = index(process, lambda s: s == '--config-file')
if p != -1 and p+1 < len(process):
config_path = process[p+1]
else:
config_path = '/etc/glance/glance-registry.conf'
glance_registry = GlanceRegistryComponent()
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))
return glance_registry
def _collect_cinder_api_data(self, client):
process = self._find_python_process(client, 'cinder-api')
if not process:
return None
p = index(process, lambda s: s == '--config-file')
if p != -1 and p+1 < len(process):
config_path = process[p+1]
else:
config_path = '/etc/cinder/cinder.conf'
cinder_api = CinderApiComponent()
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)
return cinder_api
def _collect_cinder_volume_data(self, client):
process = self._find_python_process(client, 'cinder-volume')
if not process:
return None
p = index(process, lambda s: s == '--config-file')
if p != -1 and p+1 < len(process):
config_path = process[p+1]
else:
config_path = '/etc/cinder/cinder.conf'
cinder_volume = CinderVolumeComponent()
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))
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
def _collect_cinder_scheduler_data(self, client):
process = self._find_python_process(client, 'cinder-scheduler')
if not process:
return None
p = index(process, lambda s: s == '--config-file')
if p != -1 and p+1 < len(process):
config_path = process[p+1]
else:
config_path = '/etc/cinder/cinder.conf'
cinder_scheduler = CinderSchedulerComponent()
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))
return cinder_scheduler
def _collect_mysql_data(self, client):
process = self._find_process(client, 'mysqld')
if not process:
return None
mysqld_version_re = re.compile('mysqld\s+Ver\s(\S+)\s')
mysql = MysqlComponent()
version_result = client.run(['mysqld', '--version'])
m = mysqld_version_re.match(version_result.output)
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()
for path in config_locations:
f = self._collect_file(client, path)
if f:
mysql.config_files.append(f)
return mysql
def _collect_rabbitmq_data(self, client):
process = self._find_process(client, 'beam.smp')
if not process:
return None
if ' '.join(process).find('rabbit') == -1:
return None
rabbitmq = RabbitMqComponent()
rabbitmq.version = 'unknown'
return rabbitmq

View File

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

View File

@ -3,81 +3,137 @@ from ostack_validator.common import Inspection, Issue, find
KEYSTONE_AUTHTOKEN_FILTER_FACTORY = 'keystoneclient.middleware.auth_token:filter_factory' KEYSTONE_AUTHTOKEN_FILTER_FACTORY = 'keystoneclient.middleware.auth_token:filter_factory'
class KeystoneAuthtokenSettingsInspection(Inspection): class KeystoneAuthtokenSettingsInspection(Inspection):
name = 'Keystone auth' name = 'Keystone auth'
description = 'Validate correctness of keystone settings' description = 'Validate correctness of keystone settings'
def inspect(self, openstack): def inspect(self, openstack):
components = [] components = []
for host in openstack.hosts: for host in openstack.hosts:
components.extend(host.components) components.extend(host.components)
keystones = [c for c in components if c.name == 'keystone'] keystones = [c for c in components if c.name == 'keystone']
if len(keystones) == 0: if len(keystones) == 0:
openstack.report_issue(Issue(Issue.FATAL, 'No keystone service found')) openstack.report_issue(
return Issue(Issue.FATAL, 'No keystone service found'))
return
keystone = keystones[0] keystone = keystones[0]
keystone_addresses = [keystone.config['bind_host']] keystone_addresses = [keystone.config['bind_host']]
if keystone_addresses == ['0.0.0.0']: if keystone_addresses == ['0.0.0.0']:
keystone_addresses = keystone.host.network_addresses keystone_addresses = keystone.host.network_addresses
for nova in [c for c in components if c.name == 'nova-api']: for nova in [c for c in components if c.name == 'nova-api']:
if nova.config['auth_strategy'] != 'keystone': if nova.config['auth_strategy'] != 'keystone':
continue continue
(authtoken_section,_) = find( (authtoken_section, _) = find(
nova.paste_config.items(), nova.paste_config.items(),
lambda (name, values): name.startswith('filter:') and values.get('paste.filter_factory') == KEYSTONE_AUTHTOKEN_FILTER_FACTORY lambda name_values: name_values[0].startswith('filter:') and name_values[
) 1].get('paste.filter_factory') == KEYSTONE_AUTHTOKEN_FILTER_FACTORY
)
if not authtoken_section: continue if not authtoken_section:
continue
authtoken_settings = nova.paste_config.section(authtoken_section) authtoken_settings = nova.paste_config.section(authtoken_section)
def get_value(name):
return (
authtoken_settings[
name] or nova.config[
'keystone_authtoken.%s' %
name]
)
def get_value(name): auth_host = get_value('auth_host')
return authtoken_settings[name] or nova.config['keystone_authtoken.%s' % name] auth_port = get_value('auth_port')
auth_protocol = get_value('auth_protocol')
admin_user = get_value('admin_user')
admin_password = get_value('admin_password')
admin_tenant_name = get_value('admin_tenant_name')
admin_token = get_value('admin_token')
auth_host = get_value('auth_host') msg_prefix = 'Service "%s" on host "%s"' % (
auth_port = get_value('auth_port') nova.name, nova.host.name)
auth_protocol = get_value('auth_protocol')
admin_user = get_value('admin_user')
admin_password = get_value('admin_password')
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) if not auth_host:
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'))
if not auth_host: if not auth_port:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_host" setting in keystone authtoken config')) nova.report_issue(
elif not auth_host in keystone_addresses: Issue(
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_host" setting in keystone authtoken config')) 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'))
if not auth_port: if not auth_protocol:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_port" setting in keystone authtoken config')) nova.report_issue(
elif auth_port != keystone.config['admin_port']: Issue(
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_port" setting in keystone authtoken config')) 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'))
if not auth_protocol: if not admin_user:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_protocol" setting in keystone authtoken config')) nova.report_issue(
elif not auth_protocol in ['http', 'https']: Issue(
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_protocol" setting in keystone authtoken config')) Issue.ERROR,
msg_prefix +
' miss "admin_user" setting in keystone authtoken config'))
else:
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'))
if not admin_user: if not admin_tenant_name:
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "admin_user" setting in keystone authtoken config')) nova.report_issue(
else: Issue(
user = find(keystone.db['users'], lambda u: u['name'] == admin_user) Issue.ERROR,
if not user: msg_prefix +
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has "admin_user" that is missing in Keystone catalog')) ' miss "admin_tenant_name" setting in keystone authtoken config'))
else:
if not admin_tenant_name: tenant = find(
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "admin_tenant_name" setting in keystone authtoken config')) keystone.db['tenants'],
else: lambda t: t['name'] == admin_tenant_name)
tenant = find(keystone.db['tenants'], lambda t: t['name'] == admin_tenant_name) if not tenant:
if not tenant: nova.report_issue(
nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has "admin_tenant_name" that is missing in Keystone catalog')) Issue(
Issue.ERROR,
if admin_token: msg_prefix +
nova.report_issue(Issue(Issue.WARNING, msg_prefix + ' uses insecure admin_token for authentication')) ' 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'))

View File

@ -2,40 +2,53 @@ from urlparse import urlparse
from ostack_validator.common import Inspection, Issue, find from ostack_validator.common import Inspection, Issue, find
class KeystoneEndpointsInspection(Inspection): class KeystoneEndpointsInspection(Inspection):
name = 'Keystone endpoints' name = 'Keystone endpoints'
description = 'Validate that each keystone endpoint leads to proper service' description = 'Validate that each keystone endpoint leads to proper service'
def inspect(self, openstack): def inspect(self, openstack):
keystone = find(openstack.components, lambda c: c.name == 'keystone') keystone = find(openstack.components, lambda c: c.name == 'keystone')
if not keystone: if not keystone:
return return
for service in keystone.db['services']: for service in keystone.db['services']:
if service['type'] == 'compute': if service['type'] == 'compute':
endpoint = find(keystone.db['endpoints'], lambda e: e['service_id'] == service['id']) endpoint = find(
if not endpoint: keystone.db['endpoints'],
keystone.report_issue(Issue(Issue.WARNING, 'Keystone catalog contains service "%s" that has no defined endpoints' % service['name'])) lambda e: e['service_id'] == service['id'])
continue if not endpoint:
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']: for url_attr in ['adminurl', 'publicurl', 'internalurl']:
url = urlparse(endpoint[url_attr]) url = urlparse(endpoint[url_attr])
# TODO: resolve endpoint url host address # TODO: resolve endpoint url host address
host = find(openstack.hosts, lambda h: url.hostname in h.network_addresses) host = find(
if not host: openstack.hosts,
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))) lambda h: url.hostname in h.network_addresses)
continue 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)))
continue
nova_compute = None nova_compute = None
for c in host.components: for c in host.components:
if c.name != 'nova-compute': continue if c.name != 'nova-compute':
continue
if c.config['osapi_compute_listen'] in ['0.0.0.0', url.hostname] and c.config['osapi_compute_listen_port'] == url.port:
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)))
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)))

View File

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

View File

@ -3,18 +3,23 @@ import lettuce
from ostack_validator.common import Inspection, Issue from ostack_validator.common import Inspection, Issue
class LettuceRunnerInspection(Inspection): class LettuceRunnerInspection(Inspection):
def inspect(self, openstack):
runner = lettuce.Runner(
base_path=os.path.join(os.path.dirname(__file__), 'lettuce')
)
lettuce.world.openstack = openstack def inspect(self, openstack):
result = runner.run() runner = lettuce.Runner(
del lettuce.world.openstack base_path=os.path.join(os.path.dirname(__file__), 'lettuce')
)
for feature_result in result.feature_results: lettuce.world.openstack = openstack
for scenario_result in [s for s in feature_result.scenario_results if not s.passed]: result = runner.run()
for step in scenario_result.steps_undefined: del lettuce.world.openstack
openstack.report_issue(Issue(Issue.ERROR, 'Undefined step "%s"' % step.sentence))
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))

View File

@ -5,38 +5,42 @@ import argparse
from ostack_validator.model_parser import ModelParser from ostack_validator.model_parser import ModelParser
from ostack_validator.inspection import MainConfigValidationInspection from ostack_validator.inspection import MainConfigValidationInspection
def main(args): def main(args):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-d', '--debug', help='set debug log level', action='store_true') parser.add_argument(
parser.add_argument('path', help='Path to config snapshot') '-d',
'--debug',
help='set debug log level',
action='store_true')
parser.add_argument('path', help='Path to config snapshot')
args = parser.parse_args(args) args = parser.parse_args(args)
if args.debug: if args.debug:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
else: else:
logging.basicConfig(level=logging.WARN) logging.basicConfig(level=logging.WARN)
model_parser = ModelParser() model_parser = ModelParser()
print('Analyzing configs in "%s"' % args.path) print('Analyzing configs in "%s"' % args.path)
model = model_parser.parse(args.path) model = model_parser.parse(args.path)
inspections = [MainConfigValidationInspection()] inspections = [MainConfigValidationInspection()]
issues = [] issues = []
for inspection in inspections: for inspection in inspections:
issues.extend(inspection.inspect(model)) issues.extend(inspection.inspect(model))
if len(issues) == 0:
print('No issues found')
else:
print('Found issues:')
for issue in issues:
print(issue)
if len(issues) == 0:
print('No issues found')
else:
print('Found issues:')
for issue in issues:
print(issue)
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) main(sys.argv[1:])

View File

@ -10,277 +10,341 @@ import ostack_validator.schemas
from ostack_validator.config_formats import IniConfigParser from ostack_validator.config_formats import IniConfigParser
from ostack_validator.utils import memoized from ostack_validator.utils import memoized
class IssueReporter(object): class IssueReporter(object):
def __init__(self):
super(IssueReporter, self).__init__()
self.issues = []
def report_issue(self, issue): def __init__(self):
issue.subject = self super(IssueReporter, self).__init__()
self.issues.append(issue) self.issues = []
def report_issue(self, issue):
issue.subject = self
self.issues.append(issue)
@property
def all_issues(self):
return list(self.issues)
@property
def all_issues(self):
return list(self.issues)
class Openstack(IssueReporter): class Openstack(IssueReporter):
def __init__(self):
super(Openstack, self).__init__()
self.hosts = []
def add_host(self, host): def __init__(self):
if not host: return super(Openstack, self).__init__()
self.hosts = []
self.hosts.append(host) def add_host(self, host):
host.parent = self if not host:
return
@property self.hosts.append(host)
def all_issues(self): host.parent = self
result = super(Openstack, self).all_issues
for host in self.hosts: @property
result.extend(host.all_issues) def all_issues(self):
result = super(Openstack, self).all_issues
return result for host in self.hosts:
result.extend(host.all_issues)
@property return result
def components(self):
components = []
for host in self.hosts:
components.extend(host.components)
return components @property
def components(self):
components = []
for host in self.hosts:
components.extend(host.components)
return components
class Host(IssueReporter): class Host(IssueReporter):
def __init__(self, name):
super(Host, self).__init__()
self.name = name
self.components = []
def __str__(self): def __init__(self, name):
return 'Host "%s"' % self.name super(Host, self).__init__()
self.name = name
self.components = []
def add_component(self, component): def __str__(self):
if not component: return return 'Host "%s"' % self.name
self.components.append(component) def add_component(self, component):
component.parent = self if not component:
return
@property self.components.append(component)
def openstack(self): component.parent = self
return self.parent
@property @property
def all_issues(self): def openstack(self):
result = super(Host, self).all_issues return self.parent
for component in self.components: @property
result.extend(component.all_issues) def all_issues(self):
result = super(Host, self).all_issues
return result for component in self.components:
result.extend(component.all_issues)
return result
class Service(IssueReporter): class Service(IssueReporter):
def __init__(self):
super(Service, self).__init__()
self.issues = []
def report_issue(self, issue): def __init__(self):
self.issues.append(issue) super(Service, self).__init__()
self.issues = []
@property def report_issue(self, issue):
def host(self): self.issues.append(issue)
return self.parent
@property @property
def openstack(self): def host(self):
return self.host.openstack return self.parent
@property @property
def all_issues(self): def openstack(self):
result = super(Service, self).all_issues return self.host.openstack
if hasattr(self, 'config_files') and self.config_files: @property
[result.extend(config_file.all_issues) for config_file in self.config_files] def all_issues(self):
result = super(Service, self).all_issues
return result if hasattr(self, 'config_files') and self.config_files:
[result.extend(config_file.all_issues)
for config_file in self.config_files]
def __str__(self): return result
return 'Service "%s"' % self.name
def __str__(self):
return 'Service "%s"' % self.name
class OpenstackComponent(Service): class OpenstackComponent(Service):
logger = logging.getLogger('ostack_validator.model.openstack_component') logger = logging.getLogger('ostack_validator.model.openstack_component')
component = None component = None
@property @property
@memoized @memoized
def config(self): def config(self):
schema = ConfigSchemaRegistry.get_schema(self.component, self.version) schema = ConfigSchemaRegistry.get_schema(self.component, self.version)
if not schema: if not schema:
self.logger.debug('No schema for component "%s" main config version %s. Skipping it' % (self.component, self.version)) self.logger.debug(
return None '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) return self._parse_config_resources(self.config_files, schema)
def _parse_config_resources(self, resources, schema=None): def _parse_config_resources(self, resources, schema=None):
config = Configuration() config = Configuration()
# Apply defaults # Apply defaults
if schema:
for parameter in filter(lambda p: p.default, schema.parameters):
if parameter.section == 'DEFAULT':
config.set_default(parameter.name, parameter.default)
else:
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)
return config
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
# Parse config file
config_parser = IniConfigParser()
parsed_config = config_parser.parse('', base_mark, config_contents)
for error in parsed_config.errors:
report_issue(error)
# 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)
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))
seen_parameters = set()
for section in sections:
unknown_section = False
if schema: if schema:
unknown_section = not schema.has_section(section.name.text) for parameter in filter(lambda p: p.default, schema.parameters):
if parameter.section == 'DEFAULT':
config.set_default(parameter.name, parameter.default)
else:
config.set_default(
'%s.%s' %
(parameter.section, parameter.name), parameter.default)
if unknown_section: for resource in reversed(resources):
report_issue(MarkedIssue(Issue.WARNING, 'Unknown section "%s"' % (section_name), section.start_mark)) self._parse_config_file(
continue Mark(resource.path),
resource.contents,
config,
schema,
issue_reporter=resource)
for parameter in section.parameters: return config
parameter_schema = None
if schema:
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))
continue
if parameter.name.text in seen_parameters: def _parse_config_file(
report_issue(MarkedIssue(Issue.WARNING, 'Parameter "%s" in section "%s" redeclared' % (parameter.name.text, section_name), parameter.start_mark)) self,
else: base_mark,
seen_parameters.add(parameter.name.text) 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
parameter_fullname = parameter.name.text # Parse config file
if section_name != 'DEFAULT': config_parser = IniConfigParser()
parameter_fullname = section_name + '.' + parameter_fullname parsed_config = config_parser.parse('', base_mark, config_contents)
for error in parsed_config.errors:
report_issue(error)
if parameter_schema: # Validate config parameters and store them
type_validator = TypeValidatorRegistry.get_validator(parameter_schema.type) section_name_text_f = lambda s: s.name.text
type_validation_result = type_validator.validate(parameter.value.text) sections_by_name = groupby(
if isinstance(type_validation_result, Issue): sorted(
type_validation_result.mark = parameter.value.start_mark.merge(type_validation_result.mark) parsed_config.sections,
type_validation_result.message = 'Property "%s" in section "%s": %s' % (parameter.name.text, section_name, type_validation_result.message) key=section_name_text_f),
report_issue(type_validation_result) key=section_name_text_f)
config.set(parameter_fullname, parameter.value.text) for section_name, sections in sections_by_name:
else: sections = list(sections)
value = type_validation_result
config.set(parameter_fullname, value) if len(sections) > 1:
report_issue(
Issue(
Issue.INFO,
'Section "%s" appears multiple times' %
section_name))
# if value == parameter_schema.default: seen_parameters = set()
# 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))
else:
config.set(parameter_fullname, parameter.value.text)
return config for section in sections:
unknown_section = False
if schema:
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))
continue
for parameter in section.parameters:
parameter_schema = None
if schema:
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))
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))
else:
seen_parameters.add(parameter.name.text)
parameter_fullname = parameter.name.text
if section_name != 'DEFAULT':
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)
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)
report_issue(type_validation_result)
config.set(
parameter_fullname,
parameter.value.text)
else:
value = type_validation_result
config.set(parameter_fullname, value)
# 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))
else:
config.set(parameter_fullname, parameter.value.text)
return config
class KeystoneComponent(OpenstackComponent): class KeystoneComponent(OpenstackComponent):
component = 'keystone' component = 'keystone'
name = 'keystone' name = 'keystone'
class GlanceApiComponent(OpenstackComponent): class GlanceApiComponent(OpenstackComponent):
component = 'glance' component = 'glance'
name = 'glance-api' name = 'glance-api'
class GlanceRegistryComponent(OpenstackComponent): class GlanceRegistryComponent(OpenstackComponent):
component = 'glance' component = 'glance'
name = 'glance-registry' name = 'glance-registry'
class NovaApiComponent(OpenstackComponent): class NovaApiComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-api' name = 'nova-api'
@property @property
@memoized @memoized
def paste_config(self): def paste_config(self):
return self._parse_config_resources([self.paste_config_file]) return self._parse_config_resources([self.paste_config_file])
@property @property
def all_issues(self): def all_issues(self):
result = super(NovaApiComponent, self).all_issues result = super(NovaApiComponent, self).all_issues
if hasattr(self, 'paste_config_file') and self.paste_config_file:
result.extend(self.paste_config_file.all_issues)
return result
if hasattr(self, 'paste_config_file') and self.paste_config_file:
result.extend(self.paste_config_file.all_issues)
return result
class NovaComputeComponent(OpenstackComponent): class NovaComputeComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-compute' name = 'nova-compute'
class NovaSchedulerComponent(OpenstackComponent): class NovaSchedulerComponent(OpenstackComponent):
component = 'nova' component = 'nova'
name = 'nova-scheduler' name = 'nova-scheduler'
class CinderApiComponent(OpenstackComponent): class CinderApiComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-api' name = 'cinder-api'
class CinderVolumeComponent(OpenstackComponent): class CinderVolumeComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-volume' name = 'cinder-volume'
class CinderSchedulerComponent(OpenstackComponent): class CinderSchedulerComponent(OpenstackComponent):
component = 'cinder' component = 'cinder'
name = 'cinder-scheduler' name = 'cinder-scheduler'
class MysqlComponent(Service): class MysqlComponent(Service):
component = 'mysql' component = 'mysql'
name = 'mysql' name = 'mysql'
class RabbitMqComponent(Service): class RabbitMqComponent(Service):
name = 'rabbitmq' name = 'rabbitmq'
class FileResource(IssueReporter): class FileResource(IssueReporter):
def __init__(self, path, contents, owner, group, permissions):
super(FileResource, self).__init__()
self.path = path
self.contents = contents
self.owner = owner
self.group = group
self.permissions = permissions
def __str__(self): def __init__(self, path, contents, owner, group, permissions):
return 'File "%s"' % self.path super(FileResource, self).__init__()
self.path = path
self.contents = contents
self.owner = owner
self.group = group
self.permissions = permissions
def __str__(self):
return 'File "%s"' % self.path

View File

@ -4,162 +4,205 @@ import json
from ostack_validator.common import Error, Version from ostack_validator.common import Error, Version
class Resource(object): class Resource(object):
HOST = 'host' HOST = 'host'
FILE = 'file' FILE = 'file'
DIRECTORY = 'directory' DIRECTORY = 'directory'
SERVICE = 'service' SERVICE = 'service'
def __init__(self, name): def __init__(self, name):
super(Resource, self).__init__() super(Resource, self).__init__()
self.name = name self.name = name
def __str__(self): def __str__(self):
return self.name return self.name
def __repr__(self): def __repr__(self):
return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name) return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name)
def get_contents(self):
raise Error('Not implemented')
def get_contents(self):
raise Error, 'Not implemented'
class ResourceLocator(object): class ResourceLocator(object):
def find_resource(self, resource_type, name, host=None, **kwargs):
return None def find_resource(self, resource_type, name, host=None, **kwargs):
return None
class HostResource(Resource): 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): def __init__(self, name, resource_locator, interfaces=[]):
return self.resource_locator.find_resource(resource_type, name, host=self, **kwargs) 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)
)
class DirectoryResource(Resource): class DirectoryResource(Resource):
def __init__(self, name, owner=None, group=None, permissions=None):
super(DirectoryResource, self).__init__(name) def __init__(self, name, owner=None, group=None, permissions=None):
self.owner = owner super(DirectoryResource, self).__init__(name)
self.group = group self.owner = owner
self.permissions = permissions self.group = group
self.permissions = permissions
class FileResource(Resource): class FileResource(Resource):
def __init__(self, name, path, owner=None, group=None, permissions=None):
super(FileResource, self).__init__(name)
self.path = path
self.owner = owner
self.group = group
self.permissions = permissions
def get_contents(self): def __init__(self, name, path, owner=None, group=None, permissions=None):
with open(self.path) as f: super(FileResource, self).__init__(name)
return f.read() self.path = path
self.owner = owner
self.group = group
self.permissions = permissions
def get_contents(self):
with open(self.path) as f:
return f.read()
class ServiceResource(Resource): class ServiceResource(Resource):
def __init__(self, name, version, metadata={}):
super(ServiceResource, self).__init__(name) def __init__(self, name, version, metadata={}):
self.version = Version(version) super(ServiceResource, self).__init__(name)
self.metadata = metadata self.version = Version(version)
self.metadata = metadata
class FilesystemSnapshot(object): class FilesystemSnapshot(object):
def __init__(self, path):
super(FilesystemSnapshot, self).__init__()
self.path = path
self.basedir = os.path.join(os.path.dirname(self.path), 'root')
self._parse_snapshot()
def get_resource(self, path): def __init__(self, path):
if path in self._resources: super(FilesystemSnapshot, self).__init__()
return self._resources[path] self.path = path
self.basedir = os.path.join(os.path.dirname(self.path), 'root')
self._parse_snapshot()
return None def get_resource(self, path):
if path in self._resources:
return self._resources[path]
def _parse_snapshot(self): return None
self._resources = {}
if not os.path.isfile(self.path): return def _parse_snapshot(self):
with open(self.path) as f: self._resources = {}
for line in f.readlines(): if not os.path.isfile(self.path):
line = line.lstrip() return
if line == '' or line.startswith('#'): continue with open(self.path) as f:
for line in f.readlines():
line = line.lstrip()
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)
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)
else:
self.logger.warn(
'Unknown resource "%s" in line "%s"' %
(resource_type, line))
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)
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)
else:
self.logger.warn('Unknown resource "%s" in line "%s"' % (resource_type, line))
class ConfigSnapshotResourceLocator(object): 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'
self._services = None
self._filesystem_snapshots = {}
def find_resource(self, resource_type, name=None, host=None, **kwargs): def __init__(self, basedir):
if resource_type == Resource.HOST: super(ConfigSnapshotResourceLocator, self).__init__()
if name: self.basedir = basedir
host_path = os.path.join(self.basedir, name) if not os.path.isdir(self.basedir):
if not os.path.isdir(host_path): raise Error('Invalid argument: base directory does not exist')
return None self._services = None
return HostResource(name, self) self._filesystem_snapshots = {}
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)]
if resource_type == Resource.FILE:
if not host:
raise Error, 'Invalid argument: "host" need to be specified'
if isinstance(host, HostResource): def find_resource(self, resource_type, name=None, host=None, **kwargs):
host = host.name if resource_type == Resource.HOST:
if name:
host_path = os.path.join(self.basedir, name)
if not os.path.isdir(host_path):
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)]
)
if resource_type == Resource.FILE:
if not host:
raise Error('Invalid argument: "host" need to be specified')
if name: if isinstance(host, HostResource):
return self._get_filesystem_snapshot(host).get_resource(name) host = host.name
else:
return []
elif resource_type == Resource.SERVICE:
if not host:
raise Error, 'Invalid argument: "host" need to be specified'
if isinstance(host, HostResource): if name:
host = host.name return self._get_filesystem_snapshot(host).get_resource(name)
else:
return []
elif resource_type == Resource.SERVICE:
if not host:
raise Error('Invalid argument: "host" need to be specified')
self._ensure_services_loaded() if isinstance(host, HostResource):
host = host.name
if name: self._ensure_services_loaded()
if name in self._services:
return self._services[host][name] if name:
if name in self._services:
return self._services[host][name]
else:
return None
else:
return self._services[host].values()
else: else:
return None return None
else:
return self._services[host].values()
else:
return None
def _ensure_services_loaded(self): def _ensure_services_loaded(self):
if self._services: return if self._services:
return
self._services = {} self._services = {}
for host_path in glob.glob(os.path.join(self.basedir, '*')): for host_path in glob.glob(os.path.join(self.basedir, '*')):
if not os.path.isdir(host_path): continue if not os.path.isdir(host_path):
continue
services_json_path = os.path.join(host_path, 'services.json') services_json_path = os.path.join(host_path, 'services.json')
if not os.path.isfile(services_json_path): continue if not os.path.isfile(services_json_path):
continue
host_name = os.path.basename(host_path) host_name = os.path.basename(host_path)
self._services[host_name] = {} self._services[host_name] = {}
with open(services_json_path) as f: with open(services_json_path) as f:
for service_name, metadata in json.loads(f.read()).items(): for service_name, metadata in json.loads(f.read()).items():
version = metadata.pop('version') version = metadata.pop('version')
self._services[host_name][service_name] = ServiceResource(service_name, str(version), metadata) self._services[host_name][service_name] = ServiceResource(
service_name, str(version), metadata)
def _get_filesystem_snapshot(self, host):
if not host in self._filesystem_snapshots:
self._filesystem_snapshots[host] = FilesystemSnapshot(os.path.join(self.basedir, host, 'filesystem'))
return self._filesystem_snapshots[host]
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'))
return self._filesystem_snapshots[host]

View File

@ -2,415 +2,528 @@ import sys
from ostack_validator.common import Issue, MarkedIssue, Mark, Version, find, index from ostack_validator.common import Issue, MarkedIssue, Mark, Version, find, index
class SchemaUpdateRecord(object):
# 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
version = Version(version)
self.version = version
self.operation = operation
self.data = data
def __repr__(self): class SchemaUpdateRecord(object):
return '<SchemaUpdateRecord %s %s %s' % (self.version, self.operation, self.data) # 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)
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)
)
class SchemaBuilder(object): class SchemaBuilder(object):
def __init__(self, name, data):
super(SchemaBuilder, self).__init__()
self.name = name
self.data = data
self.current_version = None def __init__(self, name, data):
self.current_section = None super(SchemaBuilder, self).__init__()
self.adds = [] self.name = name
self.removals = [] self.data = data
def __del__(self): self.current_version = None
if len(self.adds) > 0 or len(self.removals) > 0: self.current_section = None
sys.stderr.write("WARNING: Uncommitted config schema \"%s\" version %s\n" % (self.name, self.current_version)) self.adds = []
self.removals = []
def version(self, version, checkpoint=False): def __del__(self):
version = Version(version) 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))
if self.current_version and self.current_version != version: def version(self, version, checkpoint=False):
self.commit() version = Version(version)
if checkpoint or self.data == []: if self.current_version and self.current_version != version:
self.data.append(SchemaUpdateRecord(version, 'checkpoint')) self.commit()
self.current_version = version if checkpoint or self.data == []:
self.data.append(SchemaUpdateRecord(version, 'checkpoint'))
def section(self, name): self.current_version = version
self.current_section = name
def param(self, *args, **kwargs): def section(self, name):
self._ensure_version() self.current_section = name
if not 'section' in kwargs and self.current_section: def param(self, *args, **kwargs):
kwargs['section'] = self.current_section self._ensure_version()
self.adds.append(ConfigParameterSchema(*args, **kwargs)) if not 'section' in kwargs and self.current_section:
kwargs['section'] = self.current_section
def remove_param(self, name): self.adds.append(ConfigParameterSchema(*args, **kwargs))
self._ensure_version()
self.removals.append(name) def remove_param(self, name):
self._ensure_version()
def commit(self): self.removals.append(name)
"Finalize schema building"
self._ensure_version()
if len(self.removals) > 0: def commit(self):
self.data.append(SchemaUpdateRecord(self.current_version, 'remove', self.removals)) "Finalize schema building"
self.removals = [] self._ensure_version()
if len(self.adds) > 0:
self.data.append(SchemaUpdateRecord(self.current_version, 'add', self.adds)) if len(self.removals) > 0:
self.adds = [] 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.adds = []
def _ensure_version(self):
if not self.current_version:
raise Error(
'Schema version is not specified. Please call version() method first')
def _ensure_version(self):
if not self.current_version:
raise Error, 'Schema version is not specified. Please call version() method first'
class ConfigSchemaRegistry: class ConfigSchemaRegistry:
__schemas = {} __schemas = {}
@classmethod
def register_schema(self, project, configname=None):
if not configname:
configname = '%s.conf' % project
fullname = '%s/%s' % (project, configname)
self.__schemas[fullname] = []
return SchemaBuilder(fullname, self.__schemas[fullname])
@classmethod @classmethod
def get_schema(self, project, version, configname=None): def register_schema(self, project, configname=None):
if not configname: if not configname:
configname = '%s.conf' % project configname = '%s.conf' % project
fullname = '%s/%s' % (project, configname) fullname = '%s/%s' % (project, configname)
version = Version(version) self.__schemas[fullname] = []
return SchemaBuilder(fullname, self.__schemas[fullname])
if not fullname in self.__schemas: @classmethod
return None def get_schema(self, project, version, configname=None):
if not configname:
configname = '%s.conf' % project
fullname = '%s/%s' % (project, configname)
version = Version(version)
records = self.__schemas[fullname] if not fullname in self.__schemas:
i = len(records)-1 return None
# Find latest checkpoint prior given version
while i>=0 and not (records[i].operation == 'checkpoint' and records[i].version <= version): i-=1
if i < 0: records = self.__schemas[fullname]
return None 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
parameters = [] if i < 0:
seen_parameters = set() return None
last_version = None
while i < len(records) and records[i].version <= version: parameters = []
last_version = records[i].version seen_parameters = set()
if records[i].operation == 'add': last_version = None
for param in records[i].data:
if param.name in seen_parameters:
old_param_index = index(parameters, lambda p: p.name == param.name)
if old_param_index != -1:
parameters[old_param_index] = param
else:
parameters.append(param)
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)
if index != -1:
parameters.pop(param_index)
seen_parameters.remove(param_name)
i += 1
return ConfigSchema(fullname, last_version, 'ini', parameters) while i < len(records) and records[i].version <= version:
last_version = records[i].version
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)
if old_param_index != -1:
parameters[old_param_index] = param
else:
parameters.append(param)
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)
if index != -1:
parameters.pop(param_index)
seen_parameters.remove(param_name)
i += 1
return ConfigSchema(fullname, last_version, 'ini', parameters)
class ConfigSchema: class ConfigSchema:
def __init__(self, name, version, format, parameters):
self.name = name
self.version = Version(version)
self.format = format
self.parameters = parameters
def has_section(self, section): def __init__(self, name, version, format, parameters):
return find(self.parameters, lambda p: p.section == section) != None self.name = name
self.version = Version(version)
self.format = format
self.parameters = parameters
def has_section(self, section):
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)
)
def __repr__(self):
return (
'<ConfigSchema name=%s version=%s format=%s parameters=%s>' % (
self.name, self.version, self.format, self.parameters)
)
def get_parameter(self, name, section=None):
# TODO: optimize this
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)
class ConfigParameterSchema: class ConfigParameterSchema:
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
self.description = description
self.default = default
self.required = required
self.deprecation_message = deprecation_message
def __repr__(self): def __init__(
return '<ConfigParameterSchema %s>' % ' '.join(['%s=%s' % (attr, getattr(self, attr)) for attr in ['section', 'name', 'type', 'description', 'default', 'required']]) self,
name,
type,
section=None,
description=None,
default=None,
required=False,
deprecation_message=None):
self.section = section
self.name = name
self.type = type
self.description = description
self.default = default
self.required = required
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']])
)
class TypeValidatorRegistry: class TypeValidatorRegistry:
__validators = {} __validators = {}
@classmethod
def register_validator(self, type_name, type_validator):
self.__validators[type_name] = type_validator
@classmethod @classmethod
def get_validator(self, name): def register_validator(self, type_name, type_validator):
return self.__validators[name] self.__validators[type_name] = type_validator
@classmethod
def get_validator(self, name):
return self.__validators[name]
class SchemaError(Issue): class SchemaError(Issue):
def __init__(self, message):
super(SchemaError, self).__init__(Issue.ERROR, message) def __init__(self, message):
super(SchemaError, self).__init__(Issue.ERROR, message)
class InvalidValueError(MarkedIssue): class InvalidValueError(MarkedIssue):
def __init__(self, message, mark=Mark('', 0, 0)):
super(InvalidValueError, self).__init__(Issue.ERROR, 'Invalid value: '+message, mark) def __init__(self, message, mark=Mark('', 0, 0)):
super(
InvalidValueError,
self).__init__(
Issue.ERROR,
'Invalid value: ' +
message,
mark)
class TypeValidator(object): class TypeValidator(object):
def __init__(self, f):
super(TypeValidator, self).__init__()
self.f = f
def validate(self, value): def __init__(self, f):
return getattr(self, 'f')(value) super(TypeValidator, self).__init__()
self.f = f
def validate(self, value):
return getattr(self, 'f')(value)
def type_validator(name, **kwargs): def type_validator(name, **kwargs):
def wrap(fn): def wrap(fn):
def wrapped(s): def wrapped(s):
return fn(s, **kwargs) return fn(s, **kwargs)
o = TypeValidator(wrapped) o = TypeValidator(wrapped)
TypeValidatorRegistry.register_validator(name, o) TypeValidatorRegistry.register_validator(name, o)
return fn return fn
return wrap return wrap
def isissue(o): def isissue(o):
return isinstance(o, Issue) return isinstance(o, Issue)
@type_validator('boolean') @type_validator('boolean')
def validate_boolean(s): def validate_boolean(s):
s = s.lower() s = s.lower()
if s == 'true': if s == 'true':
return True return True
elif s == 'false': elif s == 'false':
return False return False
else: else:
return InvalidValueError('Value should be "true" or "false"') return InvalidValueError('Value should be "true" or "false"')
def validate_enum(s, values=[]): def validate_enum(s, values=[]):
if s in values: if s in values:
return None return None
if len(values) == 0: if len(values) == 0:
message = 'There should be no value' message = 'There should be no value'
elif len(values) == 1: elif len(values) == 1:
message = 'The only valid value is %s' % values[0] message = 'The only valid value is %s' % values[0]
else: else:
message = 'Valid values are %s and %s' % (', '.join(values[:-1]), values[-1]) message = 'Valid values are %s and %s' % (
return InvalidValueError('%s' % message) ', '.join(values[:-1]), values[-1])
return InvalidValueError('%s' % message)
def validate_ipv4_address(s): def validate_ipv4_address(s):
s = s.strip() s = s.strip()
parts = s.split('.') parts = s.split('.')
if len(parts) == 4: if len(parts) == 4:
if all([all([c.isdigit() for c in part]) for part in parts]): if all([all([c.isdigit() for c in part]) for part in parts]):
parts = [int(part) for part in parts] parts = [int(part) for part in parts]
if all([part < 256 for part in parts]): if all([part < 256 for part in parts]):
return '.'.join([str(part) for part in parts]) return '.'.join([str(part) for part in parts])
return InvalidValueError('Value should be ipv4 address')
return InvalidValueError('Value should be ipv4 address')
def validate_ipv4_network(s): def validate_ipv4_network(s):
s = s.strip() s = s.strip()
parts = s.split('/') parts = s.split('/')
if len(parts) != 2: if len(parts) != 2:
return InvalidValueError('Should have "/" character separating address and prefix length') return (
InvalidValueError(
'Should have "/" character separating address and prefix length')
)
address, prefix = parts address, prefix = parts
prefix = prefix.strip() prefix = prefix.strip()
if prefix.strip() == '': if prefix.strip() == '':
return InvalidValueError('Prefix length is required') return InvalidValueError('Prefix length is required')
address = validate_ipv4_address(address) address = validate_ipv4_address(address)
if isissue(address): if isissue(address):
return address return address
if not all([c.isdigit() for c in prefix]): if not all([c.isdigit() for c in prefix]):
return InvalidValueError('Prefix length should be an integer') return InvalidValueError('Prefix length should be an integer')
prefix = int(prefix) prefix = int(prefix)
if prefix > 32: if prefix > 32:
return InvalidValueError('Prefix length should be less than or equal to 32') return (
InvalidValueError(
'Prefix length should be less than or equal to 32')
)
return '%s/%d' % (address, prefix)
return '%s/%d' % (address, prefix)
def validate_host_label(s): def validate_host_label(s):
if len(s) == 0: if len(s) == 0:
return InvalidValueError('Host label should have at least one character') return (
InvalidValueError('Host label should have at least one character')
)
if not s[0].isalpha(): if not s[0].isalpha():
return InvalidValueError('Host label should start with a letter, but it starts with "%s"' % s[0]) return (
InvalidValueError(
'Host label should start with a letter, but it starts with "%s"' %
s[0])
)
if len(s) == 1: return s if len(s) == 1:
return s
if not (s[-1].isdigit() or s[-1].isalpha()): if not (s[-1].isdigit() or s[-1].isalpha()):
return InvalidValueError('Host label should end with letter or digit, but it ends with "%s"' % s[-1], Mark('', 0, len(s)-1)) return (
InvalidValueError(
'Host label should end with letter or digit, but it ends with "%s"' %
s[-1], Mark('', 0, len(s) - 1))
)
if len(s) == 2: return s if len(s) == 2:
return s
for i, c in enumerate(s[1:-1]): for i, c in enumerate(s[1:-1]):
if not (c.isalpha() or c.isdigit() or c == '-'): if not (c.isalpha() or c.isdigit() or c == '-'):
return InvalidValueError('Host label should contain only letters, digits or hypens, but it contains "%s"' % c, Mark('', 0, i+1)) return (
InvalidValueError(
return s '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')
@type_validator('host_address') @type_validator('host_address')
def validate_host_address(s): def validate_host_address(s):
result = validate_ipv4_address(s) result = validate_ipv4_address(s)
if not isissue(result): if not isissue(result):
return result return result
offset = len(s) - len(s.lstrip()) offset = len(s) - len(s.lstrip())
parts = s.strip().split('.') parts = s.strip().split('.')
part_offset = offset part_offset = offset
labels = [] labels = []
for part in parts: for part in parts:
host_label = validate_host_label(part) host_label = validate_host_label(part)
if isissue(host_label): if isissue(host_label):
return host_label.offset_by(Mark('', 0, part_offset)) return host_label.offset_by(Mark('', 0, part_offset))
part_offset += len(part)+1 part_offset += len(part) + 1
labels.append(host_label) labels.append(host_label)
return '.'.join(labels)
return '.'.join(labels)
@type_validator('network') @type_validator('network')
@type_validator('network_address') @type_validator('network_address')
def validate_network_address(s): def validate_network_address(s):
return validate_ipv4_network(s) return validate_ipv4_network(s)
@type_validator('host_and_port') @type_validator('host_and_port')
def validate_host_and_port(s, default_port=None): def validate_host_and_port(s, default_port=None):
parts = s.strip().split(':', 2) parts = s.strip().split(':', 2)
host_address = validate_host_address(parts[0]) host_address = validate_host_address(parts[0])
if isissue(host_address): if isissue(host_address):
return host_address return host_address
if len(parts) == 2: if len(parts) == 2:
port = validate_port(parts[1]) port = validate_port(parts[1])
if isissue(port): if isissue(port):
return port return port
elif default_port: elif default_port:
port = default_port port = default_port
else: else:
return InvalidValueError('No port specified') return InvalidValueError('No port specified')
return (host_address, port)
return (host_address, port)
@type_validator('string') @type_validator('string')
@type_validator('list') @type_validator('list')
@type_validator('multi') @type_validator('multi')
def validate_string(s): def validate_string(s):
return s return s
@type_validator('integer') @type_validator('integer')
def validate_integer(s, min=None, max=None): def validate_integer(s, min=None, max=None):
leading_whitespace_len = 0 leading_whitespace_len = 0
while leading_whitespace_len < len(s) and s[leading_whitespace_len].isspace(): leading_whitespace_len += 1 while leading_whitespace_len < len(s) and s[leading_whitespace_len].isspace():
leading_whitespace_len += 1
s = s.strip() s = s.strip()
if s == '': if s == '':
return InvalidValueError('Should not be empty') return InvalidValueError('Should not be empty')
for i, c in enumerate(s): for i, c in enumerate(s):
if not c.isdigit() and not ((c == '-') and (i == 0)): if not c.isdigit() and not ((c == '-') and (i == 0)):
return InvalidValueError('Only digits are allowed, but found char "%s"' % c, Mark('', 1, i+1+leading_whitespace_len)) return (
InvalidValueError(
'Only digits are allowed, but found char "%s"' %
c, Mark('', 1, i + 1 + leading_whitespace_len))
)
v = int(s) v = int(s)
if min and v < min: if min and v < min:
return InvalidValueError('Should be greater than or equal to %d' % min, Mark('', 1, leading_whitespace_len)) return (
if max and v > max: InvalidValueError(
return InvalidValueError('Should be less than or equal to %d' % max, Mark('', 1, leading_whitespace_len)) '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 v
return v
@type_validator('float') @type_validator('float')
def validate_float(s): def validate_float(s):
# TODO: Implement proper validation # TODO: Implement proper validation
return float(s) return float(s)
@type_validator('port') @type_validator('port')
def validate_port(s, min=1, max=65535): def validate_port(s, min=1, max=65535):
return validate_integer(s, min=min, max=max) return validate_integer(s, min=min, max=max)
@type_validator('string_list') @type_validator('string_list')
def validate_list(s, element_type='string'): def validate_list(s, element_type='string'):
element_type_validator = TypeValidatorRegistry.get_validator(element_type) element_type_validator = TypeValidatorRegistry.get_validator(element_type)
if not element_type_validator: if not element_type_validator:
return SchemaError('Invalid element type "%s"' % element_type) return SchemaError('Invalid element type "%s"' % element_type)
result = [] result = []
s = s.strip() s = s.strip()
if s == '':
return result
values = s.split(',')
for value in values:
validated_value = element_type_validator.validate(value.strip())
if isinstance(validated_value, Issue):
# TODO: provide better position reporting
return validated_value
result.append(validated_value)
if s == '':
return result return result
values = s.split(',')
for value in values:
validated_value = element_type_validator.validate(value.strip())
if isinstance(validated_value, Issue):
# TODO: provide better position reporting
return validated_value
result.append(validated_value)
return result
@type_validator('string_dict') @type_validator('string_dict')
def validate_dict(s, element_type='string'): def validate_dict(s, element_type='string'):
element_type_validator = TypeValidatorRegistry.get_validator(element_type) element_type_validator = TypeValidatorRegistry.get_validator(element_type)
if not element_type_validator: if not element_type_validator:
return SchemaError('Invalid element type "%s"' % element_type) return SchemaError('Invalid element type "%s"' % element_type)
result = {} result = {}
s = s.strip() s = s.strip()
if s == '': if s == '':
return result
pairs = s.split(',')
for pair in pairs:
key_value = pair.split(':', 2)
if len(key_value) < 2:
return (
InvalidValueError(
'Value should be NAME:VALUE pairs separated by ","')
)
key, value = key_value
key = key.strip()
value = value.strip()
if key == '':
# TODO: provide better position reporting
return InvalidValueError('Key name should not be empty')
validated_value = element_type_validator.validate(value)
if isinstance(validated_value, Issue):
# TODO: provide better position reporting
return validated_value
result[key] = validated_value
return result return result
pairs = s.split(',')
for pair in pairs:
key_value = pair.split(':', 2)
if len(key_value) < 2:
return InvalidValueError('Value should be NAME:VALUE pairs separated by ","')
key, value = key_value
key = key.strip()
value = value.strip()
if key == '':
# TODO: provide better position reporting
return InvalidValueError('Key name should not be empty')
validated_value = element_type_validator.validate(value)
if isinstance(validated_value, Issue):
# TODO: provide better position reporting
return validated_value
result[key] = validated_value
return result

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -3,46 +3,57 @@ from ostack_validator.common import find
import unittest import unittest
class ConfigSchemaRegistryTests(unittest.TestCase): class ConfigSchemaRegistryTests(unittest.TestCase):
def test_sample(self):
nova = ConfigSchemaRegistry.register_schema(project='nova')
nova.version('1.0.0') def test_sample(self):
nova.section('DEFAULT') nova = ConfigSchemaRegistry.register_schema(project='nova')
nova.param(name='verbose', type='boolean')
nova.param(name='rabbit_host', type='address')
nova.version('1.1.0') nova.version('1.0.0')
nova.section('DEFAULT') nova.section('DEFAULT')
nova.param(name='verbose', type='boolean', default=False) nova.param(name='verbose', type='boolean')
nova.remove_param('rabbit_host') nova.param(name='rabbit_host', type='address')
nova.commit() nova.version('1.1.0')
nova.section('DEFAULT')
nova.param(name='verbose', type='boolean', default=False)
nova.remove_param('rabbit_host')
schema10 = ConfigSchemaRegistry.get_schema(project='nova', version='1.0.0') nova.commit()
self.assertEqual(Version('1.0.0'), schema10.version) schema10 = ConfigSchemaRegistry.get_schema(
self.assertEqual('ini', schema10.format) project='nova', version='1.0.0')
verbose_param = find(schema10.parameters, lambda p: p.name == 'verbose') self.assertEqual(Version('1.0.0'), schema10.version)
self.assertIsNotNone(verbose_param) self.assertEqual('ini', schema10.format)
self.assertEqual('boolean', verbose_param.type)
self.assertEqual(None, verbose_param.default)
rabbit_host_param = find(schema10.parameters, lambda p: p.name == 'rabbit_host') verbose_param = find(
self.assertIsNotNone(rabbit_host_param) schema10.parameters,
self.assertEqual('address', rabbit_host_param.type) lambda p: p.name == 'verbose')
self.assertIsNotNone(verbose_param)
self.assertEqual('boolean', verbose_param.type)
self.assertEqual(None, verbose_param.default)
schema11 = ConfigSchemaRegistry.get_schema(project='nova', version='1.1.0') rabbit_host_param = find(
schema10.parameters,
lambda p: p.name == 'rabbit_host')
self.assertIsNotNone(rabbit_host_param)
self.assertEqual('address', rabbit_host_param.type)
verbose_param11 = find(schema11.parameters, lambda p: p.name == 'verbose') schema11 = ConfigSchemaRegistry.get_schema(
self.assertIsNotNone(verbose_param11) project='nova', version='1.1.0')
self.assertEqual(False, verbose_param11.default)
rabbit_host_param11 = find(schema11.parameters, lambda p: p.name == 'rabbit_host') verbose_param11 = find(
self.assertIsNone(rabbit_host_param11) 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')
self.assertIsNone(rabbit_host_param11)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,204 +2,210 @@ import unittest
from ostack_validator.config_model import Configuration from ostack_validator.config_model import Configuration
class ConfigurationTests(unittest.TestCase): class ConfigurationTests(unittest.TestCase):
section = 'section1' section = 'section1'
param = 'param1' param = 'param1'
fullparam = '%s.%s' % (section, param) fullparam = '%s.%s' % (section, param)
value = 'foobar' value = 'foobar'
default_value = 'bar123' default_value = 'bar123'
def test_empty(self): def test_empty(self):
c = Configuration() c = Configuration()
self.assertIsNone(c.get('section1.param1')) self.assertIsNone(c.get('section1.param1'))
def test_storage(self):
c = Configuration()
c.set(self.fullparam, self.value)
self.assertEqual(self.value, c.get(self.fullparam)) def test_storage(self):
c = Configuration()
c.set(self.fullparam, self.value)
def test_parameter_names_containing_sections(self): self.assertEqual(self.value, c.get(self.fullparam))
c = Configuration()
c.set(self.fullparam, self.value)
self.assertEqual(self.value, c.get('%s.%s' % (self.section, self.param))) def test_parameter_names_containing_sections(self):
c = Configuration()
def test_parameter_with_default_section(self): c.set(self.fullparam, self.value)
c = Configuration()
c.set(self.param, self.value)
self.assertEqual(self.value, c.get(self.param)) self.assertEqual(
self.value, c.get('%s.%s' %
(self.section, self.param)))
def test_explicit_default_on_get(self): def test_parameter_with_default_section(self):
c = Configuration() c = Configuration()
override_value = '12345' c.set(self.param, self.value)
self.assertEqual(override_value, c.get(self.fullparam, default=override_value)) self.assertEqual(self.value, c.get(self.param))
def test_default(self): def test_explicit_default_on_get(self):
c = Configuration() c = Configuration()
c.set_default(self.fullparam, self.default_value) override_value = '12345'
self.assertEqual(self.default_value, c.get(self.fullparam)) self.assertEqual(
override_value,
c.get(self.fullparam,
default=override_value))
def test_normal_overrides_default(self): def test_default(self):
c = Configuration() c = Configuration()
c.set(self.fullparam, self.value) c.set_default(self.fullparam, self.default_value)
c.set_default(self.fullparam, self.default_value)
self.assertEqual(self.value, c.get(self.fullparam)) self.assertEqual(self.default_value, c.get(self.fullparam))
def test_contains(self): def test_normal_overrides_default(self):
c = Configuration() c = Configuration()
self.assertFalse(c.contains(self.fullparam)) c.set(self.fullparam, self.value)
c.set_default(self.fullparam, self.default_value)
def test_contains_default(self): self.assertEqual(self.value, c.get(self.fullparam))
c = Configuration()
c.set_default(self.fullparam, self.default_value)
self.assertTrue(c.contains(self.fullparam)) def test_contains(self):
self.assertFalse(c.contains(self.fullparam, ignoreDefault=True)) c = Configuration()
self.assertFalse(c.contains(self.fullparam))
def test_contains_normal(self): def test_contains_default(self):
c = Configuration() c = Configuration()
c.set(self.fullparam, self.value) c.set_default(self.fullparam, self.default_value)
self.assertTrue(c.contains(self.fullparam)) self.assertTrue(c.contains(self.fullparam))
self.assertTrue(c.contains(self.fullparam, ignoreDefault=True)) self.assertFalse(c.contains(self.fullparam, ignoreDefault=True))
def test_contains_normal(self):
c = Configuration()
c.set(self.fullparam, self.value)
def test_is_default_returns_false_if_param_missing(self): self.assertTrue(c.contains(self.fullparam))
c = Configuration() self.assertTrue(c.contains(self.fullparam, ignoreDefault=True))
self.assertFalse(c.is_default(self.fullparam))
def test_is_default_returns_true_if_only_default_value_set(self): def test_is_default_returns_false_if_param_missing(self):
c = Configuration() c = Configuration()
c.set_default(self.fullparam, self.default_value) self.assertFalse(c.is_default(self.fullparam))
self.assertTrue(c.is_default(self.fullparam)) def test_is_default_returns_true_if_only_default_value_set(self):
c = Configuration()
c.set_default(self.fullparam, self.default_value)
def test_is_default_returns_false_if_normal_value_set(self): self.assertTrue(c.is_default(self.fullparam))
c = Configuration()
c.set(self.fullparam, self.value)
self.assertFalse(c.is_default(self.fullparam)) def test_is_default_returns_false_if_normal_value_set(self):
c = Configuration()
c.set(self.fullparam, self.value)
def test_is_default_returns_false_if_both_values_set(self): self.assertFalse(c.is_default(self.fullparam))
c = Configuration()
c.set_default(self.fullparam, self.default_value)
c.set(self.fullparam, self.value)
self.assertFalse(c.is_default(self.fullparam)) def test_is_default_returns_false_if_both_values_set(self):
c = Configuration()
c.set_default(self.fullparam, self.default_value)
c.set(self.fullparam, self.value)
def test_subsection_set(self): self.assertFalse(c.is_default(self.fullparam))
c = Configuration()
c.section(self.section).set(self.param, self.value)
self.assertEqual(self.value, c.get(self.fullparam)) def test_subsection_set(self):
c = Configuration()
c.section(self.section).set(self.param, self.value)
def test_keys(self): self.assertEqual(self.value, c.get(self.fullparam))
c = Configuration()
c.set_default('section1.param1', '123')
c.set('section2.param1', '456')
self.assertEqual(['section1', 'section2'], sorted(c.keys())) def test_keys(self):
c = Configuration()
c.set_default('section1.param1', '123')
c.set('section2.param1', '456')
def test_subsection_keys(self): self.assertEqual(['section1', 'section2'], sorted(c.keys()))
c = Configuration()
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())) def test_subsection_keys(self):
c = Configuration()
c.set_default('%s.param1' % self.section, '123')
c.set('%s.param2' % self.section, '456')
def test_subsection_items(self): self.assertEqual(
c = Configuration() ['param1', 'param2'], sorted(c.section(self.section).keys()))
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())) def test_subsection_items(self):
c = Configuration()
c.set('%s.param1' % self.section, 'value1')
c.set_default('%s.param2' % self.section, 'value2')
def test_subsection_get(self): self.assertEqual(
c = Configuration() [('param1', 'value1'), ('param2', 'value2')], sorted(c.section(self.section).items()))
c.set(self.fullparam, self.value) def test_subsection_get(self):
c = Configuration()
cs = c.section(self.section) c.set(self.fullparam, self.value)
self.assertEqual(self.value, cs.get(self.param))
def test_getitem(self): cs = c.section(self.section)
c = Configuration() self.assertEqual(self.value, cs.get(self.param))
c.set(self.fullparam, self.value)
self.assertEqual(self.value, c[self.fullparam]) def test_getitem(self):
c = Configuration()
c.set(self.fullparam, self.value)
def test_subsection_getitem(self): self.assertEqual(self.value, c[self.fullparam])
c = Configuration()
c.set(self.fullparam, self.value)
cs = c.section(self.section) def test_subsection_getitem(self):
c = Configuration()
c.set(self.fullparam, self.value)
self.assertEqual(self.value, cs[self.param]) cs = c.section(self.section)
def test_setitem(self): self.assertEqual(self.value, cs[self.param])
c = Configuration()
c[self.fullparam] = self.value def test_setitem(self):
c = Configuration()
self.assertEqual(self.value, c.get(self.fullparam)) c[self.fullparam] = self.value
def test_subsection_setitem(self): self.assertEqual(self.value, c.get(self.fullparam))
c = Configuration()
cs = c.section(self.section) def test_subsection_setitem(self):
c = Configuration()
cs[self.param] = self.value cs = c.section(self.section)
self.assertEqual(self.value, c.get(self.fullparam)) cs[self.param] = self.value
def test_contains(self): self.assertEqual(self.value, c.get(self.fullparam))
c = Configuration()
self.assertFalse(self.section in c) def test_contains(self):
c = Configuration()
c.set(self.fullparam, self.value) self.assertFalse(self.section in c)
self.assertTrue(self.section in c)
def test_subsection_contains(self): c.set(self.fullparam, self.value)
c = Configuration() self.assertTrue(self.section in c)
c.set('section1.param1', '123') def test_subsection_contains(self):
c.set_default('section2.param2', '234') c = Configuration()
self.assertTrue('param1' in c.section('section1')) c.set('section1.param1', '123')
self.assertTrue('param2' in c.section('section2')) c.set_default('section2.param2', '234')
self.assertFalse('param1' in c.section('section2'))
def test_returns_section_object_even_if_section_doesnot_exist(self):
c = Configuration()
self.assertIsNotNone(c.section('foo'))
def test_template_substitution(self): self.assertTrue('param1' in c.section('section1'))
c = Configuration() self.assertTrue('param2' in c.section('section2'))
c.set('a', 'x') self.assertFalse('param1' in c.section('section2'))
c.set('b', '$a')
c.set('c', '$b')
self.assertEqual('x', c.get('c')) def test_returns_section_object_even_if_section_doesnot_exist(self):
c = Configuration()
self.assertIsNotNone(c.section('foo'))
def test_cycle_template_substitution_resolves_in_empty_string(self): def test_template_substitution(self):
c = Configuration() c = Configuration()
c.set('a', 'a$c') c.set('a', 'x')
c.set('b', 'b$a') c.set('b', '$a')
c.set('c', 'c$b') c.set('c', '$b')
self.assertEqual('cba', c.get('c')) self.assertEqual('x', c.get('c'))
def test_getting_raw_values(self): def test_cycle_template_substitution_resolves_in_empty_string(self):
c = Configuration() c = Configuration()
c.set('a', 'a$c')
c.set('b', 'b$a')
c.set('c', 'c$b')
c.set('a', '$b') self.assertEqual('cba', c.get('c'))
c.set('b', 'x')
self.assertEqual('$b', c.get('a', raw=True)) def test_getting_raw_values(self):
c = Configuration()
c.set('a', '$b')
c.set('b', 'x')
self.assertEqual('$b', c.get('a', raw=True))

View File

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

View File

@ -3,265 +3,276 @@ from ostack_validator.schema import TypeValidatorRegistry
import unittest import unittest
class TypeValidatorTestHelper(object): class TypeValidatorTestHelper(object):
def setUp(self):
super(TypeValidatorTestHelper, self).setUp()
self.validator = TypeValidatorRegistry.get_validator(self.type_name)
def assertValid(self, value): def setUp(self):
self.assertNotIsInstance(self.validator.validate(value), Issue) super(TypeValidatorTestHelper, self).setUp()
self.validator = TypeValidatorRegistry.get_validator(self.type_name)
def assertValid(self, value):
self.assertNotIsInstance(self.validator.validate(value), Issue)
def assertInvalid(self, value):
self.assertIsInstance(self.validator.validate(value), Issue)
def assertInvalid(self, value):
self.assertIsInstance(self.validator.validate(value), Issue)
class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string' type_name = 'string'
def test_empty_string_passes(self): def test_empty_string_passes(self):
self.assertValid('') self.assertValid('')
def test_validation_always_passes(self): def test_validation_always_passes(self):
self.assertValid('foo bar') self.assertValid('foo bar')
def test_should_return_same_string_if_valid(self):
s = 'foo bar'
self.assertEqual(s, self.validator.validate(s))
def test_should_return_same_string_if_valid(self):
s = 'foo bar'
self.assertEqual(s, self.validator.validate(s))
class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class BooleanTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'boolean' type_name = 'boolean'
def test_True(self): def test_True(self):
v = self.validator.validate('True') v = self.validator.validate('True')
self.assertEqual(True, v) self.assertEqual(True, v)
def test_False(self): def test_False(self):
v = self.validator.validate('False') v = self.validator.validate('False')
self.assertEqual(False, v) self.assertEqual(False, v)
def test_other_values_produce_error(self):
self.assertInvalid('foo')
def test_other_values_produce_error(self):
self.assertInvalid('foo')
class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class IntegerTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'integer' type_name = 'integer'
def test_positive_values_are_valid(self): def test_positive_values_are_valid(self):
self.assertValid('123') self.assertValid('123')
def test_zero_is_valid(self): def test_zero_is_valid(self):
self.assertValid('0') self.assertValid('0')
def test_negative_values_are_valid(self): def test_negative_values_are_valid(self):
self.assertValid('-123') self.assertValid('-123')
def test_leading_whitespace_is_ignored(self): def test_leading_whitespace_is_ignored(self):
self.assertValid(' 5') self.assertValid(' 5')
def test_trailing_whitespace_is_ignored(self): def test_trailing_whitespace_is_ignored(self):
self.assertValid('7 ') self.assertValid('7 ')
def test_non_digits_are_invalid(self): def test_non_digits_are_invalid(self):
self.assertInvalid('12a45') self.assertInvalid('12a45')
def test_invalid_char_error_contains_proper_column_in_mark(self): def test_invalid_char_error_contains_proper_column_in_mark(self):
error = self.validator.validate('12a45') error = self.validator.validate('12a45')
self.assertIsInstance(error, MarkedIssue) self.assertIsInstance(error, MarkedIssue)
self.assertEqual(3, error.mark.column) self.assertEqual(3, error.mark.column)
def test_invalid_char_error_contains_proper_column_if_leading_whitespaces(self): def test_invalid_char_error_contains_proper_column_if_leading_whitespaces(
error = self.validator.validate(' 12a45') self):
self.assertIsInstance(error, MarkedIssue) error = self.validator.validate(' 12a45')
self.assertEqual(5, error.mark.column) self.assertIsInstance(error, MarkedIssue)
self.assertEqual(5, error.mark.column)
def test_returns_integer_if_valid(self):
v = self.validator.validate('123')
self.assertEqual(123, v)
def test_returns_integer_if_valid(self):
v = self.validator.validate('123')
self.assertEqual(123, v)
class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class HostAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'host_address' type_name = 'host_address'
def test_ipv4_address(self): def test_ipv4_address(self):
self.assertValid('127.0.0.1') self.assertValid('127.0.0.1')
def test_returns_address(self): def test_returns_address(self):
s = '10.0.0.1' s = '10.0.0.1'
v = self.validator.validate(s) v = self.validator.validate(s)
self.assertEqual(s, v) self.assertEqual(s, v)
def test_value_with_less_than_4_numbers_separated_by_dots(self): def test_value_with_less_than_4_numbers_separated_by_dots(self):
self.assertInvalid('10.0.0') self.assertInvalid('10.0.0')
def test_ipv4_like_string_with_numbers_greater_than_255(self): def test_ipv4_like_string_with_numbers_greater_than_255(self):
self.assertInvalid('10.0.256.1') self.assertInvalid('10.0.256.1')
def test_host_name(self): def test_host_name(self):
self.assertValid('foo.bar.baz') self.assertValid('foo.bar.baz')
def test_host_with_empty_parts(self): def test_host_with_empty_parts(self):
self.assertInvalid('.foo.bar') self.assertInvalid('.foo.bar')
self.assertInvalid('foo..bar') self.assertInvalid('foo..bar')
self.assertInvalid('foo.bar.') self.assertInvalid('foo.bar.')
def test_host_parts_with_invalid_chars(self): def test_host_parts_with_invalid_chars(self):
self.assertInvalid('foo.ba r.baz') self.assertInvalid('foo.ba r.baz')
self.assertInvalid('foo.x_y.bar') self.assertInvalid('foo.x_y.bar')
def test_host_with_single_host_label(self): def test_host_with_single_host_label(self):
self.assertValid('foo') self.assertValid('foo')
def test_host_part_starting_with_non_letter(self): def test_host_part_starting_with_non_letter(self):
self.assertInvalid('123foo') self.assertInvalid('123foo')
def test_host_that_ends_with_a_hyphen(self): def test_host_that_ends_with_a_hyphen(self):
self.assertInvalid('foo-') self.assertInvalid('foo-')
def test_mark_should_point_to_incorrect_symbol(self): def test_mark_should_point_to_incorrect_symbol(self):
e = self.validator.validate('') e = self.validator.validate('')
self.assertEqual(0, e.mark.column) self.assertEqual(0, e.mark.column)
e = self.validator.validate('123foo') e = self.validator.validate('123foo')
self.assertEqual(0, e.mark.column) self.assertEqual(0, e.mark.column)
e = self.validator.validate('foo-') e = self.validator.validate('foo-')
self.assertEqual(3, e.mark.column) self.assertEqual(3, e.mark.column)
e = self.validator.validate('foo.bar.-baz')
self.assertEqual(8, e.mark.column)
e = self.validator.validate('foo.bar.-baz')
self.assertEqual(8, e.mark.column)
class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class NetworkAddressTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'network_address' type_name = 'network_address'
def test_ipv4_network(self): def test_ipv4_network(self):
self.assertValid('127.0.0.1/24') self.assertValid('127.0.0.1/24')
def test_returns_address(self): def test_returns_address(self):
s = '10.0.0.1/32' s = '10.0.0.1/32'
v = self.validator.validate(s) v = self.validator.validate(s)
self.assertEqual(s, v) self.assertEqual(s, v)
def test_value_with_less_than_4_numbers_separated_by_dots(self): def test_value_with_less_than_4_numbers_separated_by_dots(self):
self.assertInvalid('10.0.0/24') self.assertInvalid('10.0.0/24')
def test_ipv4_like_string_with_numbers_greater_than_255(self): def test_ipv4_like_string_with_numbers_greater_than_255(self):
self.assertInvalid('10.0.256.1/24') self.assertInvalid('10.0.256.1/24')
def test_no_prefix_length(self): def test_no_prefix_length(self):
self.assertInvalid('10.0.0.0') self.assertInvalid('10.0.0.0')
self.assertInvalid('10.0.0.0/') self.assertInvalid('10.0.0.0/')
def test_non_integer_prefix_length(self): def test_non_integer_prefix_length(self):
self.assertInvalid('10.0.0.0/1a') self.assertInvalid('10.0.0.0/1a')
def test_prefix_greater_than_32(self):
self.assertInvalid('10.0.0.0/33')
def test_prefix_greater_than_32(self):
self.assertInvalid('10.0.0.0/33')
class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class PortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'port' type_name = 'port'
def test_empty(self): def test_empty(self):
self.assertInvalid('') self.assertInvalid('')
def test_positive_integer(self): def test_positive_integer(self):
self.assertValid('123') self.assertValid('123')
def test_zero_invalid(self): def test_zero_invalid(self):
self.assertInvalid('0') self.assertInvalid('0')
def test_negatives_are_invalid(self): def test_negatives_are_invalid(self):
self.assertInvalid('-1') self.assertInvalid('-1')
def test_values_greater_than_65535_are_invalid(self): def test_values_greater_than_65535_are_invalid(self):
self.assertInvalid('65536') self.assertInvalid('65536')
def test_low_boundary_is_valid(self): def test_low_boundary_is_valid(self):
self.assertValid('1') self.assertValid('1')
def test_high_boundary_is_valid(self): def test_high_boundary_is_valid(self):
self.assertValid('65535') self.assertValid('65535')
def test_non_digits_are_invalid(self): def test_non_digits_are_invalid(self):
self.assertInvalid('12a5') self.assertInvalid('12a5')
def test_leading_and_or_trailing_whitespace_is_ignored(self): def test_leading_and_or_trailing_whitespace_is_ignored(self):
self.assertValid(' 123') self.assertValid(' 123')
self.assertValid('456 ') self.assertValid('456 ')
self.assertValid(' 123 ') self.assertValid(' 123 ')
def test_returns_integer_if_valid(self):
v = self.validator.validate('123')
self.assertEqual(123, v)
def test_returns_integer_if_valid(self):
v = self.validator.validate('123')
self.assertEqual(123, v)
class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class HostAndPortTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'host_and_port' type_name = 'host_and_port'
def test_ipv4_address(self): def test_ipv4_address(self):
self.assertValid('127.0.0.1:80') self.assertValid('127.0.0.1:80')
def test_returns_address(self): def test_returns_address(self):
s = '10.0.0.1:80' s = '10.0.0.1:80'
v = self.validator.validate(s) v = self.validator.validate(s)
self.assertEqual(('10.0.0.1', 80), v) self.assertEqual(('10.0.0.1', 80), v)
def test_value_with_less_than_4_numbers_separated_by_dots(self): def test_value_with_less_than_4_numbers_separated_by_dots(self):
self.assertInvalid('10.0.0:1234') self.assertInvalid('10.0.0:1234')
def test_ipv4_like_string_with_numbers_greater_than_255(self): def test_ipv4_like_string_with_numbers_greater_than_255(self):
self.assertInvalid('10.0.256.1:1234') self.assertInvalid('10.0.256.1:1234')
def test_no_port(self): def test_no_port(self):
self.assertInvalid('10.0.0.1') self.assertInvalid('10.0.0.1')
self.assertInvalid('10.0.0.1:') self.assertInvalid('10.0.0.1:')
def test_port_is_not_an_integer(self): def test_port_is_not_an_integer(self):
self.assertInvalid('10.0.0.1:abc') self.assertInvalid('10.0.0.1:abc')
def test_port_is_greater_than_65535(self):
self.assertInvalid('10.0.0.1:65536')
def test_port_is_greater_than_65535(self):
self.assertInvalid('10.0.0.1:65536')
class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringListTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string_list' type_name = 'string_list'
def test_empty_value(self): def test_empty_value(self):
v = self.validator.validate('') v = self.validator.validate('')
self.assertEqual([], v) self.assertEqual([], v)
def test_single_value(self): def test_single_value(self):
v = self.validator.validate(' foo bar ') v = self.validator.validate(' foo bar ')
self.assertIsInstance(v, list) self.assertIsInstance(v, list)
self.assertEqual('foo bar', v[0]) self.assertEqual('foo bar', v[0])
self.assertEqual(1, len(v)) self.assertEqual(1, len(v))
def test_list_of_values(self): def test_list_of_values(self):
v = self.validator.validate(' foo bar, baz ') v = self.validator.validate(' foo bar, baz ')
self.assertIsInstance(v, list)
self.assertEqual('foo bar', v[0])
self.assertEqual('baz', v[1])
self.assertEqual(2, len(v))
self.assertIsInstance(v, list)
self.assertEqual('foo bar', v[0])
self.assertEqual('baz', v[1])
self.assertEqual(2, len(v))
class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase): class StringDictTypeValidatorTests(TypeValidatorTestHelper, unittest.TestCase):
type_name = 'string_dict' type_name = 'string_dict'
def test_empty_value(self): def test_empty_value(self):
v = self.validator.validate('') v = self.validator.validate('')
self.assertEqual({}, v) self.assertEqual({}, v)
def test_single_value(self): def test_single_value(self):
v = self.validator.validate(' foo: bar ') v = self.validator.validate(' foo: bar ')
self.assertIsInstance(v, dict) self.assertIsInstance(v, dict)
self.assertEqual('bar', v['foo']) self.assertEqual('bar', v['foo'])
self.assertEqual(1, len(v)) self.assertEqual(1, len(v))
def test_list_of_values(self): def test_list_of_values(self):
v = self.validator.validate(' foo: bar, baz: 123 ') v = self.validator.validate(' foo: bar, baz: 123 ')
self.assertIsInstance(v, dict) self.assertIsInstance(v, dict)
self.assertEqual('bar', v['foo']) self.assertEqual('bar', v['foo'])
self.assertEqual('123', v['baz']) self.assertEqual('123', v['baz'])
self.assertEqual(2, len(v)) self.assertEqual(2, len(v))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,62 +2,62 @@ from ostack_validator.schema import Version
import unittest import unittest
class VersionTests(unittest.TestCase): class VersionTests(unittest.TestCase):
def test_creation_from_components(self): def test_creation_from_components(self):
v = Version(1, 3, 7) v = Version(1, 3, 7)
self.assertEqual(1, v.major) self.assertEqual(1, v.major)
self.assertEqual(3, v.minor) self.assertEqual(3, v.minor)
self.assertEqual(7, v.maintenance) self.assertEqual(7, v.maintenance)
def test_creation_from_string(self): def test_creation_from_string(self):
v = Version('1.2.12') v = Version('1.2.12')
self.assertEqual(1, v.major) self.assertEqual(1, v.major)
self.assertEqual(2, v.minor) self.assertEqual(2, v.minor)
self.assertEqual(12, v.maintenance) self.assertEqual(12, v.maintenance)
def test_creation_from_string_with_less_parts(self): def test_creation_from_string_with_less_parts(self):
v = Version('1.2') v = Version('1.2')
self.assertEqual(1, v.major) self.assertEqual(1, v.major)
self.assertEqual(2, v.minor) self.assertEqual(2, v.minor)
self.assertEqual(0, v.maintenance) self.assertEqual(0, v.maintenance)
v = Version('12') v = Version('12')
self.assertEqual(12, v.major) self.assertEqual(12, v.major)
self.assertEqual(0, v.minor) self.assertEqual(0, v.minor)
self.assertEqual(0, v.maintenance) self.assertEqual(0, v.maintenance)
def test_creation_from_other_version(self): def test_creation_from_other_version(self):
v = Version('1.2.3') v = Version('1.2.3')
v2 = Version(v) v2 = Version(v)
self.assertEqual(1, v2.major) self.assertEqual(1, v2.major)
self.assertEqual(2, v2.minor) self.assertEqual(2, v2.minor)
self.assertEqual(3, v2.maintenance) self.assertEqual(3, v2.maintenance)
def test_equility(self): def test_equility(self):
v1 = Version('1.2.3') v1 = Version('1.2.3')
v2 = Version(1, 2, 3) v2 = Version(1, 2, 3)
v3 = Version(1, 2, 4) v3 = Version(1, 2, 4)
self.assertTrue(v1 == v2) self.assertTrue(v1 == v2)
self.assertFalse(v1 == v3) self.assertFalse(v1 == v3)
def test_non_equility(self): def test_non_equility(self):
v1 = Version('1.2.3') v1 = Version('1.2.3')
v2 = Version(1, 2, 3) v2 = Version(1, 2, 3)
v3 = Version(1, 2, 4) v3 = Version(1, 2, 4)
self.assertFalse(v1 != v2) self.assertFalse(v1 != v2)
self.assertTrue(v1 != v3) self.assertTrue(v1 != v3)
def test_comparision(self): def test_comparision(self):
v1 = Version('1.2.3') v1 = Version('1.2.3')
v2 = Version(1, 1, 5) v2 = Version(1, 1, 5)
self.assertTrue(v1 > v2) self.assertTrue(v1 > v2)
self.assertFalse(v1 < v2) self.assertFalse(v1 < v2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,29 +1,34 @@
import collections import collections
import functools 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.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
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)
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.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
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)

View File

@ -14,67 +14,91 @@ app = Flask(__name__)
Bootstrap(app) Bootstrap(app)
app.debug = True app.debug = True
app.config.update( app.config.update(
WTF_CSRF_SECRET_KEY = 'foo bar baz' WTF_CSRF_SECRET_KEY='foo bar baz'
) )
app.secret_key = 'A0Zr98j/3fooN]LWX/,?RT' app.secret_key = 'A0Zr98j/3fooN]LWX/,?RT'
class ValidationLaunchForm(Form): class ValidationLaunchForm(Form):
nodes = StringField('Nodes', validators=[DataRequired()]) nodes = StringField('Nodes', validators=[DataRequired()])
username = StringField('Username', default='root', validators=[DataRequired()]) username = StringField(
private_key = TextAreaField('Private Key', validators=[DataRequired()]) 'Username',
default='root',
validators=[DataRequired()])
private_key = TextAreaField('Private Key', validators=[DataRequired()])
launch = SubmitField('Launch validation') launch = SubmitField('Launch validation')
@app.template_filter() @app.template_filter()
def to_label(s): def to_label(s):
if s in [Issue.FATAL, Issue.ERROR]: if s in [Issue.FATAL, Issue.ERROR]:
return 'label-danger' return 'label-danger'
elif s == Issue.WARNING: elif s == Issue.WARNING:
return 'label-warning' return 'label-warning'
else: else:
return 'label-info' return 'label-info'
@app.route('/') @app.route('/')
def index(): def index():
return redirect('/validation') return redirect('/validation')
@app.route('/validation', methods=['GET', 'POST']) @app.route('/validation', methods=['GET', 'POST'])
def launch_validation(): def launch_validation():
form = ValidationLaunchForm() form = ValidationLaunchForm()
if form.validate_on_submit(): if form.validate_on_submit():
request = InspectionRequest(form.nodes.data.split(' '), form.username.data, private_key=form.private_key.data) request = InspectionRequest(
form.nodes.data.split(
' '),
form.username.data,
private_key=form.private_key.data)
job = ostack_inspect_task.delay(request) job = ostack_inspect_task.delay(request)
return redirect('/validation/%s' % job.id)
else:
return render_template('validation_form.html', form=form)
return redirect('/validation/%s' % job.id)
else:
return render_template('validation_form.html', form=form)
@app.route('/validation/<id>') @app.route('/validation/<id>')
def job(id): def job(id):
job = celery.AsyncResult(id) job = celery.AsyncResult(id)
if job.ready(): if job.ready():
r = job.result.request r = job.result.request
form = ValidationLaunchForm() form = ValidationLaunchForm()
form.nodes.data = ' '.join(r.nodes) form.nodes.data = ' '.join(r.nodes)
form.username.data = r.username form.username.data = r.username
form.private_key.data = r.private_key form.private_key.data = r.private_key
openstack = job.result.value openstack = job.result.value
if isinstance(openstack, Openstack): if isinstance(openstack, Openstack):
issue_source_f = lambda i: i.mark.source if isinstance(i, MarkedIssue) else None issue_source_f = lambda i: i.mark.source if isinstance(
source_groupped_issues = groupby(sorted(openstack.issues, key=issue_source_f), key=issue_source_f) i, MarkedIssue) else None
source_groupped_issues = groupby(
sorted(openstack.issues,
key=issue_source_f),
key=issue_source_f)
return render_template('validation_result.html', form=form, openstack=openstack, grouped_issues=source_groupped_issues) return (
render_template(
'validation_result.html',
form=form,
openstack=openstack,
grouped_issues=source_groupped_issues)
)
else:
return (
render_template(
'validation_error.html',
form=form,
message=openstack)
)
else: else:
return render_template('validation_error.html', form=form, message=openstack) return render_template('validation_state.html', state=job.state)
else:
return render_template('validation_state.html', state=job.state)
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True) app.run(host='0.0.0.0', debug=True)