Merge "Tempest tests to cover live-block-migration"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 9c7868c..2987c56 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -206,3 +206,18 @@
# Number of seconds to time out on waiting for a volume
# to be available or reach an expected status
build_timeout = 300
+
+[object-storage]
+# This section contains configuration options used when executing tests
+# against the OpenStack Object Storage API.
+# This should be the username of a user WITHOUT administrative privileges
+username = admin
+# The above non-administrative user's password
+password = password
+# The above non-administrative user's tenant name
+tenant_name = admin
+
+# The type of endpoint for an Object Storage API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "object-store"
+catalog_type = object-store
diff --git a/etc/tempest.conf.tpl b/etc/tempest.conf.tpl
index 67fc025..5e2ee7f 100644
--- a/etc/tempest.conf.tpl
+++ b/etc/tempest.conf.tpl
@@ -176,3 +176,18 @@
# Number of seconds to time out on waiting for a volume
# to be available or reach an expected status
build_timeout = %VOLUME_BUILD_TIMEOUT%
+
+[object-storage]
+# This section contains configuration options used when executing tests
+# against the OpenStack Object Storage API.
+# This should be the username of a user WITHOUT administrative privileges
+username = %USERNAME%
+# The above non-administrative user's password
+password = %PASSWORD%
+# The above non-administrative user's tenant name
+tenant_name = %TENANT_NAME%
+
+# The type of endpoint for an Object Storage API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "object-store"
+catalog_type = %OBJECT_CATALOG_TYPE%
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 71f887f..fbe05e7 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -171,6 +171,9 @@
def put(self, url, body, headers):
return self.request('PUT', url, headers, body)
+ def head(self, url, headers=None):
+ return self.request('HEAD', url, headers=None)
+
def _log(self, req_url, body, resp, resp_body):
self.log.error('Request URL: ' + req_url)
self.log.error('Request Body: ' + str(body))
@@ -241,6 +244,11 @@
elif 'error' in resp_body: # Keystone errors
message = resp_body['error']['message']
raise exceptions.IdentityError(message)
+ elif 'message' in resp_body:
+ message = resp_body['message']
+ else:
+ message = resp_body
+
raise exceptions.ComputeFault(message)
if resp.status >= 400:
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index fc7c112..15afd0a 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -1,3 +1,20 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 random
import re
import urllib
@@ -44,3 +61,27 @@
temp = image_ref.rsplit('/')
#Return the last item, which is the image id
return temp[len(temp) - 1]
+
+
+def arbitrary_string(size=4, base_text=None):
+ """Return exactly size bytes worth of base_text as a string"""
+
+ if (base_text is None) or (base_text == ''):
+ base_text = 'test'
+
+ if size <= 0:
+ return ''
+
+ extra = size % len(base_text)
+ body = ''
+
+ if extra == 0:
+ body = base_text * size
+
+ if extra == size:
+ body = base_text[:size]
+
+ if extra > 0 and extra < size:
+ body = (size / len(base_text)) * base_text + base_text[:extra]
+
+ return body
diff --git a/tempest/config.py b/tempest/config.py
index 95dffa8..c46a007 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -389,6 +389,31 @@
return self.get("catalog_type", 'volume')
+class ObjectStorageConfig(BaseConfig):
+
+ SECTION_NAME = "object-storage"
+
+ @property
+ def username(self):
+ """Username to use for Object-Storage API requests."""
+ return self.get("username", "admin")
+
+ @property
+ def tenant_name(self):
+ """Tenant name to use for Object-Storage API requests."""
+ return self.get("tenant_name", "admin")
+
+ @property
+ def password(self):
+ """API key to use when authenticating."""
+ return self.get("password", "password")
+
+ @property
+ def catalog_type(self):
+ """Catalog type of the Object-Storage service."""
+ return self.get("catalog_type", 'object-store')
+
+
# TODO(jaypipes): Move this to a common utils (not data_utils...)
def singleton(cls):
"""Simple wrapper for classes that should only have a single instance"""
@@ -437,6 +462,7 @@
self.images = ImagesConfig(self._conf)
self.network = NetworkConfig(self._conf)
self.volume = VolumeConfig(self._conf)
+ self.object_storage = ObjectStorageConfig(self._conf)
def load_config(self, path):
"""Read configuration from given path and return a config object."""
diff --git a/tempest/manager.py b/tempest/manager.py
index dc4b289..ce7cf93 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -21,7 +21,10 @@
import glanceclient
import keystoneclient.v2_0.client
import novaclient.client
-import quantumclient.v2_0.client
+try:
+ import quantumclient.v2_0.client
+except ImportError:
+ pass
import tempest.config
from tempest import exceptions
@@ -127,7 +130,7 @@
client_args = (username, password, tenant_name, auth_url)
# Create our default Nova client to use in testing
- service_type = self.config.compute.catalog_type,
+ service_type = self.config.compute.catalog_type
return novaclient.client.Client(self.NOVACLIENT_VERSION,
*client_args,
service_type=service_type,
diff --git a/tempest/openstack.py b/tempest/openstack.py
index c8bd238..359e6c6 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -53,6 +53,9 @@
import VolumesExtensionsClientXML
from tempest.services.volume.json.volumes_client import VolumesClientJSON
from tempest.services.volume.xml.volumes_client import VolumesClientXML
+from tempest.services.object_storage.account_client import AccountClient
+from tempest.services.object_storage.container_client import ContainerClient
+from tempest.services.object_storage.object_client import ObjectClient
LOG = logging.getLogger(__name__)
@@ -179,6 +182,9 @@
raise exceptions.InvalidConfiguration(msg)
self.console_outputs_client = ConsoleOutputsClient(*client_args)
self.network_client = NetworkClient(*client_args)
+ self.account_client = AccountClient(*client_args)
+ self.container_client = ContainerClient(*client_args)
+ self.object_client = ObjectClient(*client_args)
class AltManager(Manager):
diff --git a/tempest/services/nova/json/servers_client.py b/tempest/services/nova/json/servers_client.py
index a5e06c9..a96dacb 100644
--- a/tempest/services/nova/json/servers_client.py
+++ b/tempest/services/nova/json/servers_client.py
@@ -164,7 +164,7 @@
server_status = body['status']
if server_status == 'ERROR' and not ignore_error:
- raise exceptions.BuildErrorException
+ raise exceptions.BuildErrorException(server_id=server_id)
if int(time.time()) - start_time >= self.build_timeout:
raise exceptions.TimeoutException
diff --git a/tempest/services/object_storage/__init__.py b/tempest/services/object_storage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/object_storage/__init__.py
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
new file mode 100644
index 0000000..ade94d4
--- /dev/null
+++ b/tempest/services/object_storage/account_client.py
@@ -0,0 +1,80 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 json
+
+from tempest.common.rest_client import RestClient
+
+
+class AccountClient(RestClient):
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AccountClient, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.object_storage.catalog_type
+ self.format = 'json'
+
+ def list_account_metadata(self):
+ """
+ HEAD on the storage URL
+ Returns all account metadata headers
+ """
+
+ headers = {"X-Storage-Token", self.token}
+ resp, body = self.head('', headers=headers)
+ return resp, body
+
+ def create_account_metadata(self, metadata,
+ metadata_prefix='X-Account-Meta-'):
+ """Creates an account metadata entry"""
+ headers = {}
+ for key in metadata:
+ headers[metadata_prefix + key] = metadata[key]
+
+ resp, body = self.post('', headers=headers, body=None)
+ return resp, body
+
+ def list_account_containers(self, params=None):
+ """
+ GET on the (base) storage URL
+ Given the X-Storage-URL and a valid X-Auth-Token, returns
+ a list of all containers for the account.
+
+ Optional Arguments:
+ limit=[integer value N]
+ Limits the number of results to at most N values
+ DEFAULT: 10,000
+
+ marker=[string value X]
+ Given string value X, return object names greater in value
+ than the specified marker.
+ DEFAULT: No Marker
+
+ format=[string value, either 'json' or 'xml']
+ Specify either json or xml to return the respective serialized
+ response.
+ DEFAULT: Python-List returned in response body
+ """
+
+ param_list = ['format=%s&' % self.format]
+ if params is not None:
+ for param, value in params.iteritems():
+ param_list.append("%s=%s&" % (param, value))
+ url = '?' + ''.join(param_list)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
new file mode 100644
index 0000000..56dffde
--- /dev/null
+++ b/tempest/services/object_storage/container_client.py
@@ -0,0 +1,152 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 json
+
+from tempest.common.rest_client import RestClient
+
+
+class ContainerClient(RestClient):
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ContainerClient, self).__init__(config, username, password,
+ auth_url, tenant_name)
+
+ #Overwrites json-specific header encoding in RestClient
+ self.headers = {}
+ self.service = self.config.object_storage.catalog_type
+ self.format = 'json'
+
+ def create_container(self, container_name, metadata=None,
+ metadata_prefix='X-Container-Meta-'):
+ """
+ Creates a container, with optional metadata passed in as a
+ dictonary
+ """
+ url = container_name
+ headers = {}
+
+ if metadata is not None:
+ for key in metadata:
+ headers[metadata_prefix + key] = metadata[key]
+
+ resp, body = self.put(url, body=None, headers=headers)
+ return resp, body
+
+ def delete_container(self, container_name):
+ """Deletes the container (if it's empty)"""
+ url = container_name
+ resp, body = self.delete(url)
+ return resp, body
+
+ def update_container_metadata(self, container_name, metadata,
+ metadata_prefix='X-Container-Meta-'):
+ """Updates arbitrary metadata on container"""
+ url = container_name
+ headers = {}
+
+ if metadata is not None:
+ for key in metadata:
+ headers[metadata_prefix + key] = metadata[key]
+
+ resp, body = self.post(url, body=None, headers=headers)
+
+ return resp. body
+
+ def list_all_container_objects(self, container, params=None):
+ """
+ Returns complete list of all objects in the container, even if
+ item count is beyond 10,000 item listing limit.
+ Does not require any paramaters aside from container name.
+ """
+ #TODO: Rewite using json format to avoid newlines at end of obj names
+ #Set limit to API limit - 1 (max returned items = 9999)
+ limit = 9999
+ marker = None
+ if params is not None:
+ if 'limit' in params:
+ limit = params['limit']
+
+ if 'marker' in params:
+ limit = params['marker']
+
+ resp, objlist = self.list_container_contents(container,
+ params={'limit': limit})
+ return objlist
+ """tmp = []
+ for obj in objlist:
+ tmp.append(obj['name'])
+ objlist = tmp
+
+ if len(objlist) >= limit:
+
+ #Increment marker
+ marker = objlist[len(objlist) - 1]
+
+ #Get the next chunk of the list
+ objlist.extend(_list_all_container_objects(container,
+ params={'marker': marker,
+ 'limit': limit}))
+ return objlist
+ else:
+ #Return final, complete list
+ return objlist"""
+
+ def list_container_contents(self, container, params=None):
+ """
+ List the objects in a container, given the container name
+
+ Returns the container object listing as a plain text list, or as
+ xml or json if that option is specified via the 'format' argument.
+
+ Optional Arguments:
+ limit = integer
+ For an integer value n, limits the number of results to at most
+ n values.
+
+ marker = 'string'
+ Given a string value x, return object names greater in value
+ than the specified marker.
+
+ prefix = 'string'
+ For a string value x, causes the results to be limited to names
+ beginning with the substring x.
+
+ format = 'json' or 'xml'
+ Specify either json or xml to return the respective serialized
+ response.
+ If json, returns a list of json objects
+ if xml, returns a string of xml
+
+ path = 'string'
+ For a string value x, return the object names nested in the
+ pseudo path (assuming preconditions are met - see below).
+
+ delimiter = 'character'
+ For a character c, return all the object names nested in the
+ container (without the need for the directory marker objects).
+ """
+
+ url = str(container)
+ param_list = ['format=%s&' % self.format]
+ if params is not None:
+ for param, value in params.iteritems():
+ param_list.append("%s=%s&" % (param, value))
+ url += '?' + ''.join(param_list)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
new file mode 100644
index 0000000..0fc7ad4
--- /dev/null
+++ b/tempest/services/object_storage/object_client.py
@@ -0,0 +1,63 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 re
+from tempest.common.rest_client import RestClient
+
+
+class ObjectClient(RestClient):
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ObjectClient, self).__init__(config, username, password,
+ auth_url, tenant_name)
+
+ self.service = self.config.object_storage.catalog_type
+
+ def create_object(self, container, object_name, data):
+ """Create storage object"""
+
+ url = "%s/%s" % (str(container), str(object_name))
+ resp, body = self.put(url, data, self.headers)
+ return resp, body
+
+ def update_object(self, container, object_name, data):
+ """Upload data to replace current storage object"""
+ return create_object(container, object_name, data)
+
+ def delete_object(self, container, object_name):
+ """Delete storage object"""
+ url = "%s/%s" % (str(container), str(object_name))
+ resp, body = self.delete(url)
+ return resp, body
+
+ def update_object_metadata(self, container, object_name, metadata,
+ metadata_prefix='X-Object-Meta-'):
+ """Add, remove, or change X-Object-Meta metadata for storage object"""
+
+ headers = {}
+ for key in metadata:
+ headers["%s%s" % (str(metadata_prefix), str(key))] = metadata[key]
+
+ url = "%s/%s" % (str(container), str(object_name))
+ resp, body = self.post(url, None, headers=headers)
+ return resp, body
+
+ def list_object_metadata(self, container, object_name):
+ """List all storage object X-Object-Meta- metadata"""
+
+ url = "%s/%s" % (str(container), str(object_name))
+ resp, body = self.head(url)
+ return resp, body
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index ac5f524..ebf3b54 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -121,7 +121,7 @@
msg = ('Unable to create isolated tenant %s because ' +
'it already exists. If this is related to a ' +
'previous test failure, try using ' +
- 'allow_tentant_reuse in tempest.conf') % tenant_name
+ 'allow_tenant_reuse in tempest.conf') % tenant_name
raise exceptions.Duplicate(msg)
try:
@@ -135,10 +135,10 @@
username)
LOG.info('Re-using existing user %s' % user)
else:
- msg = ('Unable to create isolated tenant %s because ' +
+ msg = ('Unable to create isolated user %s because ' +
'it already exists. If this is related to a ' +
'previous test failure, try using ' +
- 'allow_tentant_reuse in tempest.conf') % tenant_name
+ 'allow_tenant_reuse in tempest.conf') % tenant_name
raise exceptions.Duplicate(msg)
# Store the complete creds (including UUID ids...) for later
@@ -159,19 +159,14 @@
admin_client.delete_tenant(tenant['id'])
@classmethod
- def clear_remaining_servers(cls):
- # NOTE(danms): Only nuke all left-over servers if we're in our
- # own isolated tenant
- if not cls.isolated_creds:
- return
- resp, servers = cls.servers_client.list_servers()
- for server in servers['servers']:
+ def clear_servers(cls):
+ for server in cls.servers:
try:
cls.servers_client.delete_server(server['id'])
except Exception:
pass
- for server in servers['servers']:
+ for server in cls.servers:
try:
cls.servers_client.wait_for_server_termination(server['id'])
except Exception:
@@ -179,20 +174,21 @@
@classmethod
def tearDownClass(cls):
- cls.clear_remaining_servers()
+ cls.clear_servers()
cls.clear_isolated_creds()
- def create_server(self, image_id=None):
+ @classmethod
+ def create_server(cls, image_id=None):
"""Wrapper utility that returns a test server"""
- server_name = rand_name(self.__class__.__name__ + "-instance")
- flavor = self.flavor_ref
+ server_name = rand_name(cls.__name__ + "-instance")
+ flavor = cls.flavor_ref
if not image_id:
- image_id = self.image_ref
+ image_id = cls.image_ref
- resp, server = self.servers_client.create_server(
+ resp, server = cls.servers_client.create_server(
server_name, image_id, flavor)
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
- self.servers.append(server)
+ cls.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ cls.servers.append(server)
return server
def wait_for(self, condition):
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 3f2feec..12fa94b 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -17,6 +17,7 @@
from nose.plugins.attrib import attr
from nose.tools import raises
+from nose import SkipTest
import unittest2 as unittest
from tempest import openstack
@@ -32,7 +33,7 @@
def setUpClass(cls):
if not compute.MULTI_USER:
msg = "Need >1 user"
- raise nose.SkipTest(msg)
+ raise SkipTest(msg)
super(AuthorizationTest, cls).setUpClass()
diff --git a/tempest/tests/compute/test_list_servers_negative.py b/tempest/tests/compute/test_list_servers_negative.py
index 2279959..1be7480 100644
--- a/tempest/tests/compute/test_list_servers_negative.py
+++ b/tempest/tests/compute/test_list_servers_negative.py
@@ -19,6 +19,7 @@
import sys
import unittest2 as unittest
+import nose
from tempest import exceptions
from tempest import openstack
@@ -47,212 +48,97 @@
cls.alt_manager = openstack.AltManager()
cls.alt_client = cls.alt_manager.servers_client
- def tearDown(self):
- """Terminate instances created by tests"""
- try:
- for server in self.servers:
- resp, body = self.client.delete_server(server)
- if resp['status'] == '204':
- self.client.wait_for_server_termination(server)
- except exceptions.NotFound:
- pass
-
- def get_active_servers(self, server_count):
- """Returns active test instances to calling test methods"""
- resp, body = self.client.list_servers_with_detail()
+ # Under circumstances when there is not a tenant/user
+ # created for the test case, the test case checks
+ # to see if there are existing servers for the
+ # either the normal user/tenant or the alt user/tenant
+ # and if so, the whole test is skipped. We do this
+ # because we assume a baseline of no servers at the
+ # start of the test instead of destroying any existing
+ # servers.
+ resp, body = cls.client.list_servers()
servers = body['servers']
- active_servers = [
- server for server in servers if server['status'] == 'ACTIVE'
- ]
- num_of_active_servers = len(active_servers)
+ num_servers = len(servers)
+ if num_servers > 0:
+ username = cls.os.username
+ tenant_name = cls.os.tenant_name
+ msg = ("User/tenant %(username)s/%(tenant_name)s already have "
+ "existing server instances. Skipping test.") % locals()
+ raise nose.SkipTest(msg)
- # Check if we already have enough active servers
- if active_servers and num_of_active_servers >= server_count:
- return active_servers[0:server_count]
+ resp, body = cls.alt_client.list_servers()
+ servers = body['servers']
+ num_servers = len(servers)
+ if num_servers > 0:
+ username = cls.alt_manager.username
+ tenant_name = cls.alt_manager.tenant_name
+ msg = ("Alt User/tenant %(username)s/%(tenant_name)s already have "
+ "existing server instances. Skipping test.") % locals()
+ raise nose.SkipTest(msg)
- # Otherwise create the remaining shortfall of servers
- servers_needed = server_count - num_of_active_servers
+ # The following servers are created for use
+ # by the test methods in this class. These
+ # servers are cleaned up automatically in the
+ # tearDownClass method of the super-class.
+ cls.existing_fixtures = []
+ cls.deleted_fixtures = []
+ for x in xrange(2):
+ srv = cls.create_server()
+ cls.existing_fixtures.append(srv)
- for i in range(0, servers_needed):
- srv = self.create_server()
- active_servers.append(srv)
+ srv = cls.create_server()
+ cls.client.delete_server(srv['id'])
+ # We ignore errors on termination because the server may
+ # be put into ERROR status on a quick spawn, then delete,
+ # as the compute node expects the instance local status
+ # to be spawning, not deleted. See LP Bug#1061167
+ cls.client.wait_for_server_termination(srv['id'],
+ ignore_error=True)
+ cls.deleted_fixtures.append(srv)
- return active_servers
-
- def test_list_servers_when_no_servers_running(self):
- """Return an empty list when there are no active servers"""
- # Delete Active servers
- try:
- resp, body = self.client.list_servers()
- for server in body['servers']:
- resp, body = self.client.delete_server(server['id'])
- self.client.wait_for_server_termination(server['id'])
- except exceptions.NotFound:
- pass
- # Verify empty list
+ def test_list_servers_with_a_deleted_server(self):
+ """Verify deleted servers do not show by default in list servers"""
+ # List servers and verify server not returned
resp, body = self.client.list_servers()
servers = body['servers']
+ deleted_ids = [s['id'] for s in self.deleted_fixtures]
+ actual = [srv for srv in servers
+ if srv['id'] in deleted_ids]
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], actual)
+
+ def test_list_servers_by_non_existing_image(self):
+ """Listing servers for a non existing image returns empty list"""
+ non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde'
+ resp, body = self.client.list_servers(dict(image=non_existing_image))
+ servers = body['servers']
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- def test_list_servers_with_a_deleted_server(self):
- """Create and delete a server and verify empty list"""
- server = self.get_active_servers(1)[0]
-
- # Delete the server
- self.client.delete_server(server['id'])
- self.client.wait_for_server_termination(server['id'])
-
- # List servers and verify server not returned
- resp, body = self.client.list_servers()
- servers = body['servers']
- actual = [srv for srv in servers if srv['id'] == server['id']]
- self.assertEqual('200', resp['status'])
- self.assertEqual([], actual)
-
- def test_list_servers_by_existing_image(self):
- """Server list is returned for a specific image and snapshot images"""
- try:
- base_server = self.get_active_servers(1)[0]
-
- # Create a snapshot of the server
- snapshot_name = "%s_snapshot" % base_server['id']
- resp, body = self.client.create_image(base_server['id'],
- snapshot_name)
- snapshot_url = resp['location']
- match = re.search('/images/(?P<image_id>.+)', snapshot_url)
- self.assertIsNotNone(match)
- snapshot_id = match.groupdict()['image_id']
-
- self.images_client.wait_for_image_status(snapshot_id, 'ACTIVE')
-
- # Create a server from the snapshot
- snap_server = self.create_server(image_id=snapshot_id)
- self.servers.append(snap_server['id'])
-
- # List base servers by image
- resp, body = self.client.list_servers({'image': self.image_ref})
- servers = body['servers']
- self.assertEqual('200', resp['status'])
- self.assertIn(base_server['id'], [server['id'] for server in
- servers])
- self.assertTrue(len(body['servers']) > 0)
-
- # List snapshotted server by image
- resp, body = self.client.list_servers({'image': snapshot_id})
- servers = body['servers']
- self.assertEqual('200', resp['status'])
- self.assertIn(snap_server['id'],
- [server['id'] for server in servers])
- self.assertEqual(1, len(body['servers']))
-
- finally:
- self.images_client.delete_image(snapshot_id)
-
- @unittest.skip("Until Bug 1002911 is fixed")
- def test_list_servers_by_non_existing_image(self):
- """Listing servers for a non existing image raises error"""
- self.assertRaises(exceptions.NotFound, self.client.list_servers,
- {'image': '1234abcd-zzz0-aaa9-ppp3-0987654abcde'})
-
- @unittest.skip("Until Bug 1002911 is fixed")
- def test_list_servers_by_image_pass_overlimit_image(self):
- """Return an error while listing server with too large image id"""
- self.assertRaises(exceptions.OverLimit, self.client.list_servers,
- {'image': sys.maxint + 1})
-
- @unittest.skip("Until Bug 1002911 is fixed")
- def test_list_servers_by_image_pass_negative_id(self):
- """Return an error while listing server with negative image id"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'image': -1})
-
- def test_list_servers_by_existing_flavor(self):
- """List servers by flavor"""
- self.get_active_servers(1)
-
- resp, body = self.client.list_servers({'flavor': self.flavor_ref})
- self.assertEqual('200', resp['status'])
- self.assertTrue(len(body['servers']) > 0)
-
- @unittest.skip("Until Bug 1002918 is fixed")
def test_list_servers_by_non_existing_flavor(self):
- """Return an error while listing server by non existing flavor"""
- self.assertRaises(exceptions.NotFound, self.client.list_servers,
- {'flavor': 1234})
-
- @unittest.skip("Until Bug 1002918 is fixed")
- def test_list_servers_by_flavor_pass_overlimit_flavor(self):
- """Return an error while listing server with too large flavor value"""
- self.assertRaises(exceptions.OverLimit, self.client.list_servers,
- {'flavor': sys.maxint + 1})
-
- @unittest.skip("Until Bug 1002918 is fixed")
- def test_list_servers_by_flavor_pass_negative_value(self):
- """Return an error while listing server with negative flavor value"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'flavor': -1})
-
- def test_list_servers_by_server_name(self):
- """Returns a list of servers containing an existing server name"""
- server_name = rand_name('test-vm-')
- resp, server = self.client.create_server(server_name, self.image_ref,
- self.flavor_ref)
- self.servers.append(server['id'])
-
- resp, body = self.client.list_servers({'name': server_name})
+ """Listing servers by non existing flavor returns empty list"""
+ non_existing_flavor = 1234
+ resp, body = self.client.list_servers(dict(flavor=non_existing_flavor))
+ servers = body['servers']
self.assertEqual('200', resp['status'])
- self.assertEqual(server_name, body['servers'][0]['name'])
+ self.assertEqual([], servers)
- @unittest.skip("Until Bug 1002892 is fixed")
def test_list_servers_by_non_existing_server_name(self):
- """Return an error while listing for a non existent server"""
- resp, body = self.client.list_servers({'name': 'junk_serv_100'})
- self.assertRaises(exceptions.NotFound, self.client.list_servers,
- {'name': 'junk_serv_100'})
-
- @unittest.skip("Until Bug 1002892 is fixed")
- def test_list_servers_by_server_name_empty(self):
- """Return an error when an empty server name is passed"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'name': ''})
-
- @unittest.skip("Until Bug 1002892 is fixed")
- def test_list_servers_by_server_name_too_large(self):
- """Return an error for a very large value for server name listing"""
- self.assertRaises(exceptions.OverLimit, self.client.list_servers,
- {'name': 'a' * 65})
-
- @unittest.skip("Until Bug 1002892 is fixed")
- def test_list_servers_by_name_pass_numeric_name(self):
- """Return an error for a numeric server name listing"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'name': 99})
-
- def test_list_servers_by_active_status(self):
- """Return a listing of servers by active status"""
- self.create_server()
- resp, body = self.client.list_servers({'status': 'ACTIVE'})
+ """Listing servers for a non existent server name returns empty list"""
+ non_existing_name = 'junk_server_1234'
+ resp, body = self.client.list_servers(dict(name=non_existing_name))
+ servers = body['servers']
self.assertEqual('200', resp['status'])
- self.assertTrue(len(body['servers']) > 0)
+ self.assertEqual([], servers)
- def test_list_servers_by_building_status(self):
- """Return a listing of servers in build state"""
- server_name = rand_name('test-vm-')
- resp, server = self.client.create_server(server_name, self.image_ref,
- self.flavor_ref)
- self.servers.append(server['id'])
- resp, body = self.client.list_servers({'status': 'BUILD'})
+ @unittest.skip("Skip until bug 1061712 is resolved")
+ def test_list_servers_status_non_existing(self):
+ """Return an empty list when invalid status is specified"""
+ non_existing_status = 'BALONEY'
+ resp, body = self.client.list_servers(dict(status=non_existing_status))
+ servers = body['servers']
self.assertEqual('200', resp['status'])
- self.assertEqual(1, len(body['servers']))
- self.assertEqual(server['id'], body['servers'][0]['id'])
-
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
-
- def test_list_servers_status_is_invalid(self):
- """Return an error when invalid status is specified"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'status': 'DEAD'})
+ self.assertEqual([], servers)
def test_list_servers_pass_numeric_status(self):
"""Return an error when a numeric value for status is specified"""
@@ -261,17 +147,15 @@
def test_list_servers_by_limits(self):
"""List servers by specifying limits"""
- self.get_active_servers(2)
resp, body = self.client.list_servers({'limit': 1})
self.assertEqual('200', resp['status'])
self.assertEqual(1, len(body['servers']))
def test_list_servers_by_limits_greater_than_actual_count(self):
"""List servers by specifying a greater value for limit"""
- self.get_active_servers(2)
resp, body = self.client.list_servers({'limit': 100})
self.assertEqual('200', resp['status'])
- self.assertTrue(len(body['servers']) >= 2)
+ self.assertEqual(len(self.existing_fixtures), len(body['servers']))
def test_list_servers_by_limits_pass_string(self):
"""Return an error if a string value is passed for limit"""
@@ -283,120 +167,34 @@
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'limit': -1})
- @unittest.skip("Until Bug 1002924 is fixed")
- def test_list_servers_by_limits_pass_overlimit_value(self):
- """Return an error if too large value for limit is passed"""
- self.assertRaises(exceptions.OverLimit, self.client.list_servers,
- {'limit': sys.maxint + 1})
-
def test_list_servers_by_changes_since(self):
"""Servers are listed by specifying changes-since date"""
- self.get_active_servers(2)
resp, body = self.client.list_servers(
{'changes-since': '2011-01-01T12:34:00Z'})
self.assertEqual('200', resp['status'])
- self.assertTrue(len(body['servers']) >= 2)
+ # changes-since returns all instances, including deleted.
+ num_expected = (len(self.existing_fixtures) +
+ len(self.deleted_fixtures))
+ self.assertEqual(num_expected, len(body['servers']))
def test_list_servers_by_changes_since_invalid_date(self):
"""Return an error when invalid date format is passed"""
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'changes-since': '2011/01/01'})
- @unittest.skip("Until Bug 1002926 is fixed")
def test_list_servers_by_changes_since_future_date(self):
- """Return an error when a date in the future is passed"""
- self.assertRaises(exceptions.BadRequest, self.client.list_servers,
- {'changes-since': '2051-01-01T12:34:00Z'})
-
- @unittest.skip("Until Bug 1002935 is fixed")
- def test_list_servers_list_another_tenant_servers(self):
- """Return an error when a user lists servers in another tenant"""
- # Create a server by a user in it's tenant
- server_name = rand_name('test-vm-')
- resp, server = self.client.create_server(server_name, self.image_ref,
- self.flavor_ref)
- self.servers.append(server['id'])
-
- # List the servers by alternate user in the base user's tenant
- self.assertRaises(exceptions.NotFound, self.alt_client.list_servers,
- {'name': server_name})
-
- def test_list_servers_detail_when_no_servers_running(self):
- """Return an empty list for servers detail when no active servers"""
- # Delete active servers
- try:
- resp, body = self.client.list_servers()
- for server in body['servers']:
- resp, body = self.client.delete_server(server['id'])
- self.client.wait_for_server_termination(server['id'])
- except exceptions.NotFound:
- pass
- # Verify empty list
- resp, body = self.client.list_servers_with_detail()
+ """Return an empty list when a date in the future is passed"""
+ resp, body = self.client.list_servers(
+ {'changes-since': '2051-01-01T12:34:00Z'})
self.assertEqual('200', resp['status'])
- servers = body['servers']
- actual = [srv for srv in servers if srv['status'] == 'ACTIVE']
- self.assertEqual([], actual)
-
- def test_list_servers_detail_server_is_building(self):
- """Server in Build state is listed"""
- server_name = rand_name('test-vm-')
- resp, server = self.client.create_server(server_name, self.image_ref,
- self.flavor_ref)
-
- self.servers.append(server['id'])
- resp, body = self.client.list_servers_with_detail()
- self.assertEqual('200', resp['status'])
- self.assertEqual('BUILD', body['servers'][0]['status'])
+ self.assertEqual(0, len(body['servers']))
def test_list_servers_detail_server_is_deleted(self):
"""Server details are not listed for a deleted server"""
- server = self.get_active_servers(1)[0]
-
- self.client.delete_server(server['id'])
- self.client.wait_for_server_termination(server['id'])
+ deleted_ids = [s['id'] for s in self.deleted_fixtures]
resp, body = self.client.list_servers_with_detail()
servers = body['servers']
- actual = [srv for srv in servers if srv['id'] == server['id']]
+ actual = [srv for srv in servers
+ if srv['id'] in deleted_ids]
self.assertEqual('200', resp['status'])
self.assertEqual([], actual)
-
- def test_get_server_details_non_existent_id(self):
- """Return an error during get server details using non-existent id"""
- self.assertRaises(exceptions.NotFound, self.client.get_server,
- 'junk-123ab-45cd')
-
- def test_get_server_details_another_tenant_server(self):
- """Return an error when querying details of server in another tenant"""
- server_name = rand_name('test-vm-')
- resp, server = self.client.create_server(server_name, self.image_ref,
- self.flavor_ref)
- self.servers.append(server['id'])
- self.assertRaises(exceptions.NotFound, self.alt_client.get_server,
- server['id'])
-
- def test_get_server_details_pass_string_uuid(self):
- """Return an error when a string value is passed for uuid"""
- try:
- self.assertRaises(exceptions.NotFound, self.client.get_server,
- 'junk-server-uuid')
- except Exception as e:
- self.fail("Incorrect Exception raised: %s" % e)
-
- @unittest.skip("Until Bug 1002901 is fixed")
- def test_get_server_details_pass_negative_uuid(self):
- """Return an error when a negative value is passed for uuid"""
- try:
- self.assertRaises(exceptions.BadRequest, self.client.get_server,
- -1)
- except Exception as e:
- self.fail("Incorrect Exception raised: %s" % e)
-
- @unittest.skip("Until Bug 1002901 is fixed")
- def test_get_server_details_pass_overlimit_length_uuid(self):
- """Return an error when a very large value is passed for uuid"""
- try:
- self.assertRaises(exceptions.OverLimit, self.client.get_server,
- 'a' * 37)
- except Exception as e:
- self.fail("Incorrect Exception raised: %s" % e)
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index b8b99f7..da697ab 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -15,8 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
-import unittest2 as unittest
+import nose
from tempest import exceptions
from tempest.common.utils.data_utils import rand_name
@@ -77,3 +76,4 @@
@classmethod
def setUpClass(cls):
super(ServicesTestXML, cls).setUpClass()
+ raise nose.SkipTest("Skipping until Bug #1061738 resolved")
diff --git a/tempest/tests/object_storage/__init__.py b/tempest/tests/object_storage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/object_storage/__init__.py
diff --git a/tempest/tests/object_storage/base.py b/tempest/tests/object_storage/base.py
new file mode 100644
index 0000000..8edb3d2
--- /dev/null
+++ b/tempest/tests/object_storage/base.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 nose
+import unittest2 as unittest
+
+import tempest.config
+from tempest import exceptions
+from tempest import openstack
+
+
+class BaseObjectTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.os = openstack.Manager()
+ cls.object_client = cls.os.object_client
+ cls.container_client = cls.os.container_client
+ cls.account_client = cls.os.account_client
+ cls.config = cls.os.config
+
+ try:
+ cls.account_client.list_account_containers()
+ except exceptions.EndpointNotFound:
+ enabled = False
+ skip_msg = "No OpenStack Object Storage API endpoint"
+ raise nose.SkipTest(skip_msg)
diff --git a/tempest/tests/object_storage/test_account_services.py b/tempest/tests/object_storage/test_account_services.py
new file mode 100644
index 0000000..6d34bb8
--- /dev/null
+++ b/tempest/tests/object_storage/test_account_services.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 unittest2 as unittest
+import tempest.config
+import re
+
+from nose.plugins.attrib import attr
+from tempest import exceptions
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.object_storage import base
+
+
+class AccountTest(base.BaseObjectTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(AccountTest, cls).setUpClass()
+
+ #Create a container
+ cls.container_name = rand_name(name='TestContainer')
+ cls.container_client.create_container(cls.container_name)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.container_client.delete_container(cls.container_name)
+
+ @attr(type='smoke')
+ def test_list_containers(self):
+ """List of all containers should not be empty"""
+
+ params = {'format': 'json'}
+ resp, container_list = \
+ self.account_client.list_account_containers(params=params)
+
+ self.assertIsNotNone(container_list)
+ container_names = [c['name'] for c in container_list]
+ self.assertTrue(self.container_name in container_names)
+
+ @attr(type='smoke')
+ def test_list_account_metadata(self):
+ """List all account metadata"""
+
+ resp, metadata = self.account_client.list_account_metadata()
+ self.assertEqual(resp['status'], '204')
+ self.assertIn('x-account-object-count', resp)
+ self.assertIn('x-account-container-count', resp)
+ self.assertIn('x-account-bytes-used', resp)
+
+ @attr(type='smoke')
+ def test_create_account_metadata(self):
+ """Add metadata to account"""
+
+ metadata = {'test-account-meta': 'Meta!'}
+ resp, _ = \
+ self.account_client.create_account_metadata(metadata=metadata)
+ self.assertEqual(resp['status'], '204')
+
+ resp, metadata = self.account_client.list_account_metadata()
+ self.assertIn('x-account-meta-test-account-meta', resp)
+ self.assertEqual(resp['x-account-meta-test-account-meta'], 'Meta!')
diff --git a/tempest/tests/object_storage/test_container_services.py b/tempest/tests/object_storage/test_container_services.py
new file mode 100644
index 0000000..639698b
--- /dev/null
+++ b/tempest/tests/object_storage/test_container_services.py
@@ -0,0 +1,110 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 re
+import unittest2 as unittest
+import tempest.config
+
+from nose.plugins.attrib import attr
+from tempest import exceptions
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name, arbitrary_string
+from tempest.tests.object_storage import base
+
+
+class ContainerTest(base.BaseObjectTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ContainerTest, cls).setUpClass()
+ cls.containers = []
+
+ @classmethod
+ def tearDownClass(cls):
+ for container in cls.containers:
+ #Get list of all object in the container
+ objlist = \
+ cls.container_client.list_all_container_objects(container)
+
+ #Attempt to delete every object in the container
+ for obj in objlist:
+ resp, _ = \
+ cls.object_client.delete_object(container, obj['name'])
+
+ #Attempt to delete the container
+ resp, _ = cls.container_client.delete_container(container)
+
+ @attr(type='smoke')
+ def test_create_container(self):
+ """Create a container, test responses"""
+
+ #Create a container
+ container_name = rand_name(name='TestContainer')
+ resp, body = self.container_client.create_container(container_name)
+ self.containers.append(container_name)
+
+ self.assertTrue(resp['status'] in ('202', '201'))
+
+ @attr(type='smoke')
+ def test_delete_container(self):
+ """Create and Delete a container, test responses"""
+
+ #Create a container
+ container_name = rand_name(name='TestContainer')
+ resp, _ = self.container_client.create_container(container_name)
+ self.containers.append(container_name)
+
+ #Delete Container
+ resp, _ = self.container_client.delete_container(container_name)
+ self.assertEqual(resp['status'], '204')
+ self.containers.remove(container_name)
+
+ @attr(type='smoke')
+ def test_list_container_contents_json(self):
+ """Add metadata to object"""
+
+ #Create a container
+ container_name = rand_name(name='TestContainer')
+ resp, _ = self.container_client.create_container(container_name)
+ self.containers.append(container_name)
+
+ #Create Object
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ resp, _ = self.object_client.create_object(container_name,
+ object_name, data)
+
+ #Set Object Metadata
+ meta_key = rand_name(name='Meta-Test-')
+ meta_value = rand_name(name='MetaValue-')
+ orig_metadata = {meta_key: meta_value}
+
+ resp, _ = self.object_client.update_object_metadata(container_name,
+ object_name,
+ orig_metadata)
+
+ #Get Container contents list json format
+ params = {'format': 'json'}
+ resp, object_list = \
+ self.container_client.\
+ list_container_contents(container_name, params=params)
+
+ self.assertEqual(resp['status'], '200')
+ self.assertIsNotNone(object_list)
+
+ object_names = [obj['name'] for obj in object_list]
+ self.assertIn(object_name, object_names)
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
new file mode 100644
index 0000000..ab92d26
--- /dev/null
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -0,0 +1,112 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# 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 re
+import unittest2 as unittest
+import tempest.config
+
+from nose.plugins.attrib import attr
+from tempest import exceptions
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name, arbitrary_string
+from tempest.tests.object_storage import base
+
+
+class ObjectTest(base.BaseObjectTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ObjectTest, cls).setUpClass()
+
+ #Create a container
+ cls.container_name = rand_name(name='TestContainer')
+ cls.container_client.create_container(cls.container_name)
+
+ @classmethod
+ def tearDownClass(cls):
+ #Get list of all object in the container
+ objlist = \
+ cls.container_client.list_all_container_objects(cls.container_name)
+
+ #Attempt to delete every object in the container
+ for obj in objlist:
+ resp, _ = cls.object_client.delete_object(cls.container_name,
+ obj['name'])
+
+ #Attempt to delete the container
+ resp, _ = cls.container_client.delete_container(cls.container_name)
+
+ @attr(type='smoke')
+ def test_create_object(self):
+ """Create storage object, test response"""
+
+ #Create Object
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+
+ #Create another Object
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+ self.assertEqual(resp['status'], '201')
+
+ @attr(type='smoke')
+ def test_delete_object(self):
+ """Create and delete a storage object, test responses"""
+
+ #Create Object
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+
+ resp, _ = self.object_client.delete_object(self.container_name,
+ object_name)
+ self.assertEqual(resp['status'], '204')
+
+ @attr(type='smoke')
+ def test_object_metadata(self):
+ """Add metadata to storage object, test if metadata is retrievable"""
+
+ #Create Object
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+
+ #Set Object Metadata
+ meta_key = rand_name(name='test-')
+ meta_value = rand_name(name='MetaValue-')
+ orig_metadata = {meta_key: meta_value}
+
+ resp, _ = \
+ self.object_client.update_object_metadata(self.container_name,
+ object_name,
+ orig_metadata)
+ self.assertEqual(resp['status'], '202')
+
+ #Get Object Metadata
+ resp, resp_metadata = \
+ self.object_client.list_object_metadata(self.container_name,
+ object_name)
+ self.assertEqual(resp['status'], '200')
+ actual_meta_key = 'x-object-meta-' + meta_key
+ self.assertTrue(actual_meta_key in resp)
+ self.assertEqual(resp[actual_meta_key], meta_value)