Increment max FormatVersion

max_format_version is now 2.1.0
use semantic_version (found in global_requirements) for comparisons
added IncorrectFormat exception, to help distinguish format errors from
any other AgentExceptions

Closes Bug: #1441276

Co-Authored-By: Kirill Zaitsev <kzaitsev@mirantis.com>
Change-Id: I0331e4c6c9674eebee1b9b0b73df2283ca082caf
This commit is contained in:
Henar Muñoz Frutos 2015-04-08 15:01:13 +02:00 committed by Kirill Zaitsev
parent 63186f21cd
commit 0e2bea6ff2
6 changed files with 110 additions and 54 deletions

View File

@ -19,7 +19,7 @@ import time
import types
import bunch
import semver
import semantic_version
from muranoagent.common import config
from muranoagent.common import messaging
@ -33,7 +33,7 @@ from muranoagent.openstack.common import service
CONF = config.CONF
LOG = logging.getLogger(__name__)
format_version = '2.0.0'
max_format_version = semantic_version.Spec('<=2.1.0')
class MuranoAgent(service.Service):
@ -143,7 +143,6 @@ class MuranoAgent(service.Service):
delay = min(delay * 1.2, 60)
def _handle_message(self, msg):
print(msg.body)
if 'ID' not in msg.body and msg.id:
msg.body['ID'] = msg.id
try:
@ -159,69 +158,105 @@ class MuranoAgent(service.Service):
LOG.warn('Execution result is not produced')
def _verify_plan(self, plan):
plan_format_version = plan.get('FormatVersion', '1.0.0')
if semver.compare(plan_format_version, '2.0.0') > 0 or \
semver.compare(plan_format_version, format_version) < 0:
range_str = 'in range 2.0.0-{0}'.format(plan_format_version) \
if format_version != '2.0.0' \
else 'equal to {0}'.format(format_version)
raise exc.AgentException(
3,
'Unsupported format version {0} (must be {1})'.format(
plan_format_version, range_str))
plan_format_version = semantic_version.Version(
plan.get('FormatVersion', '1.0.0'))
if plan_format_version not in max_format_version:
# NOTE(kazitsev) this is Version in Spec not str in str
raise exc.IncorrectFormat(
9,
"Unsupported format version {0} "
"(I support versions {1})".format(
plan_format_version, max_format_version))
for attr in ('Scripts', 'Files'):
if attr not in plan:
raise exc.AgentException(
raise exc.IncorrectFormat(
2, '{0} is not in the execution plan'.format(attr))
for attr in ('Scripts', 'Files', 'Options'):
if attr in plan and not isinstance(
plan[attr], types.DictionaryType):
raise exc.AgentException(
raise exc.IncorrectFormat(
2, '{0} is not a dictionary'.format(attr))
for name, script in plan.get('Scripts', {}).items():
for attr in ('Type', 'EntryPoint'):
if attr not in script or not isinstance(
script[attr], types.StringTypes):
raise exc.AgentException(
2, 'Incorrect {0} entry in script {1}'.format(
attr, name))
if not isinstance(script.get('Options', {}), types.DictionaryType):
raise exc.AgentException(
2, 'Incorrect Options entry in script {0}'.format(name))
self._validate_script(name, script, plan_format_version, plan)
if (script['Type'] == 'Application' and
script['EntryPoint'] not in plan.get('Files', {})):
raise exc.AgentException(
for key, plan_file in plan.get('Files', {}).items():
self._validate_file(plan_file, key, plan_format_version)
def _validate_script(self, name, script, plan_format_version, plan):
for attr in ('Type', 'EntryPoint'):
if attr not in script or not isinstance(script[attr],
types.StringTypes):
raise exc.IncorrectFormat(
2, 'Incorrect {0} entry in script {1}'.format(
attr, name))
if plan_format_version in semantic_version.Spec('>=2.0.0,<2.1.0'):
if script['Type'] != 'Application':
raise exc.IncorrectFormat(
2, 'Type {0} is not valid for format {1}'.format(
script['Type'], plan_format_version))
if script['EntryPoint'] not in plan.get('Files', {}):
raise exc.IncorrectFormat(
2, 'Script {0} misses entry point {1}'.format(
name, script['EntryPoint']))
for additional_file in script.get('Files', []):
if plan_format_version in semantic_version.Spec('==2.1.0'):
if script['Type'] not in ('Application', 'Chef', 'Puppet'):
raise exc.IncorrectFormat(
2, 'Script has not a valid type {0}'.format(
script['Type']))
if (script['Type'] == 'Application' and script['EntryPoint']
not in plan.get('Files', {})):
raise exc.IncorrectFormat(
2, 'Script {0} misses entry point {1}'.format(
name, script['EntryPoint']))
elif (script['Type'] != 'Application' and
"::" not in script['EntryPoint']):
raise exc.IncorrectFormat(
2, 'Wrong EntryPoint {0} for Puppet/Chef '
'executors. :: needed'.format(name,
script['EntryPoint']))
for additional_file in script.get('Files', []):
mns_error = ('Script {0} misses file {1}'.
format(name, additional_file))
if isinstance(additional_file, dict):
if (additional_file.keys()[0] not in
plan.get('Files', {}).keys()):
raise exc.AgentException(2, mns_error)
raise exc.IncorrectFormat(2, mns_error)
elif additional_file not in plan.get('Files', {}):
raise exc.AgentException(2, mns_error)
raise exc.IncorrectFormat(2, mns_error)
for key, plan_file in plan.get('Files', {}).items():
if 'Type' in plan_file:
for attr in ('Type', 'URL', 'Name'):
if attr not in plan_file:
raise exc.AgentException(
2, 'Incorrect {0} entry in file {1}'.format(
attr, key))
else:
for attr in ('BodyType', 'Body', 'Name'):
if attr not in plan_file:
raise exc.AgentException(
2, 'Incorrect {0} entry in file {1}'.format(
attr, key))
def _validate_file(self, plan_file, key, format_version):
if format_version in semantic_version.Spec('>=2.0.0,<2.1.0'):
for plan in plan_file.keys():
if plan in ('Type', 'URL'):
raise exc.IncorrectFormat(
2, 'Download file is {0} not valid for this '
'version {1}'.format(key, format_version))
if plan_file['BodyType'] not in ('Text', 'Base64'):
raise exc.AgentException(
if 'Type' in plan_file:
for attr in ('Type', 'URL', 'Name'):
if attr not in plan_file:
raise exc.IncorrectFormat(
2,
'Incorrect {0} entry in file {1}'.format(attr, key))
elif 'Body' in plan_file:
for attr in ('BodyType', 'Body', 'Name'):
if attr not in plan_file:
raise exc.IncorrectFormat(
2, 'Incorrect {0} entry in file {1}'.format(
attr, key))
if plan_file['BodyType'] not in ('Text', 'Base64'):
raise exc.IncorrectFormat(
2, 'Incorrect BodyType in file {1}'.format(key))
else:
raise exc.IncorrectFormat(
2, 'Invalid file {0}: {1}'.format(
key, plan_file))

View File

@ -33,3 +33,7 @@ class CustomException(AgentException):
def __init__(self, code, message=None, additional_data=None):
super(CustomException, self).__init__(
code + 100, message, additional_data)
class IncorrectFormat(AgentException):
pass

View File

@ -36,7 +36,7 @@ class ExPlanDownloable(fixtures.Fixture):
},
},
FormatVersion='2.0.0',
FormatVersion='2.1.0',
ID='ID',
Name='Deploy Chef',
Parameters={
@ -44,7 +44,7 @@ class ExPlanDownloable(fixtures.Fixture):
},
Scripts={
'deploy': {
'EntryPoint': 'cookbook/recipe',
'EntryPoint': 'cookbook::recipe',
'Files': [
'ID1',
'ID2'
@ -85,7 +85,7 @@ class ExPlanApplication(fixtures.Fixture):
'Name': 'common.sh'
}
},
FormatVersion='2.0.0',
FormatVersion='2.1.0',
ID='ID',
Name='Deploy Tomcat',
Parameters={
@ -126,10 +126,10 @@ class ExPlanDownloableNoFiles(fixtures.Fixture):
super(ExPlanDownloableNoFiles, self).setUp()
self.execution_plan = bunch.Bunch(
ID='ID',
FormatVersion='2.0.0',
FormatVersion='2.1.0',
Scripts={
'deploy': {
'EntryPoint': 'cookbook/recipe',
'EntryPoint': 'cookbook::recipe',
'Files': [
'https://github.com/tomcat.git',
{'java': 'https://github.com/java.git'}

View File

@ -43,14 +43,31 @@ class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture):
ID='ID',
FormatVersion='0.0.0',
)
self.assertRaises(exc.AgentException,
self.assertRaises(exc.IncorrectFormat,
self.agent._verify_plan, template)
def test_verify_over_max_execution_plan(self):
template = self.useFixture(ep.ExPlanApplication()).execution_plan
template['FormatVersion'] = '1000.0.0'
self.assertRaises(exc.IncorrectFormat,
self.agent._verify_plan, template)
def test_verify_execution_application(self):
template = self.useFixture(ep.ExPlanApplication()).execution_plan
self.agent._verify_plan(template)
def test_verify_wrong_execution_application(self):
template = self.useFixture(ep.ExPlanApplication()).execution_plan
template['Files']['ID1'] = {
'Name': 'tomcat.git',
'Type': 'Downloadable',
'URL': 'https://github.com/tomcat.git'
}
template['FormatVersion'] = '2.0.0'
self.assertRaises(exc.IncorrectFormat,
self.agent._verify_plan, template)
def test_verify_execution_plan_no_files(self):
template = self.useFixture(ep.ExPlanDownloableNoFiles()).execution_plan
self.assertRaises(exc.AgentException,
self.assertRaises(exc.IncorrectFormat,
self.agent._verify_plan, template)

View File

@ -107,7 +107,7 @@ class TestFileManager(base.MuranoAgentTestCase):
@mock.patch("git.Git")
@mock.patch('os.path.isdir')
@mock.patch('os.makedirs')
def test_putfile_downloable(self, mock_makedir, mock_git, path):
def test_putfile_downloable(self, mock_makedir, path, mock_git):
"""It tests the putfile method when the file is a git
URL.
"""

View File

@ -11,9 +11,9 @@ oslo.config>=1.4.0 # Apache-2.0
PyYAML>=3.1.0
six>=1.7.0
stevedore>=1.0.0 # Apache-2.0
semantic_version>=2.3.1
# not listed in global requirements
semver!=2.0
bunch
gitpython
requests