Merge "Make metadata handling consistent in Object Store"

This commit is contained in:
Jenkins 2016-02-26 14:23:48 +00:00 committed by Gerrit Code Review
commit 4fafbbe406
9 changed files with 555 additions and 105 deletions

View File

@ -18,6 +18,52 @@ from openstack import resource
class BaseResource(resource.Resource):
service = object_store_service.ObjectStoreService()
#: Metadata stored for this resource. *Type: dict*
metadata = dict()
_custom_metadata_prefix = None
_system_metadata = dict()
def _calculate_headers(self, metadata):
headers = dict()
for key in metadata:
if key in self._system_metadata:
header = self._system_metadata[key]
else:
header = self._custom_metadata_prefix + key
headers[header] = metadata[key]
return headers
def set_metadata(self, session, metadata):
url = self._get_url(self, self.id)
session.post(url, endpoint_filter=self.service,
headers=self._calculate_headers(metadata))
def delete_metadata(self, session, keys):
url = self._get_url(self, self.id)
headers = {key: '' for key in keys}
session.post(url, endpoint_filter=self.service,
headers=self._calculate_headers(headers))
def _set_metadata(self):
self.metadata = dict()
headers = self.get_headers()
for header in headers:
if header.startswith(self._custom_metadata_prefix):
key = header[len(self._custom_metadata_prefix):].lower()
self.metadata[key] = headers[header]
def get(self, session, include_headers=False, args=None):
super(BaseResource, self).get(session, include_headers, args)
self._set_metadata()
return self
def head(self, session):
super(BaseResource, self).head(session)
self._set_metadata()
return self
@classmethod
def update_by_id(cls, session, resource_id, attrs, path_args=None):
"""Update a Resource with the given attributes.

View File

@ -19,30 +19,36 @@ from openstack import proxy
class Proxy(proxy.BaseProxy):
def get_account_metadata(self):
"""Get metatdata for this account
"""Get metadata for this account.
:rtype:
:class:`~openstack.object_store.v1.account.Account`
"""
return self._head(_account.Account)
def set_account_metadata(self, account):
"""Set metatdata for this account.
def set_account_metadata(self, **metadata):
"""Set metadata for this account.
:param account: Account metadata specified on a
:class:`~openstack.object_store.v1.account.Account` object
to be sent to the server.
:type account:
:class:`~openstack.object_store.v1.account.Account`
:rtype: ``None``
:param kwargs metadata: Key/value pairs to be set as metadata
on the container. Custom metadata can be set.
Custom metadata are keys and values defined
by the user.
"""
account.update(self.session)
account = self._get_resource(_account.Account, None)
account.set_metadata(self.session, metadata)
def delete_account_metadata(self, keys):
"""Delete metadata for this account.
:param list keys: The keys of metadata to be deleted.
"""
account = self._get_resource(_account.Account, None)
account.delete_metadata(self.session, keys)
def containers(self, **query):
"""Obtain Container objects for this account.
:param kwargs \*\*query: Optional query parameters to be sent to limit
:param kwargs query: Optional query parameters to be sent to limit
the resources being returned.
:rtype: A generator of
@ -50,30 +56,6 @@ class Proxy(proxy.BaseProxy):
"""
return _container.Container.list(self.session, **query)
def get_container_metadata(self, container):
"""Get metatdata for a container
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:returns: One :class:`~openstack.object_store.v1.container.Container`
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
return self._head(_container.Container, container)
def set_container_metadata(self, container):
"""Set metatdata for a container.
:param container: A container object containing metadata to be set.
:type container:
:class:`~openstack.object_store.v1.container.Container`
:rtype: ``None``
"""
container.create(self.session)
def create_container(self, **attrs):
"""Create a new container from attributes
@ -103,6 +85,54 @@ class Proxy(proxy.BaseProxy):
self._delete(_container.Container, container,
ignore_missing=ignore_missing)
def get_container_metadata(self, container):
"""Get metadata for a container
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:returns: One :class:`~openstack.object_store.v1.container.Container`
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
return self._head(_container.Container, container)
def set_container_metadata(self, container, **metadata):
"""Set metadata for a container.
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:param kwargs metadata: Key/value pairs to be set as metadata
on the container. Both custom and system
metadata can be set. Custom metadata are keys
and values defined by the user. System
metadata are keys defined by the Object Store
and values defined by the user. The system
metadata keys are:
- `content_type`
- `detect_content_type`
- `versions_location`
- `read_ACL`
- `write_ACL`
- `sync_to`
- `sync_key`
"""
res = self._get_resource(_container.Container, container)
res.set_metadata(self.session, metadata)
def delete_container_metadata(self, container, keys):
"""Delete metadata for a container.
:param container: The value can be the ID of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:param list keys: The keys of metadata to be deleted.
"""
res = self._get_resource(_container.Container, container)
res.delete_metadata(self.session, keys)
def objects(self, container, **query):
"""Return a generator that yields the Container's objects.
@ -121,28 +151,24 @@ class Proxy(proxy.BaseProxy):
objs = _obj.Object.list(self.session,
path_args={"container": container.name},
**query)
# TODO(briancurtin): Objects have to know their container at this
# point, otherwise further operations like getting their metadata
# or downloading them is a hassle because the end-user would have
# to maintain both the container and the object separately.
for ob in objs:
ob.container = container.name
yield ob
for obj in objs:
obj.container = container.name
yield obj
def _get_container_name(self, object, container):
if isinstance(object, _obj.Object):
if object.container is not None:
return object.container
def _get_container_name(self, obj, container):
if isinstance(obj, _obj.Object):
if obj.container is not None:
return obj.container
if container is not None:
container = _container.Container.from_id(container)
return container.name
raise ValueError("container must be specified")
def get_object(self, object, container=None):
def get_object(self, obj, container=None):
"""Get the data associated with an object
:param object: The value can be the name of an object or a
:param obj: The value can be the name of an object or a
:class:`~openstack.object_store.v1.obj.Object` instance.
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
@ -154,15 +180,15 @@ class Proxy(proxy.BaseProxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
container_name = self._get_container_name(object, container)
container_name = self._get_container_name(obj, container)
return self._get(_obj.Object, object,
return self._get(_obj.Object, obj,
path_args={"container": container_name})
def download_object(self, object, container=None, path=None):
def download_object(self, obj, container=None, path=None):
"""Download the data contained inside an object to disk.
:param object: The value can be the name of an object or a
:param obj: The value can be the name of an object or a
:class:`~openstack.object_store.v1.obj.Object` instance.
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
@ -173,7 +199,7 @@ class Proxy(proxy.BaseProxy):
when no resource can be found.
"""
with open(path, "w") as out:
out.write(self.get_object(object, container))
out.write(self.get_object(obj, container))
def upload_object(self, **attrs):
"""Upload a new object from attributes
@ -199,10 +225,10 @@ class Proxy(proxy.BaseProxy):
"""Copy an object."""
raise NotImplementedError
def delete_object(self, object, ignore_missing=True, container=None):
def delete_object(self, obj, ignore_missing=True, container=None):
"""Delete an object
:param object: The value can be either the name of an object or a
:param obj: The value can be either the name of an object or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:param container: The value can be the ID of a container or a
@ -216,17 +242,16 @@ class Proxy(proxy.BaseProxy):
:returns: ``None``
"""
container_name = self._get_container_name(object, container)
container_name = self._get_container_name(obj, container)
self._delete(_obj.Object, object, ignore_missing=ignore_missing,
self._delete(_obj.Object, obj, ignore_missing=ignore_missing,
path_args={"container": container_name})
def get_object_metadata(self, object, container=None):
"""Get metatdata for an object
def get_object_metadata(self, obj, container=None):
"""Get metadata for an object.
:param object: The value is an
:class:`~openstack.object_store.v1.obj.Object`
instance.
:param obj: The value can be the name of an object or a
:class:`~openstack.object_store.v1.obj.Object` instance.
:param container: The value can be the ID of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
@ -235,17 +260,51 @@ class Proxy(proxy.BaseProxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
container_name = self._get_container_name(object, container)
container_name = self._get_container_name(obj, container)
return self._head(_obj.Object, object,
return self._head(_obj.Object, obj,
path_args={"container": container_name})
def set_object_metadata(self, object):
"""Set metatdata for an object.
def set_object_metadata(self, obj, container=None, **metadata):
"""Set metadata for an object.
:param object: The object to set metadata for.
:type object: :class:`~openstack.object_store.v1.obj.Object`
Note: This method will do an extra HEAD call.
:rtype: ``None``
:param obj: The value can be the name of an object or a
:class:`~openstack.object_store.v1.obj.Object` instance.
:param container: The value can be the name of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:param kwargs metadata: Key/value pairs to be set as metadata
on the container. Both custom and system
metadata can be set. Custom metadata are keys
and values defined by the user. System
metadata are keys defined by the Object Store
and values defined by the user. The system
metadata keys are:
- `content_type`
- `content_encoding`
- `content_disposition`
- `detect_content_type`
- `delete_after`
- `delete_at`
"""
object.create(self.session)
container_name = self._get_container_name(obj, container)
res = self._get_resource(_obj.Object, obj,
path_args={"container": container_name})
res.set_metadata(self.session, metadata)
def delete_object_metadata(self, obj, container=None, keys=None):
"""Delete metadata for an object.
:param obj: The value can be the name of an object or a
:class:`~openstack.object_store.v1.obj.Object` instance.
:param container: The value can be the ID of a container or a
:class:`~openstack.object_store.v1.container.Container`
instance.
:param list keys: The keys of metadata to be deleted.
"""
container_name = self._get_container_name(obj, container)
res = self._get_resource(_obj.Object, obj,
path_args={"container": container_name})
res.delete_metadata(self.session, keys)

View File

@ -17,6 +17,8 @@ from openstack import resource
class Account(_base.BaseResource):
_custom_metadata_prefix = "X-Account-Meta-"
base_path = "/"
allow_retrieve = True

View File

@ -17,6 +17,17 @@ from openstack import resource
class Container(_base.BaseResource):
_custom_metadata_prefix = "X-Container-Meta-"
_system_metadata = {
"content_type": "content-type",
"detect_content_type": "x-detect-content-type",
"versions_location": "x-versions-location",
"read_ACL": "x-container-read",
"write_ACL": "x-container-write",
"sync_to": "x-container-sync-to",
"sync_key": "x-container-sync-key"
}
base_path = "/"
id_attribute = "name"
@ -70,9 +81,7 @@ class Container(_base.BaseResource):
#: the name before you include it in the header. To disable
#: versioning, set the header to an empty string.
versions_location = resource.header("x-versions-location")
#: Set to any value to disable versioning.
remove_versions_location = resource.header("x-remove-versions-location")
#: Changes the MIME type for the object.
#: The MIME type of the list of names.
content_type = resource.header("content-type")
#: If set to true, Object Storage guesses the content type based
#: on the file extension and ignores the value sent in the

View File

@ -11,12 +11,25 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from openstack import format
from openstack.object_store import object_store_service
from openstack.object_store.v1 import _base
from openstack import resource
class Object(resource.Resource):
class Object(_base.BaseResource):
_custom_metadata_prefix = "X-Object-Meta-"
_system_metadata = {
"content_disposition": "content-disposition",
"content_encoding": "content-encoding",
"content_type": "content-type",
"detect_content_type": "x-detect-content-type",
"delete_after": "x-delete-after",
"delete_at": "x-delete-at"
}
base_path = "/%(container)s"
service = object_store_service.ObjectStoreService()
id_attribute = "name"
@ -87,9 +100,6 @@ class Object(resource.Resource):
content_type = resource.header("content_type", alias="content-type")
#: The type of ranges that the object accepts.
accept_ranges = resource.header("accept-ranges")
#: The date and time that the object was created or the last
#: time that the metadata was changed.
last_modified = resource.header("last_modified", alias="last-modified")
#: For objects smaller than 5 GB, this value is the MD5 checksum
#: of the object content. The value is not quoted.
#: For manifest objects, this value is the MD5 checksum of the
@ -113,6 +123,10 @@ class Object(resource.Resource):
#: which is the default.
#: If not set, this header is not returned by this operation.
content_disposition = resource.header("content-disposition")
#: Specifies the number of seconds after which the object is
#: removed. Internally, the Object Storage system stores this
#: value in the X-Delete-At metadata item.
delete_after = resource.header("x-delete-after", type=int)
#: If set, the time when the object will be deleted by the system
#: in the format of a UNIX Epoch timestamp.
#: If not set, this header is not returned by this operation.
@ -123,6 +137,9 @@ class Object(resource.Resource):
object_manifest = resource.header("x-object-manifest")
#: The timestamp of the transaction.
timestamp = resource.header("x-timestamp", type=format.UNIXEpoch)
#: The date and time that the object was created or the last
#: time that the metadata was changed.
last_modified = resource.header("last_modified", alias="last-modified")
# Headers for PUT and POST requests
#: Set to chunked to enable chunked transfer encoding. If used,
@ -140,21 +157,65 @@ class Object(resource.Resource):
#: Using PUT with X-Copy-From has the same effect as using the
#: COPY operation to copy an object.
copy_from = resource.header("x-copy-from")
#: Specifies the number of seconds after which the object is
#: removed. Internally, the Object Storage system stores this
#: value in the X-Delete-At metadata item.
delete_after = resource.header("x-delete-after", type=int)
def get(self, session, args=None):
# The Object Store treats the metadata for its resources inconsistently so
# Object.set_metadata must override the BaseResource.set_metadata to
# account for it.
def set_metadata(self, session, metadata):
# Filter out items with empty values so the create metadata behaviour
# is the same as account and container
filtered_metadata = \
{key: value for key, value in metadata.iteritems() if value}
# Get a copy of the original metadata so it doesn't get erased on POST
# and update it with the new metadata values.
obj = self.head(session)
metadata2 = copy.deepcopy(obj.metadata)
metadata2.update(filtered_metadata)
# Include any original system metadata so it doesn't get erased on POST
for key in self._system_metadata:
value = getattr(obj, key)
if value and key not in metadata2:
metadata2[key] = value
super(Object, self).set_metadata(session, metadata2)
# The Object Store treats the metadata for its resources inconsistently so
# Object.delete_metadata must override the BaseResource.delete_metadata to
# account for it.
def delete_metadata(self, session, keys):
# Get a copy of the original metadata so it doesn't get erased on POST
# and update it with the new metadata values.
obj = self.head(session)
metadata = copy.deepcopy(obj.metadata)
# Include any original system metadata so it doesn't get erased on POST
for key in self._system_metadata:
value = getattr(obj, key)
if value:
metadata[key] = value
# Remove the metadata
for key in keys:
if key == 'delete_after':
del(metadata['delete_at'])
else:
del(metadata[key])
url = self._get_url(self, self.id)
session.post(url, endpoint_filter=self.service,
headers=self._calculate_headers(metadata))
def get(self, session, include_headers=False, args=None):
url = self._get_url(self, self.id)
# TODO(thowe): Add filter header support bug #1488269
headers = {'Accept': 'bytes'}
resp = session.get(url, endpoint_filter=self.service, headers=headers)
resp = resp.content
self._set_metadata()
return resp
def create(self, session):
"""Create a remote resource from this instance."""
url = self._get_url(self, self.id)
headers = self.get_headers()

View File

@ -75,13 +75,18 @@ class TestServer(base.BaseFunctionalTest):
self.assertDictEqual(self.conn.compute.replace_server_metadata(sot),
{})
# Insert first and last name metadata
# Create first and last name metadata
meta = {"first": "Matthew", "last": "Dellavedova"}
self.assertDictEqual(
self.conn.compute.create_server_metadata(sot, **meta), meta)
# Create something that already exists
meta = {"last": "Inman"}
self.assertDictEqual(
self.conn.compute.create_server_metadata(sot, **meta), meta)
# Update only the first name
short = {"first": "Matt", "last": "Dellavedova"}
short = {"first": "Matt", "last": "Inman"}
self.assertDictEqual(
self.conn.compute.update_server_metadata(sot,
first=short["first"]),

View File

@ -0,0 +1,79 @@
# 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.
from openstack.tests.functional import base
class TestAccount(base.BaseFunctionalTest):
@classmethod
def tearDownClass(cls):
super(TestAccount, cls).tearDownClass()
account = cls.conn.object_store.get_account_metadata()
cls.conn.object_store.delete_account_metadata(account.metadata.keys())
def test_system_metadata(self):
account = self.conn.object_store.get_account_metadata()
self.assertGreaterEqual(account.account_bytes_used, 0)
self.assertGreaterEqual(account.account_container_count, 0)
self.assertGreaterEqual(account.account_object_count, 0)
def test_custom_metadata(self):
# get custom metadata
account = self.conn.object_store.get_account_metadata()
self.assertFalse(account.metadata)
# set no custom metadata
self.conn.object_store.set_account_metadata()
account = self.conn.object_store.get_account_metadata()
self.assertFalse(account.metadata)
# set empty custom metadata
self.conn.object_store.set_account_metadata(k0='')
account = self.conn.object_store.get_account_metadata()
self.assertFalse(account.metadata)
# set custom metadata
self.conn.object_store.set_account_metadata(k1='v1')
account = self.conn.object_store.get_account_metadata()
self.assertTrue(account.metadata)
self.assertEqual(1, len(account.metadata))
self.assertIn('k1', account.metadata)
self.assertEqual('v1', account.metadata['k1'])
# set more custom metadata
self.conn.object_store.set_account_metadata(k2='v2')
account = self.conn.object_store.get_account_metadata()
self.assertTrue(account.metadata)
self.assertEqual(2, len(account.metadata))
self.assertIn('k1', account.metadata)
self.assertEqual('v1', account.metadata['k1'])
self.assertIn('k2', account.metadata)
self.assertEqual('v2', account.metadata['k2'])
# update custom metadata
self.conn.object_store.set_account_metadata(k1='v1.1')
account = self.conn.object_store.get_account_metadata()
self.assertTrue(account.metadata)
self.assertEqual(2, len(account.metadata))
self.assertIn('k1', account.metadata)
self.assertEqual('v1.1', account.metadata['k1'])
self.assertIn('k2', account.metadata)
self.assertEqual('v2', account.metadata['k2'])
# unset custom metadata
self.conn.object_store.delete_account_metadata(['k1'])
account = self.conn.object_store.get_account_metadata()
self.assertTrue(account.metadata)
self.assertEqual(1, len(account.metadata))
self.assertIn('k2', account.metadata)
self.assertEqual('v2', account.metadata['k2'])

View File

@ -12,7 +12,7 @@
import uuid
from openstack.object_store.v1 import container
from openstack.object_store.v1 import container as _container
from openstack.tests.functional import base
@ -23,9 +23,9 @@ class TestContainer(base.BaseFunctionalTest):
@classmethod
def setUpClass(cls):
super(TestContainer, cls).setUpClass()
tainer = cls.conn.object_store.create_container(name=cls.NAME)
assert isinstance(tainer, container.Container)
cls.assertIs(cls.NAME, tainer.name)
container = cls.conn.object_store.create_container(name=cls.NAME)
assert isinstance(container, _container.Container)
cls.assertIs(cls.NAME, container.name)
@classmethod
def tearDownClass(cls):
@ -37,8 +37,98 @@ class TestContainer(base.BaseFunctionalTest):
names = [o.name for o in self.conn.object_store.containers()]
self.assertIn(self.NAME, names)
def test_get_metadata(self):
tainer = self.conn.object_store.get_container_metadata(self.NAME)
self.assertEqual(0, tainer.object_count)
self.assertEqual(0, tainer.bytes_used)
self.assertEqual(self.NAME, tainer.name)
def test_system_metadata(self):
# get system metadata
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertEqual(0, container.object_count)
self.assertEqual(0, container.bytes_used)
# set system metadata
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertIsNone(container.read_ACL)
self.assertIsNone(container.write_ACL)
self.conn.object_store.set_container_metadata(
container, read_ACL='.r:*', write_ACL='demo:demo')
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertEqual('.r:*', container.read_ACL)
self.assertEqual('demo:demo', container.write_ACL)
# update system metadata
self.conn.object_store.set_container_metadata(
container, read_ACL='.r:demo')
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertEqual('.r:demo', container.read_ACL)
self.assertEqual('demo:demo', container.write_ACL)
# set system metadata and custom metadata
self.conn.object_store.set_container_metadata(
container, k0='v0', sync_key='1234')
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertTrue(container.metadata)
self.assertIn('k0', container.metadata)
self.assertEqual('v0', container.metadata['k0'])
self.assertEqual('.r:demo', container.read_ACL)
self.assertEqual('demo:demo', container.write_ACL)
self.assertEqual('1234', container.sync_key)
# unset system metadata
self.conn.object_store.delete_container_metadata(container,
['sync_key'])
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertTrue(container.metadata)
self.assertIn('k0', container.metadata)
self.assertEqual('v0', container.metadata['k0'])
self.assertEqual('.r:demo', container.read_ACL)
self.assertEqual('demo:demo', container.write_ACL)
self.assertIsNone(container.sync_key)
def test_custom_metadata(self):
# get custom metadata
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertFalse(container.metadata)
# set no custom metadata
self.conn.object_store.set_container_metadata(container)
container = self.conn.object_store.get_container_metadata(container)
self.assertFalse(container.metadata)
# set empty custom metadata
self.conn.object_store.set_container_metadata(container, k0='')
container = self.conn.object_store.get_container_metadata(container)
self.assertFalse(container.metadata)
# set custom metadata
self.conn.object_store.set_container_metadata(container, k1='v1')
container = self.conn.object_store.get_container_metadata(container)
self.assertTrue(container.metadata)
self.assertEqual(1, len(container.metadata))
self.assertIn('k1', container.metadata)
self.assertEqual('v1', container.metadata['k1'])
# set more custom metadata by named container
self.conn.object_store.set_container_metadata(self.NAME, k2='v2')
container = self.conn.object_store.get_container_metadata(container)
self.assertTrue(container.metadata)
self.assertEqual(2, len(container.metadata))
self.assertIn('k1', container.metadata)
self.assertEqual('v1', container.metadata['k1'])
self.assertIn('k2', container.metadata)
self.assertEqual('v2', container.metadata['k2'])
# update metadata
self.conn.object_store.set_container_metadata(container, k1='v1.1')
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertTrue(container.metadata)
self.assertEqual(2, len(container.metadata))
self.assertIn('k1', container.metadata)
self.assertEqual('v1.1', container.metadata['k1'])
self.assertIn('k2', container.metadata)
self.assertEqual('v2', container.metadata['k2'])
# delete metadata
self.conn.object_store.delete_container_metadata(container, ['k1'])
container = self.conn.object_store.get_container_metadata(self.NAME)
self.assertTrue(container.metadata)
self.assertEqual(1, len(container.metadata))
self.assertIn('k2', container.metadata)
self.assertEqual('v2', container.metadata['k2'])

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
from openstack.tests.functional import base
@ -46,12 +47,110 @@ class TestObject(base.BaseFunctionalTest):
result = self.conn.object_store.get_object(self.sot)
self.assertEqual(self.DATA, result)
def test_get_metadata(self):
self.sot.data = None
self.sot.set_headers({'x-object-meta-test': 'orly'})
result = self.conn.object_store.set_object_metadata(self.sot)
result = self.conn.object_store.get_object_metadata(self.sot)
self.assertEqual(self.FILE, result.name)
headers = result.get_headers()
self.assertEqual(str(len(self.DATA)), headers['content-length'])
self.assertEqual('orly', headers['x-object-meta-test'])
def test_system_metadata(self):
# get system metadata
obj = self.conn.object_store.get_object_metadata(
self.FILE, container=self.FOLDER)
self.assertGreaterEqual(0, obj.bytes)
self.assertIsNotNone(obj.etag)
# set system metadata
obj = self.conn.object_store.get_object_metadata(
self.FILE, container=self.FOLDER)
self.assertIsNone(obj.content_disposition)
self.assertIsNone(obj.content_encoding)
self.conn.object_store.set_object_metadata(
obj, content_disposition='attachment', content_encoding='gzip')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertEqual('attachment', obj.content_disposition)
self.assertEqual('gzip', obj.content_encoding)
# update system metadata
self.conn.object_store.set_object_metadata(
obj, content_encoding='deflate')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertEqual('attachment', obj.content_disposition)
self.assertEqual('deflate', obj.content_encoding)
# set system metadata and custom metadata
self.conn.object_store.set_object_metadata(
obj, k0='v0', delete_after=100)
obj = self.conn.object_store.get_object_metadata(obj)
self.assertIn('k0', obj.metadata)
self.assertEqual('v0', obj.metadata['k0'])
self.assertEqual('attachment', obj.content_disposition)
self.assertEqual('deflate', obj.content_encoding)
self.assertIsInstance(obj.delete_at, datetime.datetime)
# unset system metadata
self.conn.object_store.delete_object_metadata(
obj, keys=['delete_after'])
obj = self.conn.object_store.get_object_metadata(obj)
self.assertIn('k0', obj.metadata)
self.assertEqual('v0', obj.metadata['k0'])
self.assertEqual('attachment', obj.content_disposition)
self.assertEqual('deflate', obj.content_encoding)
self.assertIsNone(obj.delete_at)
# unset more system metadata
self.conn.object_store.delete_object_metadata(
obj, keys=['content_disposition'])
obj = self.conn.object_store.get_object_metadata(obj)
self.assertIn('k0', obj.metadata)
self.assertEqual('v0', obj.metadata['k0'])
self.assertIsNone(obj.content_disposition)
self.assertEqual('deflate', obj.content_encoding)
self.assertIsNone(obj.delete_at)
def test_custom_metadata(self):
# get custom metadata
obj = self.conn.object_store.get_object_metadata(
self.FILE, container=self.FOLDER)
self.assertFalse(obj.metadata)
# set no custom metadata
self.conn.object_store.set_object_metadata(obj)
obj = self.conn.object_store.get_object_metadata(obj)
self.assertFalse(obj.metadata)
# set empty custom metadata
self.conn.object_store.set_object_metadata(obj, k0='')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertFalse(obj.metadata)
# set custom metadata
self.conn.object_store.set_object_metadata(obj, k1='v1')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertTrue(obj.metadata)
self.assertEqual(1, len(obj.metadata))
self.assertIn('k1', obj.metadata)
self.assertEqual('v1', obj.metadata['k1'])
# set more custom metadata by named object and container
self.conn.object_store.set_object_metadata(self.FILE, self.FOLDER,
k2='v2')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertTrue(obj.metadata)
self.assertEqual(2, len(obj.metadata))
self.assertIn('k1', obj.metadata)
self.assertEqual('v1', obj.metadata['k1'])
self.assertIn('k2', obj.metadata)
self.assertEqual('v2', obj.metadata['k2'])
# update custom metadata
self.conn.object_store.set_object_metadata(obj, k1='v1.1')
obj = self.conn.object_store.get_object_metadata(obj)
self.assertTrue(obj.metadata)
self.assertEqual(2, len(obj.metadata))
self.assertIn('k1', obj.metadata)
self.assertEqual('v1.1', obj.metadata['k1'])
self.assertIn('k2', obj.metadata)
self.assertEqual('v2', obj.metadata['k2'])
# unset custom metadata
self.conn.object_store.delete_object_metadata(obj, keys=['k1'])
obj = self.conn.object_store.get_object_metadata(obj)
self.assertTrue(obj.metadata)
self.assertEqual(1, len(obj.metadata))
self.assertIn('k2', obj.metadata)
self.assertEqual('v2', obj.metadata['k2'])