diff --git a/shade/_utils.py b/shade/_utils.py index 66f3138cc..ccc13b5f1 100644 --- a/shade/_utils.py +++ b/shade/_utils.py @@ -21,6 +21,7 @@ import six import time from decorator import decorator +from heatclient import exc as heat_exc from neutronclient.common import exceptions as neutron_exc from shade import _log @@ -534,6 +535,18 @@ def cache_on_arguments(*cache_on_args, **cache_on_kwargs): return _inner_cache_on_arguments +@contextlib.contextmanager +def heat_exceptions(error_message): + try: + yield + except heat_exc.NotFound as e: + raise exc.OpenStackCloudResourceNotFound( + "{msg}: {exc}".format(msg=error_message, exc=str(e))) + except Exception as e: + raise exc.OpenStackCloudException( + "{msg}: {exc}".format(msg=error_message, exc=str(e))) + + @contextlib.contextmanager def neutron_exceptions(error_message): try: diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index c494057a7..c8900384d 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -32,6 +32,7 @@ import glanceclient.exc import heatclient.client from heatclient.common import event_utils from heatclient.common import template_utils +from heatclient import exc as heat_exceptions import keystoneauth1.exceptions import keystoneclient.client import neutronclient.neutron.client @@ -845,7 +846,7 @@ class OpenStackCloud(object): environment=env, timeout_mins=timeout // 60, ) - with _utils.shade_exceptions("Error creating stack {name}".format( + with _utils.heat_exceptions("Error creating stack {name}".format( name=name)): self.manager.submitTask(_tasks.StackCreate(**params)) if wait: @@ -868,8 +869,8 @@ class OpenStackCloud(object): self.log.debug("Stack %s not found for deleting" % name_or_id) return False - with _utils.shade_exceptions("Failed to delete stack {id}".format( - id=stack['id'])): + with _utils.heat_exceptions("Failed to delete stack {id}".format( + id=name_or_id)): self.manager.submitTask(_tasks.StackDelete(id=stack['id'])) return True @@ -1774,8 +1775,11 @@ class OpenStackCloud(object): # stack names are mandatory and enforced unique in the project # so a StackGet can always be used for name or ID. with _utils.shade_exceptions("Error fetching stack"): - stacks = [self.manager.submitTask( - _tasks.StackGet(stack_id=name_or_id))] + try: + stacks = [self.manager.submitTask( + _tasks.StackGet(stack_id=name_or_id))] + except heat_exceptions.NotFound: + return [] nstacks = _utils.normalize_stacks(stacks) return _utils._filter_list(nstacks, name_or_id, filters) diff --git a/shade/tests/functional/test_stack.py b/shade/tests/functional/test_stack.py index b4d5a09f4..2d08e3970 100644 --- a/shade/tests/functional/test_stack.py +++ b/shade/tests/functional/test_stack.py @@ -21,6 +21,7 @@ Functional tests for `shade` stack methods. import tempfile +from shade import exc from shade import openstack_cloud from shade.tests import base @@ -70,6 +71,8 @@ resource_registry: My::Simple::Template: %s ''' +validate_template = '''heat_template_version: asdf-no-such-version ''' + class TestStack(base.TestCase): @@ -82,6 +85,16 @@ class TestStack(base.TestCase): def _cleanup_stack(self): self.cloud.delete_stack(self.stack_name) + def test_stack_validation(self): + test_template = tempfile.NamedTemporaryFile(delete=False) + test_template.write(validate_template) + test_template.close() + stack_name = self.getUniqueString('validate_template') + self.assertRaises(exc.OpenStackCloudException, + self.cloud.create_stack, + name=stack_name, + template_file=test_template.name) + def test_stack_simple(self): test_template = tempfile.NamedTemporaryFile(delete=False) test_template.write(simple_template) diff --git a/shade/tests/unit/test_stack.py b/shade/tests/unit/test_stack.py index 383523bff..d8b8250a0 100644 --- a/shade/tests/unit/test_stack.py +++ b/shade/tests/unit/test_stack.py @@ -101,10 +101,10 @@ class TestStack(base.TestCase): def test_delete_stack_exception(self, mock_heat, mock_get): stack = {'id': 'stack_id', 'name': 'stack_name'} mock_get.return_value = stack - mock_heat.stacks.delete.side_effect = Exception() + mock_heat.stacks.delete.side_effect = Exception('ouch') with testtools.ExpectedException( shade.OpenStackCloudException, - "Failed to delete stack %s" % stack['id'] + "Failed to delete stack stack_name: ouch" ): self.cloud.delete_stack('stack_name')