Add Berkshelf support
Add Berkshelf support in murano-agent. If Berkshelf support is enabled (Execution plan with "Type": "Chef" and "useBerkshelf": True), then the image is expected to contain Berkshelf. Change-Id: I036b5b9b7e1202d26fa2fd16591b680755cbfbc1 Partial-Implements: blueprint support-chef-berkshelf
This commit is contained in:
parent
d075358882
commit
ec60c7139e
|
@ -33,7 +33,7 @@ from muranoagent import execution_result as ex_result
|
|||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
max_format_version = semantic_version.Spec('<=2.1.0')
|
||||
max_format_version = semantic_version.Spec('<=2.2.0')
|
||||
|
||||
|
||||
class MuranoAgent(service.Service):
|
||||
|
@ -212,7 +212,7 @@ class MuranoAgent(service.Service):
|
|||
2, 'Script {0} misses entry point {1}'.format(
|
||||
name, script['EntryPoint']))
|
||||
|
||||
if plan_format_version in semantic_version.Spec('==2.1.0'):
|
||||
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(
|
||||
|
@ -229,6 +229,18 @@ class MuranoAgent(service.Service):
|
|||
'executors. :: needed'.format(name,
|
||||
script['EntryPoint']))
|
||||
|
||||
for option in script['Options']:
|
||||
if option in ('useBerkshelf', 'berksfilePath'):
|
||||
if plan_format_version in semantic_version.Spec('<2.2.0'):
|
||||
raise exc.IncorrectFormat(
|
||||
2, 'Script has an option {0} invalid '
|
||||
'for version {1}'.format(option,
|
||||
plan_format_version))
|
||||
elif script['Type'] != 'Chef':
|
||||
raise exc.IncorrectFormat(
|
||||
2, 'Script has an option {0} invalid '
|
||||
'for type {1}'.format(option, script['Type']))
|
||||
|
||||
for additional_file in script.get('Files', []):
|
||||
mns_error = ('Script {0} misses file {1}'.
|
||||
format(name, additional_file))
|
||||
|
|
|
@ -31,6 +31,11 @@ LOG = logging.getLogger(__name__)
|
|||
@executors.executor('Chef')
|
||||
class ChefExecutor(chef_puppet_executor_base.ChefPuppetExecutorBase):
|
||||
|
||||
def load(self, path, options):
|
||||
super(ChefExecutor, self).load(path, options)
|
||||
self._use_berkshelf = options.get('useBerkshelf', False)
|
||||
self._berksfile_path = options.get('berksfilePath', None)
|
||||
|
||||
def run(self, function, recipe_attributes=None, input=None):
|
||||
"""It runs the chef executor.
|
||||
|
||||
|
@ -40,8 +45,10 @@ class ChefExecutor(chef_puppet_executor_base.ChefPuppetExecutorBase):
|
|||
"""
|
||||
self._valid_module_name()
|
||||
|
||||
cookbook_path = self._create_cookbook_path(self.module_name)
|
||||
|
||||
try:
|
||||
self._configure_chef()
|
||||
self._configure_chef(cookbook_path)
|
||||
self._generate_manifest(self.module_name,
|
||||
self.module_recipe, recipe_attributes)
|
||||
except Exception as e:
|
||||
|
@ -61,14 +68,54 @@ class ChefExecutor(chef_puppet_executor_base.ChefPuppetExecutorBase):
|
|||
result = self._execute_command(command)
|
||||
return bunch.Bunch(result)
|
||||
|
||||
def _configure_chef(self):
|
||||
def _create_cookbook_path(self, cookbook_name):
|
||||
"""It defines a path where all required cookbooks are located."""
|
||||
path = os.path.abspath(self._path)
|
||||
|
||||
if self._use_berkshelf:
|
||||
LOG.debug('Using Berkshelf')
|
||||
|
||||
# Get Berksfile
|
||||
if self._berksfile_path is None:
|
||||
self._berksfile_path = cookbook_name + '/Berksfile'
|
||||
berksfile = os.path.join(path, self._berksfile_path)
|
||||
if not os.path.isfile(berksfile):
|
||||
msg = "Berskfile {0} not found".format(berksfile)
|
||||
LOG.debug(msg)
|
||||
raise muranoagent.exceptions.CustomException(
|
||||
0,
|
||||
message=msg,
|
||||
additional_data=None)
|
||||
|
||||
# Create cookbooks path
|
||||
cookbook_path = os.path.join(path, "berks-cookbooks")
|
||||
if not os.path.isdir(cookbook_path):
|
||||
os.makedirs(cookbook_path)
|
||||
|
||||
# Vendor cookbook and its dependencies to cookbook_path
|
||||
command = 'berks vendor --berksfile={0} {1}'.format(
|
||||
berksfile,
|
||||
cookbook_path)
|
||||
result = self._execute_command(command)
|
||||
if result['exitCode'] != 0:
|
||||
raise muranoagent.exceptions.CustomException(
|
||||
0,
|
||||
message='Berks returned error code',
|
||||
additional_data=result)
|
||||
|
||||
return cookbook_path
|
||||
|
||||
else:
|
||||
return path
|
||||
|
||||
def _configure_chef(self, cookbook_path):
|
||||
"""It generates the chef files for configuration."""
|
||||
solo_file = os.path.join(self._path, 'solo.rb')
|
||||
if not os.path.exists(solo_file):
|
||||
if not os.path.isdir(self._path):
|
||||
os.makedirs(self._path)
|
||||
with open(solo_file, "w+") as f:
|
||||
f.write('cookbook_path \"' + self._path + '\"')
|
||||
f.write('cookbook_path \"' + cookbook_path + '\"')
|
||||
|
||||
def _generate_manifest(self, cookbook_name,
|
||||
cookbook_recipe, recipe_attributes):
|
||||
|
|
|
@ -189,3 +189,115 @@ class PuppetExPlanDownloable(fixtures.Fixture):
|
|||
Version='1.0.0'
|
||||
)
|
||||
self.addCleanup(delattr, self, 'execution_plan')
|
||||
|
||||
|
||||
class ExPlanBerkshelf(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(ExPlanBerkshelf, 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'
|
||||
}
|
||||
},
|
||||
FormatVersion='2.2.0',
|
||||
ID='ID',
|
||||
Name='Deploy Chef',
|
||||
Parameters={},
|
||||
Scripts={
|
||||
'deploy': {
|
||||
'EntryPoint': 'cookbook::recipe',
|
||||
'Files': [
|
||||
'ID1'
|
||||
],
|
||||
'Options': {
|
||||
'captureStderr': True,
|
||||
'captureStdout': True,
|
||||
'useBerkshelf': True
|
||||
},
|
||||
'Type': 'Chef',
|
||||
'Version': '1.0.0'
|
||||
}
|
||||
},
|
||||
Version='1.0.0'
|
||||
)
|
||||
self.addCleanup(delattr, self, 'execution_plan')
|
||||
|
||||
|
||||
class ExPlanCustomBerskfile(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(ExPlanCustomBerskfile, 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'
|
||||
}
|
||||
},
|
||||
FormatVersion='2.2.0',
|
||||
ID='ID',
|
||||
Name='Deploy Chef',
|
||||
Parameters={},
|
||||
Scripts={
|
||||
'deploy': {
|
||||
'EntryPoint': 'cookbook::recipe',
|
||||
'Files': [
|
||||
'ID1'
|
||||
],
|
||||
'Options': {
|
||||
'captureStderr': True,
|
||||
'captureStdout': True,
|
||||
'useBerkshelf': True,
|
||||
'berksfilePath': 'custom/customFile'
|
||||
},
|
||||
'Type': 'Chef',
|
||||
'Version': '1.0.0'
|
||||
}
|
||||
},
|
||||
Version='1.0.0'
|
||||
)
|
||||
self.addCleanup(delattr, self, 'execution_plan')
|
||||
|
||||
|
||||
class ExPlanBerkWrongVersion(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(ExPlanBerkWrongVersion, 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'
|
||||
}
|
||||
},
|
||||
FormatVersion='2.1.0',
|
||||
ID='ID',
|
||||
Name='Deploy Chef',
|
||||
Parameters={},
|
||||
Scripts={
|
||||
'deploy': {
|
||||
'EntryPoint': 'cookbook::recipe',
|
||||
'Files': [
|
||||
'ID1'
|
||||
],
|
||||
'Options': {
|
||||
'captureStderr': True,
|
||||
'captureStdout': True,
|
||||
'useBerkshelf': True
|
||||
},
|
||||
'Type': 'Chef',
|
||||
'Version': '1.0.0'
|
||||
}
|
||||
},
|
||||
Version='1.0.0'
|
||||
)
|
||||
self.addCleanup(delattr, self, 'execution_plan')
|
||||
|
|
|
@ -16,6 +16,8 @@ import bunch
|
|||
import fixtures
|
||||
import json
|
||||
import mock
|
||||
from mock import ANY
|
||||
import os
|
||||
|
||||
from muranoagent.common import config as cfg
|
||||
from muranoagent import exceptions as ex
|
||||
|
@ -49,13 +51,16 @@ class TestChefExecutor(base.MuranoAgentTestCase, fixtures.TestWithFixtures):
|
|||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('__builtin__.open')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_cookbook(self, mock_exist, open_mock, mock_subproc_popen):
|
||||
@mock.patch('os.path.isdir')
|
||||
def test_cookbook(self, mock_isdir, mock_exist, open_mock,
|
||||
mock_subproc_popen):
|
||||
"""It tests chef executor."""
|
||||
self._open_mock(open_mock)
|
||||
mock_exist.return_value = True
|
||||
mock_isdir.return_value = True
|
||||
|
||||
process_mock = mock.Mock()
|
||||
attrs = {'communicate.return_value': ('ouput', 'ok'),
|
||||
attrs = {'communicate.return_value': ('output', 'ok'),
|
||||
'poll.return_value': 0}
|
||||
process_mock.configure_mock(**attrs)
|
||||
mock_subproc_popen.return_value = process_mock
|
||||
|
@ -68,13 +73,16 @@ class TestChefExecutor(base.MuranoAgentTestCase, fixtures.TestWithFixtures):
|
|||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('__builtin__.open')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_cookbook_error(self, mock_exist, open_mock, mock_subproc_popen):
|
||||
@mock.patch('os.path.isdir')
|
||||
def test_cookbook_error(self, mock_isdir, mock_exist, open_mock,
|
||||
mock_subproc_popen):
|
||||
"""It tests chef executor with error in the request."""
|
||||
self._open_mock(open_mock)
|
||||
mock_exist.return_value = True
|
||||
mock_isdir.return_value = True
|
||||
|
||||
process_mock = mock.Mock()
|
||||
attrs = {'communicate.return_value': ('ouput', 'error'),
|
||||
attrs = {'communicate.return_value': ('output', 'error'),
|
||||
'poll.return_value': 2}
|
||||
process_mock.configure_mock(**attrs)
|
||||
mock_subproc_popen.return_value = process_mock
|
||||
|
@ -91,6 +99,97 @@ class TestChefExecutor(base.MuranoAgentTestCase, fixtures.TestWithFixtures):
|
|||
self.assertRaises(ex.CustomException, chef_executor.run,
|
||||
'test')
|
||||
|
||||
def test_chef_no_berkshelf(self):
|
||||
"""It tests the cookbook path if Berkshelf is not enabled"""
|
||||
template = self.useFixture(ep.ExPlanDownloable()).execution_plan
|
||||
self.chef_executor.load('path',
|
||||
template['Scripts'].values()[0]['Options'])
|
||||
cookbook_path = self.chef_executor._create_cookbook_path('cookbook')
|
||||
self.assertEqual(cookbook_path, os.path.abspath('path'))
|
||||
|
||||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('os.path.isfile')
|
||||
def test_chef_berkshelf_default_berksfile(self, mock_isfile,
|
||||
mock_subproc_popen):
|
||||
"""It tests Berkshelf usage if no Berksfile path is provided"""
|
||||
mock_isfile.return_value = True
|
||||
|
||||
process_mock = mock.Mock()
|
||||
attrs = {'communicate.return_value': ('output', 'ok'),
|
||||
'poll.return_value': 0}
|
||||
process_mock.configure_mock(**attrs)
|
||||
mock_subproc_popen.return_value = process_mock
|
||||
|
||||
template = self.useFixture(ep.ExPlanBerkshelf()).execution_plan
|
||||
self.chef_executor.load('path',
|
||||
template['Scripts'].values()[0]['Options'])
|
||||
self.chef_executor.module_name = 'test'
|
||||
cookbook_path = self.chef_executor._create_cookbook_path('cookbook')
|
||||
|
||||
self.assertEqual(cookbook_path,
|
||||
os.path.abspath('path/berks-cookbooks'))
|
||||
expected_command = 'berks vendor --berksfile={0} {1}'.format(
|
||||
os.path.abspath('path/cookbook/Berksfile'),
|
||||
cookbook_path)
|
||||
mock_subproc_popen.assert_called_once_with(expected_command,
|
||||
cwd=ANY,
|
||||
shell=ANY,
|
||||
stdout=ANY,
|
||||
stderr=ANY,
|
||||
universal_newlines=ANY)
|
||||
|
||||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('os.path.isfile')
|
||||
def test_chef_berkshelf_custom_berksfile(self, mock_isfile,
|
||||
mock_subproc_popen):
|
||||
"""It tests Berkshelf usage if a custom Berksfile is provided"""
|
||||
mock_isfile.return_value = True
|
||||
|
||||
process_mock = mock.Mock()
|
||||
attrs = {'communicate.return_value': ('output', 'ok'),
|
||||
'poll.return_value': 0}
|
||||
process_mock.configure_mock(**attrs)
|
||||
mock_subproc_popen.return_value = process_mock
|
||||
|
||||
template = self.useFixture(ep.ExPlanCustomBerskfile()).execution_plan
|
||||
self.chef_executor.load('path',
|
||||
template['Scripts'].values()[0]['Options'])
|
||||
self.chef_executor.module_name = 'test'
|
||||
cookbook_path = self.chef_executor._create_cookbook_path('cookbook')
|
||||
|
||||
self.assertEqual(cookbook_path,
|
||||
os.path.abspath('path/berks-cookbooks'))
|
||||
expected_command = 'berks vendor --berksfile={0} {1}'.format(
|
||||
os.path.abspath('path/custom/customFile'),
|
||||
cookbook_path)
|
||||
mock_subproc_popen.assert_called_once_with(expected_command,
|
||||
cwd=ANY,
|
||||
shell=ANY,
|
||||
stdout=ANY,
|
||||
stderr=ANY,
|
||||
universal_newlines=ANY)
|
||||
|
||||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('os.path.isfile')
|
||||
def test_chef_berkshelf_error(self, mock_isfile,
|
||||
mock_subproc_popen):
|
||||
"""It tests if Berkshelf throws an error"""
|
||||
mock_isfile.return_value = True
|
||||
|
||||
process_mock = mock.Mock()
|
||||
attrs = {'communicate.return_value': ('output', 'error'),
|
||||
'poll.return_value': 2}
|
||||
process_mock.configure_mock(**attrs)
|
||||
mock_subproc_popen.return_value = process_mock
|
||||
|
||||
template = self.useFixture(ep.ExPlanBerkshelf()).execution_plan
|
||||
self.chef_executor.load('path',
|
||||
template['Scripts'].values()[0]['Options'])
|
||||
self.chef_executor.module_name = 'test'
|
||||
self.assertRaises(ex.CustomException,
|
||||
self.chef_executor._create_cookbook_path,
|
||||
'cookbook')
|
||||
|
||||
def _open_mock(self, open_mock):
|
||||
context_manager_mock = mock.Mock()
|
||||
open_mock.return_value = context_manager_mock
|
||||
|
|
|
@ -71,3 +71,12 @@ class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture):
|
|||
template = self.useFixture(ep.ExPlanDownloableNoFiles()).execution_plan
|
||||
self.assertRaises(exc.IncorrectFormat,
|
||||
self.agent._verify_plan, template)
|
||||
|
||||
def test_verify_execution_plan_berkshelf(self):
|
||||
template = self.useFixture(ep.ExPlanBerkshelf()).execution_plan
|
||||
self.agent._verify_plan(template)
|
||||
|
||||
def test_verify_execution_plan_berkshelf_wrong_version(self):
|
||||
template = self.useFixture(ep.ExPlanBerkWrongVersion()).execution_plan
|
||||
self.assertRaises(exc.IncorrectFormat,
|
||||
self.agent._verify_plan, template)
|
||||
|
|
Loading…
Reference in New Issue