Move DeployStackAction functionality to utils
Change-Id: I5b1fa484010f88212ddb312f963a8f530e5a78d9
This commit is contained in:
parent
08fe86531b
commit
f6d55d57eb
|
@ -21,15 +21,15 @@ import yaml
|
|||
from heatclient.common import deployment_utils
|
||||
from heatclient import exc as heat_exc
|
||||
from mistral_lib import actions
|
||||
import six
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.actions import base
|
||||
from tripleo_common import constants
|
||||
from tripleo_common import update
|
||||
from tripleo_common.utils import overcloudrc
|
||||
from tripleo_common.utils import plan as plan_utils
|
||||
from tripleo_common.utils import stack as stack_utils
|
||||
from tripleo_common.utils import swift as swiftutils
|
||||
from tripleo_common.utils import template as template_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -143,123 +143,15 @@ class DeployStackAction(base.TripleOAction):
|
|||
def run(self, context):
|
||||
# check to see if the stack exists
|
||||
heat = self.get_orchestration_client(context)
|
||||
try:
|
||||
stack = heat.stacks.get(self.container, resolve_outputs=False)
|
||||
except heat_exc.HTTPNotFound:
|
||||
stack = None
|
||||
|
||||
stack_is_new = stack is None
|
||||
|
||||
# update StackAction, DeployIdentifier and UpdateIdentifier
|
||||
swift = self.get_object_client(context)
|
||||
|
||||
parameters = dict()
|
||||
if not self.skip_deploy_identifier:
|
||||
parameters['DeployIdentifier'] = int(time.time())
|
||||
else:
|
||||
parameters['DeployIdentifier'] = ''
|
||||
parameters['UpdateIdentifier'] = ''
|
||||
parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE'
|
||||
|
||||
try:
|
||||
env = plan_utils.get_env(swift, self.container)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error retrieving environment for plan %s: %s" % (
|
||||
self.container, err))
|
||||
LOG.exception(err_msg)
|
||||
return actions.Result(error=err_msg)
|
||||
|
||||
self.set_tls_parameters(parameters, env)
|
||||
try:
|
||||
plan_utils.update_in_env(swift, env, 'parameter_defaults',
|
||||
parameters)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error updating environment for plan %s: %s" % (
|
||||
self.container, err))
|
||||
LOG.exception(err_msg)
|
||||
return actions.Result(error=err_msg)
|
||||
|
||||
if not stack_is_new:
|
||||
try:
|
||||
LOG.debug('Checking for compatible neutron mechanism drivers')
|
||||
msg = update.check_neutron_mechanism_drivers(env, stack,
|
||||
swift,
|
||||
self.container)
|
||||
if msg:
|
||||
return actions.Result(error=msg)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error getting template %s: %s" % (
|
||||
self.container, err))
|
||||
LOG.exception(err_msg)
|
||||
return actions.Result(error=err_msg)
|
||||
|
||||
# process all plan files and create or update a stack
|
||||
processed_data = template_utils.process_templates(
|
||||
swift, heat, container=self.container,
|
||||
prune_services=True
|
||||
)
|
||||
stack_args = processed_data.copy()
|
||||
stack_args['timeout_mins'] = self.timeout_mins
|
||||
|
||||
if stack_is_new:
|
||||
try:
|
||||
swift.copy_object(
|
||||
"%s-swift-rings" % self.container, "swift-rings.tar.gz",
|
||||
"%s-swift-rings/%s-%d" % (
|
||||
self.container, "swift-rings.tar.gz", time.time()))
|
||||
swift.delete_object(
|
||||
"%s-swift-rings" % self.container, "swift-rings.tar.gz")
|
||||
except swiftexceptions.ClientException:
|
||||
pass
|
||||
LOG.info("Perfoming Heat stack create")
|
||||
try:
|
||||
return heat.stacks.create(**stack_args)
|
||||
except heat_exc.HTTPException as err:
|
||||
err_msg = "Error during stack creation: %s" % (err,)
|
||||
LOG.exception(err_msg)
|
||||
return actions.Result(error=err_msg)
|
||||
|
||||
LOG.info("Performing Heat stack update")
|
||||
stack_args['existing'] = 'true'
|
||||
try:
|
||||
return heat.stacks.update(stack.id, **stack_args)
|
||||
except heat_exc.HTTPException as err:
|
||||
err_msg = "Error during stack update: %s" % (err,)
|
||||
LOG.exception(err_msg)
|
||||
return actions.Result(error=err_msg)
|
||||
|
||||
def set_tls_parameters(self, parameters, env,
|
||||
local_ca_path=constants.LOCAL_CACERT_PATH):
|
||||
cacert_string = self._get_local_cacert(local_ca_path)
|
||||
if cacert_string:
|
||||
parameters['CAMap'] = self._get_updated_camap_entry(
|
||||
'undercloud-ca', cacert_string, self._get_camap(env))
|
||||
|
||||
def _get_local_cacert(self, local_ca_path):
|
||||
# Since the undercloud has TLS by default, we'll add the undercloud's
|
||||
# CA to be trusted by the overcloud.
|
||||
try:
|
||||
with open(local_ca_path, 'rb') as ca_file:
|
||||
return ca_file.read().decode('utf-8')
|
||||
except IOError:
|
||||
# If the file wasn't found it means that the undercloud's TLS
|
||||
# was explicitly disabled or another CA is being used. So we'll
|
||||
# let the user handle this.
|
||||
return None
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def _get_camap(self, env):
|
||||
return env['parameter_defaults'].get('CAMap', {})
|
||||
|
||||
def _get_updated_camap_entry(self, entry_name, cacert, orig_camap):
|
||||
ca_map_entry = {
|
||||
entry_name: {
|
||||
'content': cacert
|
||||
}
|
||||
}
|
||||
orig_camap.update(ca_map_entry)
|
||||
return orig_camap
|
||||
stack_utils.deploy_stack(swift, heat, self.container,
|
||||
self.skip_deploy_identifier,
|
||||
self.timeout_mins)
|
||||
except Exception as err:
|
||||
LOG.exception(six.text_type(err))
|
||||
return actions.Result(six.text_type(err))
|
||||
|
||||
|
||||
class OvercloudRcAction(base.TripleOAction):
|
||||
|
|
|
@ -14,15 +14,12 @@
|
|||
# under the License.
|
||||
import json
|
||||
import mock
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
from heatclient import exc as heat_exc
|
||||
from mistral_lib import actions
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.actions import deployment
|
||||
from tripleo_common import constants
|
||||
from tripleo_common.tests import base
|
||||
|
||||
|
||||
|
@ -204,314 +201,6 @@ class OrchestrationDeployActionTest(base.TestCase):
|
|||
get_obj_client_mock.delete_container.called_once_with('container')
|
||||
|
||||
|
||||
class DeployStackActionTest(base.TestCase):
|
||||
|
||||
@mock.patch('tripleo_common.actions.deployment.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_orchestration_client')
|
||||
def test_run(self, get_orchestration_client_mock,
|
||||
mock_get_object_client, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files,
|
||||
mock_time):
|
||||
|
||||
mock_ctx = mock.MagicMock()
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
mock_get_object_client.return_value = swift
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
get_orchestration_client_mock.return_value = heat
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
action = deployment.DeployStackAction(1, 'overcloud')
|
||||
action.run(mock_ctx)
|
||||
|
||||
# verify parameters are as expected
|
||||
expected_defaults = {'DeployIdentifier': 1473366264,
|
||||
'StackAction': 'CREATE',
|
||||
'UpdateIdentifier': '',
|
||||
'random_existing_data': 'a_value'}
|
||||
|
||||
mock_env_updated = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'parameter_defaults': expected_defaults,
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}]
|
||||
}, default_flow_style=False)
|
||||
|
||||
swift.put_object.assert_called_once_with(
|
||||
'overcloud',
|
||||
constants.PLAN_ENVIRONMENT,
|
||||
mock_env_updated
|
||||
)
|
||||
|
||||
heat.stacks.create.assert_called_once_with(
|
||||
environment={},
|
||||
files={},
|
||||
stack_name='overcloud',
|
||||
template={'heat_template_version': '2016-04-30'},
|
||||
timeout_mins=1,
|
||||
)
|
||||
swift.delete_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz")
|
||||
swift.copy_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz",
|
||||
"overcloud-swift-rings/swift-rings.tar.gz-%d" % 1473366264)
|
||||
|
||||
@mock.patch('tripleo_common.actions.deployment.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_orchestration_client')
|
||||
def test_run_skip_deploy_identifier(
|
||||
self, get_orchestration_client_mock,
|
||||
mock_get_object_client, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files,
|
||||
mock_time):
|
||||
|
||||
mock_ctx = mock.MagicMock()
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_get_object_client.return_value = swift
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
get_orchestration_client_mock.return_value = heat
|
||||
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': constants.DEFAULT_CONTAINER_NAME,
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
action = deployment.DeployStackAction(1, 'overcloud',
|
||||
skip_deploy_identifier=True)
|
||||
action.run(mock_ctx)
|
||||
|
||||
# verify parameters are as expected
|
||||
mock_env_updated = yaml.safe_dump({
|
||||
'name': constants.DEFAULT_CONTAINER_NAME,
|
||||
'temp_environment': 'temp_environment',
|
||||
'parameter_defaults': {'StackAction': 'CREATE',
|
||||
'DeployIdentifier': '',
|
||||
'UpdateIdentifier': '',
|
||||
'random_existing_data': 'a_value'},
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}]
|
||||
}, default_flow_style=False)
|
||||
|
||||
swift.put_object.assert_called_once_with(
|
||||
constants.DEFAULT_CONTAINER_NAME,
|
||||
constants.PLAN_ENVIRONMENT,
|
||||
mock_env_updated
|
||||
)
|
||||
|
||||
heat.stacks.create.assert_called_once_with(
|
||||
environment={},
|
||||
files={},
|
||||
stack_name='overcloud',
|
||||
template={'heat_template_version': '2016-04-30'},
|
||||
timeout_mins=1,
|
||||
)
|
||||
swift.delete_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz")
|
||||
swift.copy_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz",
|
||||
"overcloud-swift-rings/swift-rings.tar.gz-%d" % 1473366264)
|
||||
|
||||
@mock.patch('tripleo_common.actions.deployment.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_orchestration_client')
|
||||
def test_run_create_failed(
|
||||
self, get_orchestration_client_mock, mock_get_object_client,
|
||||
mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files, mock_time):
|
||||
|
||||
mock_ctx = mock.MagicMock()
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
mock_get_object_client.return_value = swift
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
heat.stacks.create.side_effect = heat_exc.HTTPException("Oops")
|
||||
get_orchestration_client_mock.return_value = heat
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
action = deployment.DeployStackAction(1, 'overcloud')
|
||||
expected = actions.Result(
|
||||
error="Error during stack creation: ERROR: Oops\n")
|
||||
self.assertEqual(expected, action.run(mock_ctx))
|
||||
|
||||
@mock.patch('tripleo_common.update.check_neutron_mechanism_drivers')
|
||||
@mock.patch('tripleo_common.actions.deployment.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_orchestration_client')
|
||||
def test_run_update_failed(
|
||||
self, get_orchestration_client_mock, mock_get_object_client,
|
||||
mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files, mock_time,
|
||||
mock_check_neutron_drivers):
|
||||
|
||||
mock_ctx = mock.MagicMock()
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
mock_get_object_client.return_value = swift
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = mock.Mock()
|
||||
heat.stacks.update.side_effect = heat_exc.HTTPException("Oops")
|
||||
get_orchestration_client_mock.return_value = heat
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
mock_check_neutron_drivers.return_value = None
|
||||
|
||||
action = deployment.DeployStackAction(1, 'overcloud')
|
||||
expected = actions.Result(
|
||||
error="Error during stack update: ERROR: Oops\n")
|
||||
self.assertEqual(expected, action.run(mock_ctx))
|
||||
|
||||
def test_set_tls_parameters_no_ca_found(self):
|
||||
action = deployment.DeployStackAction(1, 'overcloud',
|
||||
skip_deploy_identifier=True)
|
||||
my_params = {}
|
||||
my_env = {'parameter_defaults': {}}
|
||||
action.set_tls_parameters(parameters=my_params, env=my_env,
|
||||
local_ca_path='/tmp/my-unexistent-file.txt')
|
||||
self.assertEqual(my_params, {})
|
||||
|
||||
def test_set_tls_parameters_ca_found_no_camap_provided(self):
|
||||
action = deployment.DeployStackAction(1, 'overcloud',
|
||||
skip_deploy_identifier=True)
|
||||
my_params = {}
|
||||
my_env = {'parameter_defaults': {}}
|
||||
with tempfile.NamedTemporaryFile() as ca_file:
|
||||
# Write test data
|
||||
ca_file.write(b'FAKE CA CERT')
|
||||
ca_file.flush()
|
||||
|
||||
# Test
|
||||
action.set_tls_parameters(parameters=my_params, env=my_env,
|
||||
local_ca_path=ca_file.name)
|
||||
self.assertIn('CAMap', my_params)
|
||||
self.assertIn('undercloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['undercloud-ca'])
|
||||
self.assertEqual('FAKE CA CERT',
|
||||
my_params['CAMap']['undercloud-ca']['content'])
|
||||
|
||||
def test_set_tls_parameters_ca_found_camap_provided(self):
|
||||
action = deployment.DeployStackAction(1, 'overcloud',
|
||||
skip_deploy_identifier=True)
|
||||
my_params = {}
|
||||
my_env = {
|
||||
'parameter_defaults': {
|
||||
'CAMap': {'overcloud-ca': {'content': 'ANOTER FAKE CERT'}}}}
|
||||
with tempfile.NamedTemporaryFile() as ca_file:
|
||||
# Write test data
|
||||
ca_file.write(b'FAKE CA CERT')
|
||||
ca_file.flush()
|
||||
|
||||
# Test
|
||||
action.set_tls_parameters(parameters=my_params, env=my_env,
|
||||
local_ca_path=ca_file.name)
|
||||
self.assertIn('CAMap', my_params)
|
||||
self.assertIn('undercloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['undercloud-ca'])
|
||||
self.assertEqual('FAKE CA CERT',
|
||||
my_params['CAMap']['undercloud-ca']['content'])
|
||||
self.assertIn('overcloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['overcloud-ca'])
|
||||
self.assertEqual('ANOTER FAKE CERT',
|
||||
my_params['CAMap']['overcloud-ca']['content'])
|
||||
|
||||
|
||||
class OvercloudRcActionTestCase(base.TestCase):
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.'
|
||||
'get_object_client')
|
||||
|
|
|
@ -12,9 +12,14 @@
|
|||
# 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 tempfile
|
||||
import yaml
|
||||
|
||||
from heatclient import exc as heat_exc
|
||||
import mock
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common import constants
|
||||
from tripleo_common.tests import base
|
||||
from tripleo_common.utils import stack
|
||||
|
||||
|
@ -104,3 +109,274 @@ class UpdateStackTest(base.TestCase):
|
|||
})
|
||||
|
||||
heat.stacks.update.assert_called_once_with('stack_id')
|
||||
|
||||
|
||||
class DeployStackTest(base.TestCase):
|
||||
|
||||
@mock.patch('tripleo_common.utils.stack.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
def test_stack_deploy(
|
||||
self, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files,
|
||||
mock_time):
|
||||
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
stack.deploy_stack(swift, heat, 'overcloud')
|
||||
|
||||
# verify parameters are as expected
|
||||
expected_defaults = {'DeployIdentifier': 1473366264,
|
||||
'StackAction': 'CREATE',
|
||||
'random_existing_data': 'a_value'}
|
||||
|
||||
mock_env_updated = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'parameter_defaults': expected_defaults,
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}]
|
||||
}, default_flow_style=False)
|
||||
|
||||
swift.put_object.assert_called_once_with(
|
||||
'overcloud',
|
||||
constants.PLAN_ENVIRONMENT,
|
||||
mock_env_updated
|
||||
)
|
||||
|
||||
heat.stacks.create.assert_called_once_with(
|
||||
environment={},
|
||||
files={},
|
||||
stack_name='overcloud',
|
||||
template={'heat_template_version': '2016-04-30'},
|
||||
timeout_mins=240,
|
||||
)
|
||||
swift.delete_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz")
|
||||
swift.copy_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz",
|
||||
"overcloud-swift-rings/swift-rings.tar.gz-%d" % 1473366264)
|
||||
|
||||
@mock.patch('tripleo_common.utils.stack.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
def test_run_skip_deploy_identifier(
|
||||
self, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files,
|
||||
mock_time):
|
||||
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': constants.DEFAULT_CONTAINER_NAME,
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
stack.deploy_stack(swift, heat,
|
||||
'overcloud', skip_deploy_identifier=True)
|
||||
|
||||
# verify parameters are as expected
|
||||
mock_env_updated = yaml.safe_dump({
|
||||
'name': constants.DEFAULT_CONTAINER_NAME,
|
||||
'temp_environment': 'temp_environment',
|
||||
'parameter_defaults': {'StackAction': 'CREATE',
|
||||
'DeployIdentifier': '',
|
||||
'random_existing_data': 'a_value'},
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}]
|
||||
}, default_flow_style=False)
|
||||
|
||||
swift.put_object.assert_called_once_with(
|
||||
constants.DEFAULT_CONTAINER_NAME,
|
||||
constants.PLAN_ENVIRONMENT,
|
||||
mock_env_updated
|
||||
)
|
||||
|
||||
heat.stacks.create.assert_called_once_with(
|
||||
environment={},
|
||||
files={},
|
||||
stack_name='overcloud',
|
||||
template={'heat_template_version': '2016-04-30'},
|
||||
timeout_mins=240,
|
||||
)
|
||||
swift.delete_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz")
|
||||
swift.copy_object.assert_called_once_with(
|
||||
"overcloud-swift-rings", "swift-rings.tar.gz",
|
||||
"overcloud-swift-rings/swift-rings.tar.gz-%d" % 1473366264)
|
||||
|
||||
@mock.patch('tripleo_common.utils.stack.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
def test_run_create_failed(
|
||||
self, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files,
|
||||
mock_time):
|
||||
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = None
|
||||
heat.stacks.create.side_effect = heat_exc.HTTPException("Oops")
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
|
||||
self.assertRaises(RuntimeError, stack.deploy_stack,
|
||||
swift, heat, 'overcloud')
|
||||
|
||||
@mock.patch('tripleo_common.update.check_neutron_mechanism_drivers')
|
||||
@mock.patch('tripleo_common.utils.stack.time')
|
||||
@mock.patch('heatclient.common.template_utils.'
|
||||
'process_multiple_environments_and_files')
|
||||
@mock.patch('heatclient.common.template_utils.get_template_contents')
|
||||
def test_run_update_failed(
|
||||
self, mock_get_template_contents,
|
||||
mock_process_multiple_environments_and_files, mock_time,
|
||||
mock_check_neutron_drivers):
|
||||
|
||||
# setup swift
|
||||
swift = mock.MagicMock(url="http://test.com")
|
||||
mock_env = yaml.safe_dump({
|
||||
'name': 'overcloud',
|
||||
'temp_environment': 'temp_environment',
|
||||
'template': 'template',
|
||||
'environments': [{u'path': u'environments/test.yaml'}],
|
||||
'parameter_defaults': {'random_existing_data': 'a_value'},
|
||||
}, default_flow_style=False)
|
||||
swift.get_object.side_effect = (
|
||||
({}, mock_env),
|
||||
({}, mock_env),
|
||||
swiftexceptions.ClientException('atest2')
|
||||
)
|
||||
|
||||
heat = mock.MagicMock()
|
||||
heat.stacks.get.return_value = mock.Mock()
|
||||
heat.stacks.update.side_effect = heat_exc.HTTPException("Oops")
|
||||
|
||||
mock_get_template_contents.return_value = ({}, {
|
||||
'heat_template_version': '2016-04-30'
|
||||
})
|
||||
mock_process_multiple_environments_and_files.return_value = ({}, {})
|
||||
|
||||
# freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24)
|
||||
mock_time.time.return_value = 1473366264
|
||||
mock_check_neutron_drivers.return_value = None
|
||||
|
||||
self.assertRaises(RuntimeError, stack.deploy_stack,
|
||||
swift, heat, 'overcloud')
|
||||
|
||||
def test_set_tls_parameters_no_ca_found(self):
|
||||
my_params = {}
|
||||
my_env = {'parameter_defaults': {}}
|
||||
stack.set_tls_parameters(
|
||||
parameters=my_params, env=my_env,
|
||||
local_ca_path='/tmp/my-unexistent-file.txt')
|
||||
self.assertEqual(my_params, {})
|
||||
|
||||
def test_set_tls_parameters_ca_found_no_camap_provided(self):
|
||||
my_params = {}
|
||||
my_env = {'parameter_defaults': {}}
|
||||
with tempfile.NamedTemporaryFile() as ca_file:
|
||||
# Write test data
|
||||
ca_file.write(b'FAKE CA CERT')
|
||||
ca_file.flush()
|
||||
|
||||
# Test
|
||||
stack.set_tls_parameters(
|
||||
parameters=my_params, env=my_env,
|
||||
local_ca_path=ca_file.name)
|
||||
self.assertIn('CAMap', my_params)
|
||||
self.assertIn('undercloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['undercloud-ca'])
|
||||
self.assertEqual(
|
||||
'FAKE CA CERT',
|
||||
my_params['CAMap']['undercloud-ca']['content'])
|
||||
|
||||
def test_set_tls_parameters_ca_found_camap_provided(self):
|
||||
my_params = {}
|
||||
my_env = {
|
||||
'parameter_defaults': {
|
||||
'CAMap': {'overcloud-ca': {'content': 'ANOTER FAKE CERT'}}}}
|
||||
with tempfile.NamedTemporaryFile() as ca_file:
|
||||
# Write test data
|
||||
ca_file.write(b'FAKE CA CERT')
|
||||
ca_file.flush()
|
||||
|
||||
# Test
|
||||
stack.set_tls_parameters(
|
||||
parameters=my_params, env=my_env,
|
||||
local_ca_path=ca_file.name)
|
||||
self.assertIn('CAMap', my_params)
|
||||
self.assertIn('undercloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['undercloud-ca'])
|
||||
self.assertEqual('FAKE CA CERT',
|
||||
my_params['CAMap']['undercloud-ca']['content'])
|
||||
self.assertIn('overcloud-ca', my_params['CAMap'])
|
||||
self.assertIn('content', my_params['CAMap']['overcloud-ca'])
|
||||
self.assertEqual('ANOTER FAKE CERT',
|
||||
my_params['CAMap']['overcloud-ca']['content'])
|
||||
|
|
|
@ -23,6 +23,7 @@ from heatclient import exc as heat_exc
|
|||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common import constants
|
||||
from tripleo_common import update
|
||||
from tripleo_common.utils import plan as plan_utils
|
||||
from tripleo_common.utils import template as templates
|
||||
|
||||
|
@ -154,3 +155,125 @@ def validate_stack_and_flatten_parameters(heat, processed_data, env):
|
|||
processed_data['heat_resource_tree'])
|
||||
processed_data['heat_resource_tree'] = flattened
|
||||
return processed_data
|
||||
|
||||
|
||||
def deploy_stack(swift, heat, container, skip_deploy_identifier=False,
|
||||
timeout_mins=240):
|
||||
try:
|
||||
stack = heat.stacks.get(container, resolve_outputs=False)
|
||||
except heat_exc.HTTPNotFound:
|
||||
stack = None
|
||||
|
||||
stack_is_new = stack is None
|
||||
|
||||
# update StackAction, DeployIdentifier and UpdateIdentifier
|
||||
|
||||
parameters = dict()
|
||||
if not skip_deploy_identifier:
|
||||
parameters['DeployIdentifier'] = int(time.time())
|
||||
else:
|
||||
parameters['DeployIdentifier'] = ''
|
||||
parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE'
|
||||
|
||||
try:
|
||||
env = plan_utils.get_env(swift, container)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error retrieving environment for plan %s: %s" % (
|
||||
container, err))
|
||||
LOG.exception(err_msg)
|
||||
raise RuntimeError(err_msg)
|
||||
|
||||
set_tls_parameters(parameters, env)
|
||||
try:
|
||||
plan_utils.update_in_env(swift, env, 'parameter_defaults',
|
||||
parameters)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error updating environment for plan %s: %s" % (
|
||||
container, err))
|
||||
LOG.exception(err_msg)
|
||||
raise RuntimeError(err_msg)
|
||||
|
||||
if not stack_is_new:
|
||||
try:
|
||||
LOG.debug('Checking for compatible neutron mechanism drivers')
|
||||
msg = update.check_neutron_mechanism_drivers(env, stack,
|
||||
swift,
|
||||
container)
|
||||
if msg:
|
||||
raise RuntimeError(msg)
|
||||
except swiftexceptions.ClientException as err:
|
||||
err_msg = ("Error getting template %s: %s" % (
|
||||
container, err))
|
||||
LOG.exception(err_msg)
|
||||
raise RuntimeError(err_msg)
|
||||
|
||||
# process all plan files and create or update a stack
|
||||
processed_data = templates.process_templates(
|
||||
swift, heat, container=container,
|
||||
prune_services=True
|
||||
)
|
||||
stack_args = processed_data.copy()
|
||||
stack_args['timeout_mins'] = timeout_mins
|
||||
|
||||
if stack_is_new:
|
||||
try:
|
||||
swift.copy_object(
|
||||
"%s-swift-rings" % container, "swift-rings.tar.gz",
|
||||
"%s-swift-rings/%s-%d" % (
|
||||
container, "swift-rings.tar.gz", time.time()))
|
||||
swift.delete_object(
|
||||
"%s-swift-rings" % container, "swift-rings.tar.gz")
|
||||
except swiftexceptions.ClientException:
|
||||
pass
|
||||
LOG.info("Perfoming Heat stack create")
|
||||
try:
|
||||
return heat.stacks.create(**stack_args)
|
||||
except heat_exc.HTTPException as err:
|
||||
err_msg = "Error during stack creation: %s" % (err,)
|
||||
LOG.exception(err_msg)
|
||||
raise RuntimeError(err_msg)
|
||||
|
||||
LOG.info("Performing Heat stack update")
|
||||
stack_args['existing'] = 'true'
|
||||
try:
|
||||
return heat.stacks.update(stack.id, **stack_args)
|
||||
except heat_exc.HTTPException as err:
|
||||
err_msg = "Error during stack update: %s" % (err,)
|
||||
LOG.exception(err_msg)
|
||||
raise RuntimeError(err_msg)
|
||||
|
||||
|
||||
def set_tls_parameters(parameters, env,
|
||||
local_ca_path=constants.LOCAL_CACERT_PATH):
|
||||
|
||||
def get_camap():
|
||||
return env['parameter_defaults'].get('CAMap', {})
|
||||
|
||||
def get_updated_camap_entry(entry_name, cacert, orig_camap):
|
||||
ca_map_entry = {
|
||||
entry_name: {
|
||||
'content': cacert
|
||||
}
|
||||
}
|
||||
orig_camap.update(ca_map_entry)
|
||||
return orig_camap
|
||||
|
||||
cacert_string = get_local_cacert(local_ca_path)
|
||||
if cacert_string:
|
||||
parameters['CAMap'] = get_updated_camap_entry(
|
||||
'undercloud-ca', cacert_string, get_camap())
|
||||
|
||||
|
||||
def get_local_cacert(local_ca_path):
|
||||
# Since the undercloud has TLS by default, we'll add the undercloud's
|
||||
# CA to be trusted by the overcloud.
|
||||
try:
|
||||
with open(local_ca_path, 'rb') as ca_file:
|
||||
return ca_file.read().decode('utf-8')
|
||||
except IOError:
|
||||
# If the file wasn't found it means that the undercloud's TLS
|
||||
# was explicitly disabled or another CA is being used. So we'll
|
||||
# let the user handle this.
|
||||
return None
|
||||
except Exception:
|
||||
raise
|
||||
|
|
Loading…
Reference in New Issue