PEP8 fixes

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1 @@
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

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_3

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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