From fb8ea73f27f683519dcb27ed9e83cd6e4e423379 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Fri, 11 Dec 2015 16:29:32 -0500 Subject: [PATCH] Stack API improvements The exception that would be raised from list_stacks() if it failed would not have any type of error message (except the inner exception). This could be confusing, so add some text that indicates where the exception is being thrown. Unit tests for this method are added. Also improve some comments in delete_stack() and add unit tests for this method. Change-Id: I979a2948938fc73b708c26d81599061bac7681d4 --- shade/openstackcloud.py | 8 ++-- shade/tests/fakes.py | 8 ++++ shade/tests/unit/test_stack.py | 78 ++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 shade/tests/unit/test_stack.py diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 930b7597e..912b6ff27 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -739,14 +739,14 @@ class OpenStackCloud(object): def delete_stack(self, name_or_id): """Delete a Heat Stack - :param name_or_id: Stack name or id. + :param string name_or_id: Stack name or id. - :returns: True if delete succeeded, False otherwise. + :returns: True if delete succeeded, False if the stack was not found. :raises: ``OpenStackCloudException`` if something goes wrong during the openstack API call """ - stack = self.get_stack(name_or_id=name_or_id) + stack = self.get_stack(name_or_id) if stack is None: self.log.debug("Stack %s not found for deleting" % name_or_id) return False @@ -1054,7 +1054,7 @@ class OpenStackCloud(object): :raises: ``OpenStackCloudException`` if something goes wrong during the openstack API call. """ - with _utils.shade_exceptions(): + with _utils.shade_exceptions("Error fetching stack list"): stacks = self.manager.submitTask(_tasks.StackList()) return stacks diff --git a/shade/tests/fakes.py b/shade/tests/fakes.py index fadf5c1e0..94a5e45f1 100644 --- a/shade/tests/fakes.py +++ b/shade/tests/fakes.py @@ -199,3 +199,11 @@ class FakeHypervisor(object): def __init__(self, id, hostname): self.id = id self.hypervisor_hostname = hostname + + +class FakeStack(object): + def __init__(self, id, name, description=None, status=None): + self.id = id + self.stack_name = name + self.stack_description = description + self.stack_status = status diff --git a/shade/tests/unit/test_stack.py b/shade/tests/unit/test_stack.py new file mode 100644 index 000000000..6cf493724 --- /dev/null +++ b/shade/tests/unit/test_stack.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# 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 mock +import testtools + +import shade +from shade import meta +from shade.tests import fakes +from shade.tests.unit import base + + +class TestStack(base.TestCase): + + def setUp(self): + super(TestStack, self).setUp() + self.cloud = shade.openstack_cloud(validate=False) + + @mock.patch.object(shade.OpenStackCloud, 'heat_client') + def test_list_stacks(self, mock_heat): + fake_stacks = [ + fakes.FakeStack('001', 'stack1'), + fakes.FakeStack('002', 'stack2'), + ] + mock_heat.stacks.list.return_value = fake_stacks + stacks = self.cloud.list_stacks() + mock_heat.stacks.list.assert_called_once_with() + self.assertEqual(meta.obj_list_to_dict(fake_stacks), stacks) + + @mock.patch.object(shade.OpenStackCloud, 'heat_client') + def test_list_stacks_exception(self, mock_heat): + mock_heat.stacks.list.side_effect = Exception() + with testtools.ExpectedException( + shade.OpenStackCloudException, + "Error fetching stack list" + ): + self.cloud.list_stacks() + + @mock.patch.object(shade.OpenStackCloud, 'get_stack') + @mock.patch.object(shade.OpenStackCloud, 'heat_client') + def test_delete_stack(self, mock_heat, mock_get): + stack = {'id': 'stack_id', 'name': 'stack_name'} + mock_get.return_value = stack + self.assertTrue(self.cloud.delete_stack('stack_name')) + mock_get.assert_called_once_with('stack_name') + mock_heat.stacks.delete.assert_called_once_with(id=stack['id']) + + @mock.patch.object(shade.OpenStackCloud, 'get_stack') + @mock.patch.object(shade.OpenStackCloud, 'heat_client') + def test_delete_stack_not_found(self, mock_heat, mock_get): + mock_get.return_value = None + self.assertFalse(self.cloud.delete_stack('stack_name')) + mock_get.assert_called_once_with('stack_name') + self.assertFalse(mock_heat.stacks.delete.called) + + @mock.patch.object(shade.OpenStackCloud, 'get_stack') + @mock.patch.object(shade.OpenStackCloud, 'heat_client') + 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() + with testtools.ExpectedException( + shade.OpenStackCloudException, + "Failed to delete stack %s" % stack['id'] + ): + self.cloud.delete_stack('stack_name')