# 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 tempfile import testtools import openstack.cloud from openstack.orchestration.v1 import stack from openstack.tests import fakes from openstack.tests.unit import base class TestStack(base.TestCase): def setUp(self): super(TestStack, self).setUp() self.stack_id = self.getUniqueString('id') self.stack_name = self.getUniqueString('name') self.stack_tag = self.getUniqueString('tag') self.stack = fakes.make_fake_stack(self.stack_id, self.stack_name) def _compare_stacks(self, exp, real): self.assertDictEqual( stack.Stack(**exp).to_dict(computed=False), real.to_dict(computed=False), ) def test_list_stacks(self): fake_stacks = [ self.stack, fakes.make_fake_stack( self.getUniqueString('id'), self.getUniqueString('name') ), ] self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), json={"stacks": fake_stacks}, ), ] ) stacks = self.cloud.list_stacks() [self._compare_stacks(b, a) for a, b in zip(stacks, fake_stacks)] self.assert_calls() def test_list_stacks_filters(self): fake_stacks = [ self.stack, fakes.make_fake_stack( self.getUniqueString('id'), self.getUniqueString('name') ), ] self.register_uris( [ dict( method='GET', uri=self.get_mock_url( 'orchestration', 'public', append=['stacks'], qs_elements=['name=a', 'status=b'], ), json={"stacks": fake_stacks}, ), ] ) stacks = self.cloud.list_stacks(name='a', status='b') [self._compare_stacks(b, a) for a, b in zip(stacks, fake_stacks)] self.assert_calls() def test_list_stacks_exception(self): self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), status_code=404, ) ] ) with testtools.ExpectedException( openstack.cloud.OpenStackCloudURINotFound ): self.cloud.list_stacks() self.assert_calls() def test_search_stacks(self): fake_stacks = [ self.stack, fakes.make_fake_stack( self.getUniqueString('id'), self.getUniqueString('name') ), ] self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), json={"stacks": fake_stacks}, ), ] ) stacks = self.cloud.search_stacks() [self._compare_stacks(b, a) for a, b in zip(stacks, fake_stacks)] self.assert_calls() def test_search_stacks_filters(self): fake_stacks = [ self.stack, fakes.make_fake_stack( self.getUniqueString('id'), self.getUniqueString('name'), status='CREATE_FAILED', ), ] self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), json={"stacks": fake_stacks}, ), ] ) filters = {'status': 'FAILED'} stacks = self.cloud.search_stacks(filters=filters) [self._compare_stacks(b, a) for a, b in zip(stacks, fake_stacks)] self.assert_calls() def test_search_stacks_exception(self): self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), status_code=404, ) ] ) with testtools.ExpectedException( openstack.cloud.OpenStackCloudURINotFound ): self.cloud.search_stacks() def test_delete_stack(self): resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{name}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, resolve=resolve, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": self.stack}, ), dict( method='DELETE', uri='{endpoint}/stacks/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), ), ] ) self.assertTrue(self.cloud.delete_stack(self.stack_name)) self.assert_calls() def test_delete_stack_not_found(self): resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/stack_name?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, resolve=resolve ), status_code=404, ), ] ) self.assertFalse(self.cloud.delete_stack('stack_name')) self.assert_calls() def test_delete_stack_exception(self): resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, resolve=resolve, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": self.stack}, ), dict( method='DELETE', uri='{endpoint}/stacks/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), status_code=400, reason="ouch", ), ] ) with testtools.ExpectedException( openstack.cloud.OpenStackCloudBadRequest ): self.cloud.delete_stack(self.stack_id) self.assert_calls() def test_delete_stack_by_name_wait(self): marker_event = fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='CREATE_COMPLETE', resource_name='name', ) marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id'] ) resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{name}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, resolve=resolve, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": self.stack}, ), dict( method='GET', uri='{endpoint}/stacks/{name}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, qs='limit=1&sort_dir=desc', ), complete_qs=True, json={"events": [marker_event]}, ), dict( method='DELETE', uri='{endpoint}/stacks/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, qs=marker_qs, ), complete_qs=True, json={ "events": [ fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='DELETE_COMPLETE', resource_name='name', ), ] }, ), dict( method='GET', uri='{endpoint}/stacks/{name}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, resolve=resolve, ), status_code=404, ), ] ) self.assertTrue(self.cloud.delete_stack(self.stack_name, wait=True)) self.assert_calls() def test_delete_stack_by_id_wait(self): marker_event = fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='CREATE_COMPLETE', resource_name='name', ) marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id'] ) resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, resolve=resolve, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": self.stack}, ), dict( method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, qs='limit=1&sort_dir=desc', ), complete_qs=True, json={"events": [marker_event]}, ), dict( method='DELETE', uri='{endpoint}/stacks/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), ), dict( method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, qs=marker_qs, ), complete_qs=True, json={ "events": [ fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='DELETE_COMPLETE', ), ] }, ), dict( method='GET', uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, resolve=resolve, ), status_code=404, ), ] ) self.assertTrue(self.cloud.delete_stack(self.stack_id, wait=True)) self.assert_calls() def test_delete_stack_wait_failed(self): failed_stack = self.stack.copy() failed_stack['stack_status'] = 'DELETE_FAILED' marker_event = fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='CREATE_COMPLETE' ) marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id'] ) resolve = 'resolve_outputs=False' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, resolve=resolve, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": self.stack}, ), dict( method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, qs='limit=1&sort_dir=desc', ), complete_qs=True, json={"events": [marker_event]}, ), dict( method='DELETE', uri='{endpoint}/stacks/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), ), dict( method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, qs=marker_qs, ), complete_qs=True, json={ "events": [ fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='DELETE_COMPLETE', ), ] }, ), dict( method='GET', uri='{endpoint}/stacks/{id}?resolve_outputs=False'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( # noqa: E501 endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, resolve=resolve, ), json={"stack": failed_stack}, ), ] ) with testtools.ExpectedException( openstack.cloud.OpenStackCloudException ): self.cloud.delete_stack(self.stack_id, wait=True) self.assert_calls() def test_create_stack(self): test_template = tempfile.NamedTemporaryFile(delete=False) test_template.write(fakes.FAKE_TEMPLATE.encode('utf-8')) test_template.close() self.register_uris( [ dict( method='POST', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), json={"stack": self.stack}, validate=dict( json={ 'disable_rollback': False, 'parameters': {}, 'stack_name': self.stack_name, 'tags': self.stack_tag, 'template': fakes.FAKE_TEMPLATE_CONTENT, 'timeout_mins': 60, } ), ), dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": self.stack}, ), ] ) self.cloud.create_stack( self.stack_name, tags=self.stack_tag, template_file=test_template.name, ) self.assert_calls() def test_create_stack_wait(self): test_template = tempfile.NamedTemporaryFile(delete=False) test_template.write(fakes.FAKE_TEMPLATE.encode('utf-8')) test_template.close() self.register_uris( [ dict( method='POST', uri='{endpoint}/stacks'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT ), json={"stack": self.stack}, validate=dict( json={ 'disable_rollback': False, 'parameters': {}, 'stack_name': self.stack_name, 'tags': self.stack_tag, 'template': fakes.FAKE_TEMPLATE_CONTENT, 'timeout_mins': 60, } ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/events?sort_dir=asc'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), json={ "events": [ fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='CREATE_COMPLETE', resource_name='name', ), ] }, ), dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": self.stack}, ), ] ) self.cloud.create_stack( self.stack_name, tags=self.stack_tag, template_file=test_template.name, wait=True, ) self.assert_calls() def test_update_stack(self): test_template = tempfile.NamedTemporaryFile(delete=False) test_template.write(fakes.FAKE_TEMPLATE.encode('utf-8')) test_template.close() self.register_uris( [ dict( method='PUT', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), validate=dict( json={ 'disable_rollback': False, 'parameters': {}, 'tags': self.stack_tag, 'template': fakes.FAKE_TEMPLATE_CONTENT, 'timeout_mins': 60, } ), json={}, ), dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": self.stack}, ), ] ) self.cloud.update_stack( self.stack_name, tags=self.stack_tag, template_file=test_template.name, ) self.assert_calls() def test_update_stack_wait(self): marker_event = fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='CREATE_COMPLETE', resource_name='name', ) marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id'] ) test_template = tempfile.NamedTemporaryFile(delete=False) test_template.write(fakes.FAKE_TEMPLATE.encode('utf-8')) test_template.close() self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{name}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, qs='limit=1&sort_dir=desc', ), json={"events": [marker_event]}, ), dict( method='PUT', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), validate=dict( json={ 'disable_rollback': False, 'parameters': {}, 'tags': self.stack_tag, 'template': fakes.FAKE_TEMPLATE_CONTENT, 'timeout_mins': 60, } ), json={}, ), dict( method='GET', uri='{endpoint}/stacks/{name}/events?{qs}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, qs=marker_qs, ), json={ "events": [ fakes.make_fake_stack_event( self.stack_id, self.stack_name, status='UPDATE_COMPLETE', resource_name='name', ), ] }, ), dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": self.stack}, ), ] ) self.cloud.update_stack( self.stack_name, tags=self.stack_tag, template_file=test_template.name, wait=True, ) self.assert_calls() def test_get_stack(self): self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": self.stack}, ), ] ) res = self.cloud.get_stack(self.stack_name) self.assertIsNotNone(res) self.assertEqual(self.stack['stack_name'], res['name']) self.assertEqual(self.stack['stack_status'], res['stack_status']) self.assertEqual('CREATE_COMPLETE', res['status']) self.assert_calls() def test_get_stack_in_progress(self): in_progress = self.stack.copy() in_progress['stack_status'] = 'CREATE_IN_PROGRESS' self.register_uris( [ dict( method='GET', uri='{endpoint}/stacks/{name}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, name=self.stack_name, ), status_code=302, headers=dict( location='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ) ), ), dict( method='GET', uri='{endpoint}/stacks/{name}/{id}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name, ), json={"stack": in_progress}, ), ] ) res = self.cloud.get_stack(self.stack_name) self.assertIsNotNone(res) self.assertEqual(in_progress['stack_name'], res.name) self.assertEqual(in_progress['stack_status'], res['stack_status']) self.assertEqual('CREATE_IN_PROGRESS', res['status']) self.assert_calls()