heat/heat/tests/openstack/swift/test_container.py

493 lines
19 KiB
Python

#
# 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)
# 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['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)