Merge "Initial add of Swift tests"
This commit is contained in:
commit
d246eb4350
@ -199,3 +199,18 @@ build_interval = 10
|
|||||||
# Number of seconds to time out on waiting for a volume
|
# Number of seconds to time out on waiting for a volume
|
||||||
# to be available or reach an expected status
|
# to be available or reach an expected status
|
||||||
build_timeout = 300
|
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
|
||||||
|
@ -169,3 +169,18 @@ build_interval = %VOLUME_BUILD_INTERVAL%
|
|||||||
# Number of seconds to time out on waiting for a volume
|
# Number of seconds to time out on waiting for a volume
|
||||||
# to be available or reach an expected status
|
# to be available or reach an expected status
|
||||||
build_timeout = %VOLUME_BUILD_TIMEOUT%
|
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%
|
||||||
|
@ -171,6 +171,9 @@ class RestClient(object):
|
|||||||
def put(self, url, body, headers):
|
def put(self, url, body, headers):
|
||||||
return self.request('PUT', url, headers, body)
|
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):
|
def _log(self, req_url, body, resp, resp_body):
|
||||||
self.log.error('Request URL: ' + req_url)
|
self.log.error('Request URL: ' + req_url)
|
||||||
self.log.error('Request Body: ' + str(body))
|
self.log.error('Request Body: ' + str(body))
|
||||||
|
@ -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 random
|
||||||
import re
|
import re
|
||||||
import urllib
|
import urllib
|
||||||
@ -44,3 +61,27 @@ def parse_image_id(image_ref):
|
|||||||
temp = image_ref.rsplit('/')
|
temp = image_ref.rsplit('/')
|
||||||
#Return the last item, which is the image id
|
#Return the last item, which is the image id
|
||||||
return temp[len(temp) - 1]
|
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
|
||||||
|
@ -378,6 +378,31 @@ class VolumeConfig(BaseConfig):
|
|||||||
return self.get("catalog_type", 'volume')
|
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...)
|
# TODO(jaypipes): Move this to a common utils (not data_utils...)
|
||||||
def singleton(cls):
|
def singleton(cls):
|
||||||
"""Simple wrapper for classes that should only have a single instance"""
|
"""Simple wrapper for classes that should only have a single instance"""
|
||||||
@ -426,6 +451,7 @@ class TempestConfig:
|
|||||||
self.images = ImagesConfig(self._conf)
|
self.images = ImagesConfig(self._conf)
|
||||||
self.network = NetworkConfig(self._conf)
|
self.network = NetworkConfig(self._conf)
|
||||||
self.volume = VolumeConfig(self._conf)
|
self.volume = VolumeConfig(self._conf)
|
||||||
|
self.object_storage = ObjectStorageConfig(self._conf)
|
||||||
|
|
||||||
def load_config(self, path):
|
def load_config(self, path):
|
||||||
"""Read configuration from given path and return a config object."""
|
"""Read configuration from given path and return a config object."""
|
||||||
|
@ -53,6 +53,9 @@ from tempest.services.nova.xml.volumes_extensions_client \
|
|||||||
import VolumesExtensionsClientXML
|
import VolumesExtensionsClientXML
|
||||||
from tempest.services.volume.json.volumes_client import VolumesClientJSON
|
from tempest.services.volume.json.volumes_client import VolumesClientJSON
|
||||||
from tempest.services.volume.xml.volumes_client import VolumesClientXML
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -179,6 +182,9 @@ class Manager(object):
|
|||||||
raise exceptions.InvalidConfiguration(msg)
|
raise exceptions.InvalidConfiguration(msg)
|
||||||
self.console_outputs_client = ConsoleOutputsClient(*client_args)
|
self.console_outputs_client = ConsoleOutputsClient(*client_args)
|
||||||
self.network_client = NetworkClient(*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):
|
class AltManager(Manager):
|
||||||
|
0
tempest/services/object_storage/__init__.py
Normal file
0
tempest/services/object_storage/__init__.py
Normal file
80
tempest/services/object_storage/account_client.py
Normal file
80
tempest/services/object_storage/account_client.py
Normal file
@ -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
|
152
tempest/services/object_storage/container_client.py
Normal file
152
tempest/services/object_storage/container_client.py
Normal file
@ -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
|
63
tempest/services/object_storage/object_client.py
Normal file
63
tempest/services/object_storage/object_client.py
Normal file
@ -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
|
0
tempest/tests/object_storage/__init__.py
Normal file
0
tempest/tests/object_storage/__init__.py
Normal file
41
tempest/tests/object_storage/base.py
Normal file
41
tempest/tests/object_storage/base.py
Normal file
@ -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)
|
76
tempest/tests/object_storage/test_account_services.py
Normal file
76
tempest/tests/object_storage/test_account_services.py
Normal file
@ -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!')
|
110
tempest/tests/object_storage/test_container_services.py
Normal file
110
tempest/tests/object_storage/test_container_services.py
Normal file
@ -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)
|
112
tempest/tests/object_storage/test_object_services.py
Normal file
112
tempest/tests/object_storage/test_object_services.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user