# # 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 six import swiftclient.client as sc from heat.common import exception from heat.common import template_format from heat.engine import node_data from heat.engine.resources.openstack.swift import container as swift_c from heat.engine import scheduler from heat.tests import common from heat.tests import utils SWIFT_TEMPLATE = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to test OS::Swift::Container resources", "Resources" : { "SwiftContainerWebsite" : { "Type" : "OS::Swift::Container", "DeletionPolicy" : "Delete", "Properties" : { "X-Container-Read" : ".r:*", "X-Container-Meta" : { "Web-Index" : "index.html", "Web-Error" : "error.html" } } }, "SwiftAccountMetadata" : { "Type" : "OS::Swift::Container", "DeletionPolicy" : "Delete", "Properties" : { "X-Account-Meta" : { "Temp-Url-Key" : "secret" } } }, "S3Bucket" : { "Type" : "AWS::S3::Bucket", "Properties" : { "SwiftContainer" : {"Ref" : "SwiftContainer"} } }, "SwiftContainer" : { "Type" : "OS::Swift::Container", "Properties" : { } } } } ''' class SwiftTest(common.HeatTestCase): def setUp(self): super(SwiftTest, self).setUp() self.t = template_format.parse(SWIFT_TEMPLATE) def _create_container(self, stack, definition_name='SwiftContainer'): resource_defns = stack.t.resource_definitions(stack) container = swift_c.SwiftContainer('test_resource', resource_defns[definition_name], stack) runner = scheduler.TaskRunner(container.create) runner() self.assertEqual((container.CREATE, container.COMPLETE), container.state) return container @mock.patch('swiftclient.client.Connection.put_container') def test_create_container_name(self, mock_put): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['name'] = 'the_name' stack = utils.parse_stack(self.t) # Test container = self._create_container(stack) container_name = container.physical_resource_name() # Verify self.assertEqual('the_name', container_name) mock_put.assert_called_once_with('the_name', {}) def test_build_meta_headers(self): # Setup headers = {'Web-Index': 'index.html', 'Web-Error': 'error.html'} # Test self.assertEqual({}, swift_c.SwiftContainer._build_meta_headers( 'container', {})) self.assertEqual({}, swift_c.SwiftContainer._build_meta_headers( 'container', None)) built = swift_c.SwiftContainer._build_meta_headers( 'container', headers) # Verify expected = { 'X-Container-Meta-Web-Index': 'index.html', 'X-Container-Meta-Web-Error': 'error.html' } self.assertEqual(expected, built) @mock.patch('swiftclient.client.Connection.head_container') @mock.patch('swiftclient.client.Connection.put_container') def test_attributes(self, mock_put, mock_head): # Setup headers = {'content-length': '0', 'x-container-object-count': '82', 'accept-ranges': 'bytes', 'x-trans-id': 'tx08ea48ef2fa24e6da3d2f5c188fd938b', 'date': 'Wed, 23 Jan 2013 22:48:05 GMT', 'x-timestamp': '1358980499.84298', 'x-container-read': '.r:*', 'x-container-bytes-used': '17680980', 'content-type': 'text/plain; charset=utf-8'} mock_head.return_value = headers stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test container = self._create_container(stack) # call this to populate the url of swiftclient. This is actually # set in head_container/put_container, but we're patching them in # this test. container.client().get_auth() # Verify Attributes self.assertEqual(container_name, container.FnGetRefId()) self.assertEqual('82', container.FnGetAtt('ObjectCount')) self.assertEqual('17680980', container.FnGetAtt('BytesUsed')) self.assertEqual('server.test', container.FnGetAtt('DomainName')) self.assertEqual(headers, container.FnGetAtt('HeadContainer')) self.assertEqual(headers, container.FnGetAtt('show')) expected_url = 'http://server.test:5000/v3/%s' % container.FnGetRefId() self.assertEqual(expected_url, container.FnGetAtt('WebsiteURL')) self.assertRaises(exception.InvalidTemplateAttribute, container.FnGetAtt, 'Foo') # Verify Expected Calls mock_put.assert_called_once_with(container_name, {}) self.assertGreater(mock_head.call_count, 0) @mock.patch('swiftclient.client.Connection.put_container') def test_public_read(self, mock_put): # Setup properties = self.t['Resources']['SwiftContainer']['Properties'] properties['X-Container-Read'] = '.r:*' stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test self._create_container(stack) # Verify expected = {'X-Container-Read': '.r:*'} mock_put.assert_called_once_with(container_name, expected) @mock.patch('swiftclient.client.Connection.put_container') def test_public_read_write(self, mock_put): # Setup properties = self.t['Resources']['SwiftContainer']['Properties'] properties['X-Container-Read'] = '.r:*' properties['X-Container-Write'] = '.r:*' stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test self._create_container(stack) # Verify expected = {'X-Container-Write': '.r:*', 'X-Container-Read': '.r:*'} mock_put.assert_called_once_with(container_name, expected) @mock.patch('swiftclient.client.Connection.put_container') def test_container_headers(self, mock_put): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test self._create_container(stack, definition_name='SwiftContainerWebsite') # Verify expected = {'X-Container-Meta-Web-Error': 'error.html', 'X-Container-Meta-Web-Index': 'index.html', 'X-Container-Read': '.r:*'} mock_put.assert_called_once_with(container_name, expected) @mock.patch('swiftclient.client.Connection.post_account') @mock.patch('swiftclient.client.Connection.put_container') def test_account_headers(self, mock_put, mock_post): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test self._create_container(stack, definition_name='SwiftAccountMetadata') # Verify mock_put.assert_called_once_with(container_name, {}) expected = {'X-Account-Meta-Temp-Url-Key': 'secret'} mock_post.assert_called_once_with(expected) @mock.patch('swiftclient.client.Connection.put_container') def test_default_headers_not_none_empty_string(self, mock_put): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test container = self._create_container(stack) # Verify mock_put.assert_called_once_with(container_name, {}) self.assertEqual({}, container.metadata_get()) @mock.patch('swiftclient.client.Connection.delete_container') @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_exception(self, mock_put, mock_get, mock_delete): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') mock_delete.side_effect = sc.ClientException('test-delete-failure') mock_get.return_value = ({'name': container_name}, []) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) self.assertRaises(exception.ResourceFailure, runner) # Verify self.assertEqual((container.DELETE, container.FAILED), container.state) mock_put.assert_called_once_with(container_name, {}) mock_get.assert_called_once_with(container_name) mock_delete.assert_called_once_with(container_name) @mock.patch('swiftclient.client.Connection.delete_container') @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_not_found(self, mock_put, mock_get, mock_delete): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') mock_delete.side_effect = sc.ClientException('missing', http_status=404) mock_get.return_value = ({'name': container_name}, []) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) runner() # Verify self.assertEqual((container.DELETE, container.COMPLETE), container.state) mock_put.assert_called_once_with(container_name, {}) mock_get.assert_called_once_with(container_name) mock_delete.assert_called_once_with(container_name) @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_non_empty_not_allowed(self, mock_put, mock_get): # Setup stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') mock_get.return_value = ({'name': container_name}, [{'name': 'test_object'}]) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) ex = self.assertRaises(exception.ResourceFailure, runner) # Verify self.assertEqual((container.DELETE, container.FAILED), container.state) self.assertIn('ResourceActionNotSupported: resources.test_resource: ' 'Deleting non-empty container', six.text_type(ex)) mock_put.assert_called_once_with(container_name, {}) mock_get.assert_called_once_with(container_name) @mock.patch('swiftclient.client.Connection.delete_container') @mock.patch('swiftclient.client.Connection.delete_object') @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_non_empty_allowed(self, mock_put, mock_get, mock_delete_object, mock_delete_container): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['PurgeOnDelete'] = True stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') get_return_values = [ ({'name': container_name}, [{'name': 'test_object1'}, {'name': 'test_object2'}]), ({'name': container_name}, [{'name': 'test_object1'}]), ] mock_get.side_effect = get_return_values # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) runner() # Verify self.assertEqual((container.DELETE, container.COMPLETE), container.state) mock_put.assert_called_once_with(container_name, {}) mock_delete_container.assert_called_once_with(container_name) self.assertEqual(2, mock_get.call_count) self.assertEqual(2, mock_delete_object.call_count) @mock.patch('swiftclient.client.Connection.delete_container') @mock.patch('swiftclient.client.Connection.delete_object') @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_non_empty_allowed_not_found(self, mock_put, mock_get, mock_delete_object, mock_delete_container): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['PurgeOnDelete'] = True stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') mock_get.return_value = ({'name': container_name}, [{'name': 'test_object'}]) mock_delete_object.side_effect = sc.ClientException('object-is-gone', http_status=404) mock_delete_container.side_effect = sc.ClientException( 'container-is-gone', http_status=404) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) runner() # Verify self.assertEqual((container.DELETE, container.COMPLETE), container.state) mock_put.assert_called_once_with(container_name, {}) mock_get.assert_called_once_with(container_name) mock_delete_object.assert_called_once_with(container_name, 'test_object') mock_delete_container.assert_called_once_with(container_name) @mock.patch('swiftclient.client.Connection.delete_object') @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_non_empty_fails_delete_object(self, mock_put, mock_get, mock_delete_object): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['PurgeOnDelete'] = True stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') mock_get.return_value = ({'name': container_name}, [{'name': 'test_object'}]) mock_delete_object.side_effect = ( sc.ClientException('object-delete-failure')) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) self.assertRaises(exception.ResourceFailure, runner) # Verify self.assertEqual((container.DELETE, container.FAILED), container.state) mock_put.assert_called_once_with(container_name, {}) mock_get.assert_called_once_with(container_name) mock_delete_object.assert_called_once_with(container_name, 'test_object') @mock.patch('swiftclient.client.Connection.put_container') def test_delete_retain(self, mock_put): # Setup self.t['Resources']['SwiftContainer']['DeletionPolicy'] = 'Retain' stack = utils.parse_stack(self.t) container_name = utils.PhysName(stack.name, 'test_resource') # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.delete) runner() # Verify self.assertEqual((container.DELETE, container.COMPLETE), container.state) mock_put.assert_called_once_with(container_name, {}) @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_check(self, mock_put, mock_get): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['PurgeOnDelete'] = True stack = utils.parse_stack(self.t) # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.check) runner() self.assertEqual((container.CHECK, container.COMPLETE), container.state) @mock.patch('swiftclient.client.Connection.get_container') @mock.patch('swiftclient.client.Connection.put_container') def test_check_fail(self, mock_put, mock_get): # Setup res_prop = self.t['Resources']['SwiftContainer']['Properties'] res_prop['PurgeOnDelete'] = True stack = utils.parse_stack(self.t) mock_get.side_effect = Exception('boom') # Test container = self._create_container(stack) runner = scheduler.TaskRunner(container.check) ex = self.assertRaises(exception.ResourceFailure, runner) # Verify self.assertIn('boom', six.text_type(ex)) self.assertEqual((container.CHECK, container.FAILED), container.state) def test_refid(self): stack = utils.parse_stack(self.t) rsrc = stack['SwiftContainer'] rsrc.resource_id = 'xyz' self.assertEqual('xyz', rsrc.FnGetRefId()) def test_refid_convergence_cache_data(self): cache_data = {'SwiftContainer': node_data.NodeData.from_dict({ 'uuid': mock.ANY, 'id': mock.ANY, 'action': 'CREATE', 'status': 'COMPLETE', 'reference_id': 'xyz_convg' })} stack = utils.parse_stack(self.t, cache_data=cache_data) rsrc = stack.defn['SwiftContainer'] self.assertEqual('xyz_convg', rsrc.FnGetRefId()) @mock.patch('swiftclient.client.Connection.head_account') @mock.patch('swiftclient.client.Connection.head_container') @mock.patch('swiftclient.client.Connection.put_container') def test_parse_live_resource_data(self, mock_put, mock_container, mock_account): stack = utils.parse_stack(self.t) container = self._create_container( stack, definition_name="SwiftContainerWebsite") mock_container.return_value = { 'x-container-read': '.r:*', 'x-container-meta-web-index': 'index.html', 'x-container-meta-web-error': 'error.html', 'x-container-meta-login': 'login.html' } mock_account.return_value = {} live_state = container.parse_live_resource_data( container.properties, container.get_live_resource_data()) # live state properties values should be equal to current resource # properties values self.assertEqual(dict(container.properties.items()), live_state)