Support non-application scripts in the execution plan

Adds support for a custom entry point for non-application scripts

Change-Id: I87d03a225c61bf7340d88d6444b054d3ba438fbf
This commit is contained in:
Henar Muñoz Frutos 2015-03-18 12:36:34 +01:00
parent d7af39d3dd
commit 57a58d8698
5 changed files with 368 additions and 33 deletions

View File

@ -84,17 +84,17 @@ class MuranoAgent(service.Service):
msg_iterator.next()
def _run(self, plan):
with execution_plan_runner.ExecutionPlanRunner(plan) as runner:
try:
try:
with execution_plan_runner.ExecutionPlanRunner(plan) as runner:
result = runner.run()
execution_result = ex_result.ExecutionResult.from_result(
result, plan)
self._queue.put_execution_result(execution_result, plan)
except Exception as ex:
LOG.exception('Error running execution plan')
execution_result = ex_result.ExecutionResult.from_error(ex,
plan)
self._queue.put_execution_result(execution_result, plan)
except Exception as ex:
LOG.exception('Error running execution plan')
execution_result = ex_result.ExecutionResult.from_error(ex,
plan)
self._queue.put_execution_result(execution_result, plan)
def _send_result(self, result):
with self._create_rmq_client() as mq:
@ -146,10 +146,10 @@ class MuranoAgent(service.Service):
print(msg.body)
if 'ID' not in msg.body and msg.id:
msg.body['ID'] = msg.id
err = self._verify_plan(msg.body)
if err is None:
try:
self._verify_plan(msg.body)
self._queue.put_execution_plan(msg.body)
else:
except Exception as err:
try:
execution_result = ex_result.ExecutionResult.from_error(
err, bunch.Bunch(msg.body))
@ -165,48 +165,63 @@ class MuranoAgent(service.Service):
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)
return exc.AgentException(
raise exc.AgentException(
3,
'Unsupported format version {0} (must be {1})'.format(
plan_format_version, range_str))
for attr in ('Scripts', 'Files'):
if attr not in plan:
raise exc.AgentException(
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):
return exc.AgentException(
raise exc.AgentException(
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):
return exc.AgentException(
raise exc.AgentException(
2, 'Incorrect {0} entry in script {1}'.format(
attr, name))
if not isinstance(script.get('Options', {}), types.DictionaryType):
return exc.AgentException(
raise exc.AgentException(
2, 'Incorrect Options entry in script {0}'.format(name))
if script['EntryPoint'] not in plan.get('Files', {}):
return exc.AgentException(
if (script['Type'] == 'Application' and
script['EntryPoint'] not in plan.get('Files', {})):
raise exc.AgentException(
2, 'Script {0} misses entry point {1}'.format(
name, script['EntryPoint']))
for additional_file in script.get('Files', []):
if additional_file not in plan.get('Files', {}):
return exc.AgentException(
2, 'Script {0} misses file {1}'.format(
name, additional_file))
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)
elif additional_file not in plan.get('Files', {}):
raise exc.AgentException(2, mns_error)
for key, plan_file in plan.get('Files', {}).items():
for attr in ('BodyType', 'Body', 'Name'):
if attr not in plan_file:
return exc.AgentException(
2, 'Incorrect {0} entry in file {1}'.format(
attr, key))
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))
if plan_file['BodyType'] not in ('Text', 'Base64'):
return exc.AgentException(
2, 'Incorrect BodyType in file {1}'.format(key))
return None
if plan_file['BodyType'] not in ('Text', 'Base64'):
raise exc.AgentException(
2, 'Incorrect BodyType in file {1}'.format(key))

View File

@ -29,7 +29,8 @@ class FunctionRunner(object):
class ScriptRunner(object):
def __init__(self, name, script_info, files_manager):
self._name = name
self._executor = exe.Executors.create_executor(script_info.Type, name)
self._executor = self._get_executor(script_info['Type'], name,
script_info['EntryPoint'])
self._script_info = script_info
self._script_loaded = False
self._files_manager = files_manager
@ -37,6 +38,17 @@ class ScriptRunner(object):
def __call__(self, *args, **kwargs):
return self.execute_function(None, *args, **kwargs)
def _get_executor(self, script_type, script_name, entry_point):
create_executor = exe.Executors.create_executor
if script_type != 'Application':
executor = create_executor(script_type, entry_point)
else:
executor = create_executor(script_type, script_name)
if executor is None:
raise ValueError('The application type in {0} is not a valid '
'executor {1}'.format(script_name, script_type))
return executor
def execute_function(self, name, *args, **kwargs):
self._load()
return self._executor.run(name, *args, **kwargs)
@ -55,5 +67,8 @@ class ScriptRunner(object):
for file_id in self._script_info.get('Files', []):
self._files_manager.put_file(file_id, self._name)
return self._files_manager.put_file(
self._script_info["EntryPoint"], self._name)
if self._script_info["Type"] == 'Application':
return self._files_manager.put_file(
self._script_info["EntryPoint"], self._name)
else:
return self._files_manager._cache_folder

View File

@ -0,0 +1,146 @@
# Copyright (c) 2015 Telefonica I+D.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import bunch
import fixtures
class ExPlanDownloable(fixtures.Fixture):
def setUp(self):
super(ExPlanDownloable, self).setUp()
self.execution_plan = bunch.Bunch(
Action='Execute',
Body='return deploy(args.appName).stdout\n',
Files={
'ID1': {
'Name': 'tomcat.git',
'Type': 'Downloadable',
'URL': 'https://github.com/tomcat.git'
},
'ID2': {
'Name': 'java',
'Type': 'Downloadable',
'URL': 'https://github.com/java.git'
},
},
FormatVersion='2.0.0',
ID='ID',
Name='Deploy Chef',
Parameters={
'appName': '$appName'
},
Scripts={
'deploy': {
'EntryPoint': 'cookbook/recipe',
'Files': [
'ID1',
'ID2'
],
'Options': {
'captureStderr': True,
'captureStdout': True
},
'Type': 'Chef',
'Version': '1.0.0'
}
},
Version='1.0.0'
)
self.addCleanup(delattr, self, 'execution_plan')
class ExPlanApplication(fixtures.Fixture):
def setUp(self):
super(ExPlanApplication, self).setUp()
self.execution_plan = bunch.Bunch(
Action='Execute',
Body='return deploy(args.appName).stdout',
Files={
'ID1': {
'Body': 'text',
'BodyType': 'Text',
'Name': 'deployTomcat.sh'
},
'ID2': {
'Body': 'dGV4dA==\n',
'BodyType': 'Base64',
'Name': 'installer'
},
'ID3': {
'Body': 'dGV4dA==\n',
'BodyType': 'Base64',
'Name': 'common.sh'
}
},
FormatVersion='2.0.0',
ID='ID',
Name='Deploy Tomcat',
Parameters={
'appName': '$appName'
},
Scripts={
'deploy': {
'EntryPoint': 'ID1',
'Files': [
'ID2',
'ID3'
],
'Options': {
'captureStderr': True,
'captureStdout': True
},
'Type': 'Application',
'Version': '1.0.0'
}
},
Version='1.0.0'
)
self.addCleanup(delattr, self, 'execution_plan')
class ExPlanDownloableWrongFormat(fixtures.Fixture):
def setUp(self):
super(ExPlanDownloableWrongFormat, self).setUp()
self.execution_plan = bunch.Bunch(
ID='ID',
FormatVersion='0.0.0'
)
self.addCleanup(delattr, self, 'execution_plan')
class ExPlanDownloableNoFiles(fixtures.Fixture):
def setUp(self):
super(ExPlanDownloableNoFiles, self).setUp()
self.execution_plan = bunch.Bunch(
ID='ID',
FormatVersion='2.0.0',
Scripts={
'deploy': {
'EntryPoint': 'cookbook/recipe',
'Files': [
'https://github.com/tomcat.git',
{'java': 'https://github.com/java.git'}
],
'Options': {
'captureStderr': True,
'captureStdout': True
},
'Type': 'Chef',
'Version': '1.0.0'
}
}
)
self.addCleanup(delattr, self, 'execution_plan')

View File

@ -0,0 +1,56 @@
# Copyright (c) 2015 Telefonica I+D
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import bunch
import fixtures
import mock
from muranoagent import app
from muranoagent.common import config as cfg
from muranoagent import exceptions as exc
from muranoagent.tests.unit import base
from muranoagent.tests.unit import execution_plan as ep
CONF = cfg.CONF
class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture):
@mock.patch('os.path.exists')
def setUp(self, mock_path):
super(TestApp, self).setUp()
mock_path.return_value = True
self.agent = app.MuranoAgent()
CONF.set_override('storage', 'cache')
def test_verify_execution_plan_downloable(self):
template = self.useFixture(ep.ExPlanDownloable()).execution_plan
self.agent._verify_plan(template)
def test_verify_execution_plan_wrong_format(self):
template = bunch.Bunch(
ID='ID',
FormatVersion='0.0.0',
)
self.assertRaises(exc.AgentException,
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_execution_plan_no_files(self):
template = self.useFixture(ep.ExPlanDownloableNoFiles()).execution_plan
self.assertRaises(exc.AgentException,
self.agent._verify_plan, template)

View File

@ -0,0 +1,103 @@
# Copyright (c) 2015 Telefonica I+D
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import bunch
import git
import mock
from muranoagent.common import config as cfg
from muranoagent import files_manager as fmanager
from muranoagent import script_runner
from muranoagent.tests.unit import base
CONF = cfg.CONF
class TestScriptRunner(base.MuranoAgentTestCase):
def setUp(self):
super(TestScriptRunner, self).setUp()
CONF.set_override('storage', 'ss')
@mock.patch("muranoagent.files_manager.FilesManager")
@mock.patch("muranoagent.executors.Executors")
def test_script_runner_downloable(self, mock_file_manager, mock_executors):
mock_file_manager.put_file.return_value = None
mock_executors.create_executor.return_value = None
template = self.get_template_downloable_git()
scripts = script_runner\
.ScriptRunner('deploy',
template.get('Scripts')['deploy'],
mock_file_manager)
scripts._prepare_files()
def _stub_uuid(self, values=[]):
class FakeUUID(object):
def __init__(self, v):
self.hex = v
mock_uuid4 = mock.patch('uuid.uuid4').start()
mock_uuid4.side_effect = [FakeUUID(v) for v in values]
return mock_uuid4
def get_template_downloable_git(self):
return bunch.Bunch(
ID='ID',
Files={
'mycoockbook': {
'Name': 'mycoockbook.txt',
'URL': 'https://github.com/tomcat.git',
'Type': 'Downloadable'
}
},
Scripts={
'deploy': {
'EntryPoint': 'cookbook/recipe',
'Files': [
'https://github.com/tomcat.git',
{'java': 'https://github.com/java.git'}
],
'Options': {
'captureStderr': True,
'captureStdout': True
},
'Type': 'Chef',
'Version': '1.0.0'
}
}
)
def get_template_downloable(self):
return bunch.Bunch(
ID='ID',
Files={
'file': {
'Name': 'myfile',
'URL': 'https://github.com'
'/apache/tomcat/blob/trunk/LICENSE',
'Type': 'Downloadable'
}
}
)
def get_template_file(self):
return bunch.Bunch(
ID='ID',
Files={
'test': {
'Body': 'dGV4dA==\n',
'BodyType': 'Base64',
'Name': 'installer'
}
}
)