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:
parent
d7af39d3dd
commit
57a58d8698
@ -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))
|
||||
|
@ -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
|
||||
|
146
muranoagent/tests/unit/execution_plan.py
Normal file
146
muranoagent/tests/unit/execution_plan.py
Normal 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')
|
56
muranoagent/tests/unit/test_app.py
Normal file
56
muranoagent/tests/unit/test_app.py
Normal 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)
|
103
muranoagent/tests/unit/test_script_runner.py
Normal file
103
muranoagent/tests/unit/test_script_runner.py
Normal 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'
|
||||
}
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue
Block a user