Merge "Fixes around variable usage"
diff --git a/tempest/clients.py b/tempest/clients.py
index 28abb79..150cf67 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -52,7 +52,7 @@
from tempest.services.identity.json.identity_client import TokenClientJSON
from tempest.services.identity.xml.identity_client import IdentityClientXML
from tempest.services.identity.xml.identity_client import TokenClientXML
-from tempest.services.image import service as image_service
+from tempest.services.image.json.image_client import ImageClientJSON
from tempest.services.network.json.network_client import NetworkClient
from tempest.services.object_storage.account_client import AccountClient
from tempest.services.object_storage.container_client import ContainerClient
@@ -202,6 +202,7 @@
raise exceptions.InvalidConfiguration(msg)
self.network_client = NetworkClient(*client_args)
self.account_client = AccountClient(*client_args)
+ self.image_client = ImageClientJSON(*client_args)
self.container_client = ContainerClient(*client_args)
self.object_client = ObjectClient(*client_args)
self.ec2api_client = APIClientEC2(*client_args)
@@ -254,16 +255,3 @@
conf.compute_admin.password,
conf.compute_admin.tenant_name,
interface=interface)
-
-
-class ServiceManager(object):
-
- """
- Top-level object housing clients for OpenStack APIs
- """
-
- def __init__(self):
- self.config = config.TempestConfig()
- self.services = {}
- self.services['image'] = image_service.Service(self.config)
- self.images = self.services['image']
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
new file mode 100644
index 0000000..faac1a0
--- /dev/null
+++ b/tempest/common/glance_http.py
@@ -0,0 +1,375 @@
+# 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.
+
+# Originally copied from python-glanceclient
+
+import copy
+import httplib
+import logging
+import posixpath
+import socket
+import StringIO
+import struct
+import urlparse
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+# Python 2.5 compat fix
+if not hasattr(urlparse, 'parse_qsl'):
+ import cgi
+ urlparse.parse_qsl = cgi.parse_qsl
+
+import OpenSSL
+
+from tempest import exceptions as exc
+
+
+LOG = logging.getLogger(__name__)
+USER_AGENT = 'tempest'
+CHUNKSIZE = 1024 * 64 # 64kB
+
+
+class HTTPClient(object):
+
+ def __init__(self, endpoint, **kwargs):
+ self.endpoint = endpoint
+ endpoint_parts = self.parse_endpoint(self.endpoint)
+ self.endpoint_scheme = endpoint_parts.scheme
+ self.endpoint_hostname = endpoint_parts.hostname
+ self.endpoint_port = endpoint_parts.port
+ self.endpoint_path = endpoint_parts.path
+
+ self.connection_class = self.get_connection_class(self.endpoint_scheme)
+ self.connection_kwargs = self.get_connection_kwargs(
+ self.endpoint_scheme, **kwargs)
+
+ self.auth_token = kwargs.get('token')
+
+ @staticmethod
+ def parse_endpoint(endpoint):
+ return urlparse.urlparse(endpoint)
+
+ @staticmethod
+ def get_connection_class(scheme):
+ if scheme == 'https':
+ return VerifiedHTTPSConnection
+ else:
+ return httplib.HTTPConnection
+
+ @staticmethod
+ def get_connection_kwargs(scheme, **kwargs):
+ _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
+
+ if scheme == 'https':
+ _kwargs['cacert'] = kwargs.get('cacert', None)
+ _kwargs['cert_file'] = kwargs.get('cert_file', None)
+ _kwargs['key_file'] = kwargs.get('key_file', None)
+ _kwargs['insecure'] = kwargs.get('insecure', False)
+ _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
+
+ return _kwargs
+
+ def get_connection(self):
+ _class = self.connection_class
+ try:
+ return _class(self.endpoint_hostname, self.endpoint_port,
+ **self.connection_kwargs)
+ except httplib.InvalidURL:
+ raise exc.EndpointNotFound
+
+ def log_curl_request(self, method, url, kwargs):
+ curl = ['curl -i -X %s' % method]
+
+ for (key, value) in kwargs['headers'].items():
+ header = '-H \'%s: %s\'' % (key, value)
+ curl.append(header)
+
+ conn_params_fmt = [
+ ('key_file', '--key %s'),
+ ('cert_file', '--cert %s'),
+ ('cacert', '--cacert %s'),
+ ]
+ for (key, fmt) in conn_params_fmt:
+ value = self.connection_kwargs.get(key)
+ if value:
+ curl.append(fmt % value)
+
+ if self.connection_kwargs.get('insecure'):
+ curl.append('-k')
+
+ if 'body' in kwargs:
+ curl.append('-d \'%s\'' % kwargs['body'])
+
+ curl.append('%s%s' % (self.endpoint, url))
+ LOG.debug(' '.join(curl))
+
+ @staticmethod
+ def log_http_response(resp, body=None):
+ status = (resp.version / 10.0, resp.status, resp.reason)
+ dump = ['\nHTTP/%.1f %s %s' % status]
+ dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
+ dump.append('')
+ if body:
+ dump.extend([body, ''])
+ LOG.debug('\n'.join(dump))
+
+ def _http_request(self, url, method, **kwargs):
+ """ Send an http request with the specified characteristics.
+
+ Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
+ as setting headers and error handling.
+ """
+ # Copy the kwargs so we can reuse the original in case of redirects
+ kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
+ kwargs['headers'].setdefault('User-Agent', USER_AGENT)
+ if self.auth_token:
+ kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
+
+ self.log_curl_request(method, url, kwargs)
+ conn = self.get_connection()
+
+ try:
+ conn_url = posixpath.normpath('%s/%s' % (self.endpoint_path, url))
+ if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
+ conn.putrequest(method, conn_url)
+ for header, value in kwargs['headers'].items():
+ conn.putheader(header, value)
+ conn.endheaders()
+ chunk = kwargs['body'].read(CHUNKSIZE)
+ # Chunk it, baby...
+ while chunk:
+ conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
+ chunk = kwargs['body'].read(CHUNKSIZE)
+ conn.send('0\r\n\r\n')
+ else:
+ conn.request(method, conn_url, **kwargs)
+ resp = conn.getresponse()
+ except socket.gaierror as e:
+ message = "Error finding address for %(url)s: %(e)s" % locals()
+ raise exc.EndpointNotFound
+ except (socket.error, socket.timeout) as e:
+ endpoint = self.endpoint
+ message = "Error communicating with %(endpoint)s %(e)s" % locals()
+ raise exc.TimeoutException
+
+ body_iter = ResponseBodyIterator(resp)
+
+ # Read body into string if it isn't obviously image data
+ if resp.getheader('content-type', None) != 'application/octet-stream':
+ body_str = ''.join([chunk for chunk in body_iter])
+ self.log_http_response(resp, body_str)
+ body_iter = StringIO.StringIO(body_str)
+ else:
+ self.log_http_response(resp)
+
+ return resp, body_iter
+
+ def json_request(self, method, url, **kwargs):
+ kwargs.setdefault('headers', {})
+ kwargs['headers'].setdefault('Content-Type', 'application/json')
+
+ if 'body' in kwargs:
+ kwargs['body'] = json.dumps(kwargs['body'])
+
+ resp, body_iter = self._http_request(url, method, **kwargs)
+
+ if 'application/json' in resp.getheader('content-type', None):
+ body = ''.join([chunk for chunk in body_iter])
+ try:
+ body = json.loads(body)
+ except ValueError:
+ LOG.error('Could not decode response body as JSON')
+ else:
+ body = None
+
+ return resp, body
+
+ def raw_request(self, method, url, **kwargs):
+ kwargs.setdefault('headers', {})
+ kwargs['headers'].setdefault('Content-Type',
+ 'application/octet-stream')
+ if 'body' in kwargs:
+ if (hasattr(kwargs['body'], 'read')
+ and method.lower() in ('post', 'put')):
+ # We use 'Transfer-Encoding: chunked' because
+ # body size may not always be known in advance.
+ kwargs['headers']['Transfer-Encoding'] = 'chunked'
+ return self._http_request(url, method, **kwargs)
+
+
+class OpenSSLConnectionDelegator(object):
+ """
+ An OpenSSL.SSL.Connection delegator.
+
+ Supplies an additional 'makefile' method which httplib requires
+ and is not present in OpenSSL.SSL.Connection.
+
+ Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
+ a delegator must be used.
+ """
+ def __init__(self, *args, **kwargs):
+ self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self.connection, name)
+
+ def makefile(self, *args, **kwargs):
+ return socket._fileobject(self.connection, *args, **kwargs)
+
+
+class VerifiedHTTPSConnection(httplib.HTTPSConnection):
+ """
+ Extended HTTPSConnection which uses the OpenSSL library
+ for enhanced SSL support.
+ Note: Much of this functionality can eventually be replaced
+ with native Python 3.3 code.
+ """
+ def __init__(self, host, port=None, key_file=None, cert_file=None,
+ cacert=None, timeout=None, insecure=False,
+ ssl_compression=True):
+ httplib.HTTPSConnection.__init__(self, host, port,
+ key_file=key_file,
+ cert_file=cert_file)
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.timeout = timeout
+ self.insecure = insecure
+ self.ssl_compression = ssl_compression
+ self.cacert = cacert
+ self.setcontext()
+
+ @staticmethod
+ def host_matches_cert(host, x509):
+ """
+ Verify that the the x509 certificate we have received
+ from 'host' correctly identifies the server we are
+ connecting to, ie that the certificate's Common Name
+ or a Subject Alternative Name matches 'host'.
+ """
+ # First see if we can match the CN
+ if x509.get_subject().commonName == host:
+ return True
+
+ # Also try Subject Alternative Names for a match
+ san_list = None
+ for i in xrange(x509.get_extension_count()):
+ ext = x509.get_extension(i)
+ if ext.get_short_name() == 'subjectAltName':
+ san_list = str(ext)
+ for san in ''.join(san_list.split()).split(','):
+ if san == "DNS:%s" % host:
+ return True
+
+ # Server certificate does not match host
+ msg = ('Host "%s" does not match x509 certificate contents: '
+ 'CommonName "%s"' % (host, x509.get_subject().commonName))
+ if san_list is not None:
+ msg = msg + ', subjectAltName "%s"' % san_list
+ raise exc.SSLCertificateError(msg)
+
+ def verify_callback(self, connection, x509, errnum,
+ depth, preverify_ok):
+ if x509.has_expired():
+ msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
+ raise exc.SSLCertificateError(msg)
+
+ if depth == 0 and preverify_ok is True:
+ # We verify that the host matches against the last
+ # certificate in the chain
+ return self.host_matches_cert(self.host, x509)
+ else:
+ # Pass through OpenSSL's default result
+ return preverify_ok
+
+ def setcontext(self):
+ """
+ Set up the OpenSSL context.
+ """
+ self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+
+ if self.ssl_compression is False:
+ self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
+
+ if self.insecure is not True:
+ self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
+ self.verify_callback)
+ else:
+ self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
+ self.verify_callback)
+
+ if self.cert_file:
+ try:
+ self.context.use_certificate_file(self.cert_file)
+ except Exception, e:
+ msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
+ raise exc.SSLConfigurationError(msg)
+ if self.key_file is None:
+ # We support having key and cert in same file
+ try:
+ self.context.use_privatekey_file(self.cert_file)
+ except Exception, e:
+ msg = ('No key file specified and unable to load key '
+ 'from "%s" %s' % (self.cert_file, e))
+ raise exc.SSLConfigurationError(msg)
+
+ if self.key_file:
+ try:
+ self.context.use_privatekey_file(self.key_file)
+ except Exception, e:
+ msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
+ raise exc.SSLConfigurationError(msg)
+
+ if self.cacert:
+ try:
+ self.context.load_verify_locations(self.cacert)
+ except Exception, e:
+ msg = 'Unable to load CA from "%s"' % (self.cacert, e)
+ raise exc.SSLConfigurationError(msg)
+ else:
+ self.context.set_default_verify_paths()
+
+ def connect(self):
+ """
+ Connect to an SSL port using the OpenSSL library and apply
+ per-connection parameters.
+ """
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.timeout is not None:
+ # '0' microseconds
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
+ struct.pack('LL', self.timeout, 0))
+ self.sock = OpenSSLConnectionDelegator(self.context, sock)
+ self.sock.connect((self.host, self.port))
+
+
+class ResponseBodyIterator(object):
+ """A class that acts as an iterator over an HTTP response."""
+
+ def __init__(self, resp):
+ self.resp = resp
+
+ def __iter__(self):
+ while True:
+ yield self.next()
+
+ def next(self):
+ chunk = self.resp.read(CHUNKSIZE)
+ if chunk:
+ return chunk
+ else:
+ raise StopIteration()
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 5af4a40..e163126 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -275,6 +275,45 @@
self._log_response(resp, resp_body)
self.response_checker(method, url, headers, body, resp, resp_body)
+ self._error_checker(method, url, headers, body, resp, resp_body, depth,
+ wait)
+
+ return resp, resp_body
+
+ def _error_checker(self, method, url,
+ headers, body, resp, resp_body, depth=0, wait=None):
+
+ # NOTE(mtreinish): Check for httplib response from glance_http. The
+ # object can't be used here because importing httplib breaks httplib2.
+ # If another object from a class not imported were passed here as
+ # resp this could possibly fail
+ if str(type(resp)) == "<type 'instance'>":
+ ctype = resp.getheader('content-type')
+ else:
+ try:
+ ctype = resp['content-type']
+ # NOTE(mtreinish): Keystone delete user responses doesn't have a
+ # content-type header. (They don't have a body) So just pretend it
+ # is set.
+ except KeyError:
+ ctype = 'application/json'
+
+ JSON_ENC = ['application/json; charset=UTF-8', 'application/json',
+ 'application/json; charset=utf-8']
+ # NOTE(mtreinish): This is for compatibility with Glance and swift
+ # APIs. These are the return content types that Glance api v1
+ # (and occasionally swift) are using.
+ TXT_ENC = ['text/plain; charset=UTF-8', 'text/html; charset=UTF-8',
+ 'text/plain; charset=utf-8']
+ XML_ENC = ['application/xml', 'application/xml; charset=UTF-8']
+
+ if ctype in JSON_ENC or ctype in XML_ENC:
+ parse_resp = True
+ elif ctype in TXT_ENC:
+ parse_resp = False
+ else:
+ raise exceptions.RestClientException(str(resp.status))
+
if resp.status == 401 or resp.status == 403:
raise exceptions.Unauthorized()
@@ -282,44 +321,45 @@
raise exceptions.NotFound(resp_body)
if resp.status == 400:
- resp_body = self._parse_resp(resp_body)
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
raise exceptions.BadRequest(resp_body)
if resp.status == 409:
- resp_body = self._parse_resp(resp_body)
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
raise exceptions.Duplicate(resp_body)
if resp.status == 413:
- resp_body = self._parse_resp(resp_body)
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
#Checking whether Absolute/Rate limit
return self.check_over_limit(resp_body, method, url, headers, body,
depth, wait)
if resp.status in (500, 501):
- resp_body = self._parse_resp(resp_body)
- #I'm seeing both computeFault and cloudServersFault come back.
- #Will file a bug to fix, but leave as is for now.
-
- if 'cloudServersFault' in resp_body:
- message = resp_body['cloudServersFault']['message']
- elif 'computeFault' in resp_body:
- message = resp_body['computeFault']['message']
- 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
+ message = resp_body
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ #I'm seeing both computeFault and cloudServersFault come back.
+ #Will file a bug to fix, but leave as is for now.
+ if 'cloudServersFault' in resp_body:
+ message = resp_body['cloudServersFault']['message']
+ elif 'computeFault' in resp_body:
+ message = resp_body['computeFault']['message']
+ 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']
raise exceptions.ComputeFault(message)
if resp.status >= 400:
- resp_body = self._parse_resp(resp_body)
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
raise exceptions.RestClientException(str(resp.status))
- return resp, resp_body
-
def check_over_limit(self, resp_body, method, url,
headers, body, depth, wait):
self.is_absolute_limit(resp_body['overLimit'])
diff --git a/tempest/config.py b/tempest/config.py
index ec48f67..c982dee 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -19,7 +19,6 @@
import os
import sys
-from tempest.common.utils import data_utils
from tempest.common.utils.misc import singleton
from tempest.openstack.common import cfg
@@ -237,6 +236,9 @@
cfg.StrOpt('api_version',
default='1',
help="Version of the API"),
+ cfg.StrOpt('catalog_type',
+ default='image',
+ help='Catalog type of the Image service.')
]
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index fca2d2d..577aa13 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -116,6 +116,10 @@
message = "Got compute fault"
+class ImageFault(TempestException):
+ message = "Got image fault"
+
+
class IdentityError(TempestException):
message = "Got identity error"
diff --git a/tempest/services/compute/admin/xml/quotas_client.py b/tempest/services/compute/admin/xml/quotas_client.py
index d567a9c..f416334 100644
--- a/tempest/services/compute/admin/xml/quotas_client.py
+++ b/tempest/services/compute/admin/xml/quotas_client.py
@@ -15,11 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import urllib
-
from lxml import etree
-from tempest.common.rest_client import RestClientXML
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import xml_to_json
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index 9bb1d11..6469761 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
-
XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
diff --git a/tempest/services/compute/xml/limits_client.py b/tempest/services/compute/xml/limits_client.py
index 473952b..d233bba 100644
--- a/tempest/services/compute/xml/limits_client.py
+++ b/tempest/services/compute/xml/limits_client.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
from lxml import objectify
from tempest.common.rest_client import RestClientXML
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 8978214..faa0aab 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -15,15 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import urllib
-
from lxml import etree
from tempest.common.rest_client import RestClientXML
-from tempest.services.compute.xml.common import Document
-from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import xml_to_json
-from tempest.services.compute.xml.common import XMLNS_11
class QuotasClientXML(RestClientXML):
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index f79c3d5..1b96ae0 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -17,16 +17,13 @@
import httplib2
import json
-import logging
from lxml import etree
-from tempest.common.rest_client import RestClient
from tempest.common.rest_client import RestClientXML
from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
-from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
diff --git a/tempest/services/image/json/__init__.py b/tempest/services/image/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/image/json/__init__.py
diff --git a/tempest/services/image/json/image_client.py b/tempest/services/image/json/image_client.py
new file mode 100644
index 0000000..d595ec3
--- /dev/null
+++ b/tempest/services/image/json/image_client.py
@@ -0,0 +1,199 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM
+# 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 copy
+import json
+import os
+import time
+import urllib
+
+from tempest.common import glance_http
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+from tempest import manager
+
+
+class ImageClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ImageClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.images.catalog_type
+ self.http = self._get_http()
+
+ def _image_meta_from_headers(self, headers):
+ meta = {'properties': {}}
+ for key, value in headers.iteritems():
+ if key.startswith('x-image-meta-property-'):
+ _key = key[22:]
+ meta['properties'][_key] = value
+ elif key.startswith('x-image-meta-'):
+ _key = key[13:]
+ meta[_key] = value
+
+ for key in ['is_public', 'protected', 'deleted']:
+ if key in meta:
+ meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
+ '1')
+ for key in ['size', 'min_ram', 'min_disk']:
+ if key in meta:
+ try:
+ meta[key] = int(meta[key])
+ except ValueError:
+ pass
+ return meta
+
+ def _image_meta_to_headers(self, fields):
+ headers = {}
+ fields_copy = copy.deepcopy(fields)
+ for key, value in fields_copy.pop('properties', {}).iteritems():
+ headers['x-image-meta-property-%s' % key] = str(value)
+ for key, value in fields_copy.iteritems():
+ headers['x-image-meta-%s' % key] = str(value)
+ return headers
+
+ def _get_file_size(self, obj):
+ """Analyze file-like object and attempt to determine its size.
+
+ :param obj: file-like object, typically redirected from stdin.
+ :retval The file's size or None if it cannot be determined.
+ """
+ # For large images, we need to supply the size of the
+ # image file. See LP Bugs #827660 and #845788.
+ if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
+ try:
+ obj.seek(0, os.SEEK_END)
+ obj_size = obj.tell()
+ obj.seek(0)
+ return obj_size
+ except IOError, e:
+ if e.errno == errno.ESPIPE:
+ # Illegal seek. This means the user is trying
+ # to pipe image data to the client, e.g.
+ # echo testdata | bin/glance add blah..., or
+ # that stdin is empty, or that a file-like
+ # object which doesn't support 'seek/tell' has
+ # been supplied.
+ return None
+ else:
+ raise
+ else:
+ # Cannot determine size of input image
+ return None
+
+ def _get_http(self):
+ temp_manager = manager.DefaultClientManager()
+ keystone = temp_manager._get_identity_client()
+ token = keystone.auth_token
+ endpoint = keystone.service_catalog.url_for(service_type='image',
+ endpoint_type='publicURL')
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ return glance_http.HTTPClient(endpoint=endpoint, token=token,
+ insecure=dscv)
+
+ def _create_with_data(self, headers, data):
+ resp, body_iter = self.http.raw_request('POST', '/v1/images',
+ headers=headers, body=data)
+ self._error_checker('POST', '/v1/images', headers, data, resp,
+ body_iter)
+ body = json.loads(''.join([c for c in body_iter]))
+ return resp, body['image']
+
+ def _update_with_data(self, image_id, headers, data):
+ url = '/v1/images/%s' % image_id
+ resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
+ body=data)
+ self._error_checker('PUT', url, headers, data,
+ resp, body_iter)
+ body = json.loads(''.join([c for c in body_iter]))
+ return resp, body['image']
+
+ def create_image(self, name, container_format, disk_format, is_public=True,
+ location=None, properties=None, data=None):
+ params = {
+ "name": name,
+ "container_format": container_format,
+ "disk_format": disk_format,
+ "is_public": is_public,
+ }
+ headers = {}
+
+ if location is not None:
+ params['location'] = location
+
+ if properties is not None:
+ params['properties'] = properties
+
+ headers.update(self._image_meta_to_headers(params))
+
+ if data is not None:
+ return self._create_with_data(headers, data)
+
+ resp, body = self.post('v1/images', data, headers)
+ body = json.loads(body)
+ return resp, body['image']
+
+ def update_image(self, image_id, name=None, container_format=None,
+ data=None):
+ params = {}
+ headers = {}
+ if name is not None:
+ params['name'] = name
+
+ if container_format is not None:
+ params['container_format'] = container_format
+
+ headers.update(self._image_meta_to_headers(params))
+
+ if data is not None:
+ return self._update_with_data(image_id, headers, data)
+
+ url = 'v1/images/%s' % image_id
+ resp, body = self.put(url, data, headers)
+ body = json.loads(body)
+ return resp, body['image']
+
+ def delete_image(self, image_id):
+ url = 'v1/images/%s' % image_id
+ try:
+ self.delete(url)
+ except exceptions.Unauthorized:
+ url = '/' + url
+ self.http.raw_request('DELETE', url)
+
+ def image_list(self, params=None):
+ url = 'v1/images'
+
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['images']
+
+ def get_image(self, image_id, wait=None):
+ url = 'v1/images/%s' % image_id
+ resp, __ = self.get(url, wait=wait)
+ body = self._image_meta_from_headers(resp)
+ return resp, body
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_image(id, wait=True)
+ except exceptions.NotFound:
+ return True
+ return False
diff --git a/tempest/services/image/service.py b/tempest/services/image/service.py
deleted file mode 100644
index 66ba219..0000000
--- a/tempest/services/image/service.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# 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.
-
-"""
-Image Service class, which acts as a descriptor for the OpenStack Images
-service running in the test environment.
-"""
-
-from tempest.services import Service as BaseService
-
-
-class Service(BaseService):
-
- def __init__(self, config):
- """
- Initializes the service.
-
- :param config: `tempest.config.Config` object
- """
- self.config = config
-
- # Determine the Images API version
- self.api_version = int(config.images.api_version)
-
- # We load the client class specific to the API version...
- if self.api_version == 1:
- import glanceclient
- import keystoneclient.v2_0.client
-
- dscv = self.config.identity.disable_ssl_certificate_validation
- auth_url = self.config.identity.uri
- keystone = keystoneclient.v2_0.client.Client(
- username=config.identity.username,
- password=config.identity.password,
- tenant_name=config.identity.tenant_name,
- auth_url=auth_url,
- insecure=dscv)
- token = keystone.auth_token
- endpoint = keystone.service_catalog.url_for(
- service_type='image',
- endpoint_type='publicURL')
-
- self._client = glanceclient.Client('1',
- endpoint=endpoint,
- token=token,
- insecure=dscv)
- else:
- raise NotImplementedError
-
- def get_client(self):
- """
- Returns a client object that may be used to query
- the service API.
- """
- assert self._client
- return self._client
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index e7820d7..ac1859a 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -18,11 +18,10 @@
from hashlib import sha1
import hmac
import httplib2
-import json
-import re
+from urlparse import urlparse
+
from tempest.common.rest_client import RestClient
from tempest import exceptions
-from urlparse import urlparse
class ObjectClient(RestClient):
diff --git a/tempest/testboto.py b/tempest/testboto.py
index d279b4c..14844b3 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -18,13 +18,11 @@
from contextlib import closing
import logging
import re
-import time
import boto
from boto.exception import BotoServerError
from boto.exception import EC2ResponseError
from boto.s3.bucket import Bucket
-from boto.s3.key import Key
import testresources
import testtools
@@ -33,7 +31,6 @@
from tempest.tests.boto.utils.wait import re_search_wait
from tempest.tests.boto.utils.wait import state_wait
from tempest.tests.boto.utils.wait import wait_exception
-from tempest.tests.boto.utils.wait import wait_no_exception
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 8a7ce8b..403ec4c 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -15,17 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-from contextlib import closing
import logging
from boto.exception import EC2ResponseError
-from boto.s3.key import Key
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
-from tempest.exceptions import EC2RegisterImageException
from tempest.test import attr
from tempest.testboto import BotoTestCase
import tempest.tests.boto
diff --git a/tempest/tests/boto/test_ec2_security_groups.py b/tempest/tests/boto/test_ec2_security_groups.py
index 5981408..dd46a91 100644
--- a/tempest/tests/boto/test_ec2_security_groups.py
+++ b/tempest/tests/boto/test_ec2_security_groups.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/tests/boto/test_ec2_volumes.py
index 7d3c5ab..aa2325f 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/tests/boto/test_ec2_volumes.py
@@ -16,9 +16,6 @@
# under the License.
import logging
-import time
-
-import testtools
from tempest import clients
from tempest.test import attr
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/tests/boto/test_s3_ec2_images.py
index da248e1..8913395 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/tests/boto/test_s3_ec2_images.py
@@ -15,10 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from contextlib import closing
import os
-from boto.s3.key import Key
import testtools
from tempest import clients
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index e7bc4b1..8334b07 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -24,7 +24,6 @@
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
from tempest.testboto import BotoTestCase
-from tempest.tests import boto
@attr("S3")
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index fe977c1..9ad8745 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_int_id
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 0f46d62..a4b106b 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -20,7 +20,6 @@
import quotas_client as adm_quotas_json
from tempest.services.compute.admin.xml import quotas_client as adm_quotas_xml
from tempest.test import attr
-from tempest.tests import compute
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index 9f0cda0..165cf79 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -15,9 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
-from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
diff --git a/tempest/tests/compute/floating_ips/test_list_floating_ips.py b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
index e534e3c..42befd0 100644
--- a/tempest/tests/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 446ef50..edc58e7 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -20,7 +20,6 @@
from tempest import clients
from tempest.common.utils.data_utils import parse_image_id
from tempest.common.utils.data_utils import rand_name
-import tempest.config
from tempest import exceptions
from tempest.test import attr
from tempest.tests import compute
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index 5cc921b..08966fd 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -20,7 +20,6 @@
from tempest import clients
from tempest.common.utils.data_utils import parse_image_id
from tempest.common.utils.data_utils import rand_name
-import tempest.config
from tempest import exceptions
from tempest.test import attr
from tempest.tests import compute
diff --git a/tempest/tests/compute/images/test_list_images.py b/tempest/tests/compute/images/test_list_images.py
index b9b57ee..0f661b7 100644
--- a/tempest/tests/compute/images/test_list_images.py
+++ b/tempest/tests/compute/images/test_list_images.py
@@ -15,9 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_disk_config.py b/tempest/tests/compute/servers/test_disk_config.py
index 3987e69..57b95f2 100644
--- a/tempest/tests/compute/servers/test_disk_config.py
+++ b/tempest/tests/compute/servers/test_disk_config.py
@@ -18,7 +18,6 @@
import testtools
from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_list_server_filters.py b/tempest/tests/compute/servers/test_list_server_filters.py
index e07a905..9b061b5 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -16,8 +16,6 @@
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
diff --git a/tempest/tests/compute/servers/test_list_servers_negative.py b/tempest/tests/compute/servers/test_list_servers_negative.py
index eb4ea02..f93bebf 100644
--- a/tempest/tests/compute/servers/test_list_servers_negative.py
+++ b/tempest/tests/compute/servers/test_list_servers_negative.py
@@ -15,13 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import re
-import sys
-
-import testtools
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.tests import compute
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index 693b0bf..c30538f 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -122,13 +122,24 @@
linux_client = RemoteClient(server, self.ssh_user, password)
self.assertTrue(linux_client.can_authenticate())
+ def _detect_server_image_flavor(self, server_id):
+ # Detects the current server image flavor ref.
+ resp, server = self.client.get_server(self.server_id)
+ current_flavor = server['flavor']['id']
+ new_flavor_ref = self.flavor_ref_alt \
+ if int(current_flavor) == self.flavor_ref else self.flavor_ref
+ return int(current_flavor), int(new_flavor_ref)
+
@attr(type='smoke')
@testtools.skipIf(not resize_available, 'Resize not available.')
def test_resize_server_confirm(self):
# The server's RAM and disk space should be modified to that of
# the provided flavor
- resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
@@ -136,7 +147,7 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
resp, server = self.client.get_server(self.server_id)
- self.assertEqual(self.flavor_ref_alt, int(server['flavor']['id']))
+ self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
@attr(type='positive')
@testtools.skipIf(not resize_available, 'Resize not available.')
@@ -144,7 +155,10 @@
# The server's RAM and disk space should return to its original
# values after a resize is reverted
- resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
@@ -155,7 +169,7 @@
resp, server = self.client.get_server(self.server_id)
start = int(time.time())
- while server['flavor']['id'] != self.flavor_ref:
+ while int(server['flavor']['id']) != previous_flavor_ref:
time.sleep(self.build_interval)
resp, server = self.client.get_server(self.server_id)
diff --git a/tempest/tests/compute/servers/test_servers_negative.py b/tempest/tests/compute/servers/test_servers_negative.py
index 55d9581..09d2a9c 100644
--- a/tempest/tests/compute/servers/test_servers_negative.py
+++ b/tempest/tests/compute/servers/test_servers_negative.py
@@ -17,8 +17,6 @@
import sys
-import testtools
-
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index 5ccfd26..6449ddc 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -20,7 +20,6 @@
import testtools
-from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import config
from tempest import exceptions
from tempest.services.compute.json.hosts_client import HostsClientJSON
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index edf3179..1aed833 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -17,7 +17,6 @@
import testtools
-from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
diff --git a/tempest/tests/identity/admin/test_roles.py b/tempest/tests/identity/admin/test_roles.py
index 2779b51..3194e7d 100644
--- a/tempest/tests/identity/admin/test_roles.py
+++ b/tempest/tests/identity/admin/test_roles.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.tests.identity import base
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index 0246f8c..16b9fd8 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -18,7 +18,6 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
-from tempest.test import attr
from tempest.tests.identity import base
diff --git a/tempest/tests/identity/admin/test_tenants.py b/tempest/tests/identity/admin/test_tenants.py
index 9321b07..594afe8 100644
--- a/tempest/tests/identity/admin/test_tenants.py
+++ b/tempest/tests/identity/admin/test_tenants.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
diff --git a/tempest/tests/image/test_images.py b/tempest/tests/image/test_images.py
index b8bc1c5..511f8b0 100644
--- a/tempest/tests/image/test_images.py
+++ b/tempest/tests/image/test_images.py
@@ -23,14 +23,8 @@
from tempest.test import attr
-GLANCE_INSTALLED = False
-try:
- import glanceclient
- GLANCE_INSTALLED = True
-except ImportError:
- pass
-
from tempest import clients
+from tempest import exceptions
class CreateRegisterImagesTest(testtools.testcase.WithAttributes,
@@ -42,91 +36,76 @@
@classmethod
def setUpClass(cls):
- if not GLANCE_INSTALLED:
- raise cls.skipException('Glance not installed')
- cls.os = clients.ServiceManager()
- cls.client = cls.os.images.get_client()
+ cls.os = clients.Manager()
+ cls.client = cls.os.image_client
cls.created_images = []
@classmethod
def tearDownClass(cls):
for image_id in cls.created_images:
- cls.client.images.delete(image_id)
+ cls.client.delete(image_id)
- @attr(type='image')
- def test_register_with_invalid_data(self):
+ @attr(type='negative')
+ def test_register_with_invalid_container_format(self):
# Negative tests for invalid data supplied to POST /images
+ try:
+ resp, body = self.client.create_image('test', 'wrong', 'vhd')
+ except exceptions.BadRequest:
+ pass
+ else:
+ self.fail('Invalid container format should not be accepted')
- metas = [
- {
- 'id': '1'
- }, # Cannot specify ID in registration
- {
- 'container_format': 'wrong',
- }, # Invalid container format
- {
- 'disk_format': 'wrong',
- }, # Invalid disk format
- ]
- for meta in metas:
- try:
- self.client.images.create(**meta)
- except glanceclient.exc.HTTPBadRequest:
- continue
- self.fail("Did not raise Invalid for meta %s." % meta)
+ @attr(type='negative')
+ def test_register_with_invalid_disk_format(self):
+ try:
+ resp, body = self.client.create_image('test', 'bare', 'wrong')
+ except exceptions.BadRequest:
+ pass
+ else:
+ self.fail("Invalid disk format should not be accepted")
@attr(type='image')
def test_register_then_upload(self):
# Register, then upload an image
- meta = {
- 'name': 'New Name',
- 'is_public': True,
- 'disk_format': 'vhd',
- 'container_format': 'bare',
- 'properties': {'prop1': 'val1'}
- }
- results = self.client.images.create(**meta)
- self.assertTrue(hasattr(results, 'id'))
- image_id = results.id
+ properties = {'prop1': 'val1'}
+ resp, body = self.client.create_image('New Name', 'bare', 'raw',
+ is_public=True,
+ properties=properties)
+ self.assertTrue('id' in body)
+ image_id = body.get('id')
self.created_images.append(image_id)
- self.assertTrue(hasattr(results, 'name'))
- self.assertEqual(meta['name'], results.name)
- self.assertTrue(hasattr(results, 'is_public'))
- self.assertEqual(meta['is_public'], results.is_public)
- self.assertTrue(hasattr(results, 'status'))
- self.assertEqual('queued', results.status)
- self.assertTrue(hasattr(results, 'properties'))
- for key, val in meta['properties'].items():
- self.assertEqual(val, results.properties[key])
+ self.assertTrue('name' in body)
+ self.assertEqual('New Name', body.get('name'))
+ self.assertTrue('is_public' in body)
+ self.assertTrue(body.get('is_public'))
+ self.assertTrue('status' in body)
+ self.assertEqual('queued', body.get('status'))
+ self.assertTrue('properties' in body)
+ for key, val in properties.items():
+ self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
- image_file = StringIO.StringIO('*' * 1024)
- results = self.client.images.update(image_id, data=image_file)
- self.assertTrue(hasattr(results, 'status'))
- self.assertEqual('active', results.status)
- self.assertTrue(hasattr(results, 'size'))
- self.assertEqual(1024, results.size)
+ image_file = StringIO.StringIO(('*' * 1024))
+ resp, body = self.client.update_image(image_id, data=image_file)
+ self.assertTrue('size' in body)
+ self.assertEqual(1024, body.get('size'))
@attr(type='image')
def test_register_remote_image(self):
# Register a new remote image
- meta = {
- 'name': 'New Remote Image',
- 'is_public': True,
- 'disk_format': 'raw',
- 'container_format': 'bare',
- 'location': 'http://example.com/someimage.iso'
- }
- results = self.client.images.create(**meta)
- self.assertTrue(hasattr(results, 'id'))
- image_id = results.id
+ resp, body = self.client.create_image('New Remote Image', 'bare',
+ 'raw', is_public=True,
+ location='http://example.com'
+ '/someimage.iso')
+ self.assertTrue('id' in body)
+ image_id = body.get('id')
self.created_images.append(image_id)
- self.assertTrue(hasattr(results, 'name'))
- self.assertEqual(meta['name'], results.name)
- self.assertTrue(hasattr(results, 'is_public'))
- self.assertEqual(meta['is_public'], results.is_public)
- self.assertTrue(hasattr(results, 'status'))
- self.assertEqual('active', results.status)
+ self.assertTrue('name' in body)
+ self.assertEqual('New Remote Image', body.get('name'))
+ self.assertTrue('is_public' in body)
+ self.assertTrue(body.get('is_public'))
+ self.assertTrue('status' in body)
+ self.assertEqual('active', body.get('status'))
class ListImagesTest(testtools.testcase.WithAttributes,
@@ -138,10 +117,8 @@
@classmethod
def setUpClass(cls):
- if not GLANCE_INSTALLED:
- raise cls.skipException('Glance not installed')
- cls.os = clients.ServiceManager()
- cls.client = cls.os.images.get_client()
+ cls.os = clients.Manager()
+ cls.client = cls.os.image_client
cls.created_images = []
# We add a few images here to test the listing functionality of
@@ -156,7 +133,8 @@
@classmethod
def tearDownClass(cls):
for image_id in cls.created_images:
- cls.client.images.delete(image_id)
+ cls.client.delete_image(image_id)
+ cls.client.wait_for_resource_deletion(image_id)
@classmethod
def _create_remote_image(cls, x):
@@ -164,15 +142,12 @@
Create a new remote image and return the ID of the newly-registered
image
"""
- meta = {
- 'name': 'New Remote Image %s' % x,
- 'is_public': True,
- 'disk_format': 'raw',
- 'container_format': 'bare',
- 'location': 'http://example.com/someimage_%s.iso' % x
- }
- results = cls.client.images.create(**meta)
- image_id = results.id
+ name = 'New Remote Image %s' % x
+ location = 'http://example.com/someimage_%s.iso' % x
+ resp, body = cls.client.create_image(name, 'bare', 'raw',
+ is_public=True,
+ location=location)
+ image_id = body['id']
return image_id
@classmethod
@@ -183,19 +158,17 @@
1024 and 4096
"""
image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
- meta = {
- 'name': 'New Standard Image %s' % x,
- 'is_public': True,
- 'disk_format': 'raw',
- 'container_format': 'bare',
- 'data': image_file,
- }
- results = cls.client.images.create(**meta)
- image_id = results.id
+ name = 'New Standard Image %s' % x
+ resp, body = cls.client.create_image(name, 'bare', 'raw',
+ is_public=True, data=image_file)
+ image_id = body['id']
return image_id
@attr(type='image')
def test_index_no_params(self):
# Simple test to see all fixture images returned
- current_images = set(i.id for i in self.client.images.list())
- self.assertTrue(set(self.created_images) <= current_images)
+ resp, images_list = self.client.image_list()
+ self.assertEqual(resp['status'], '200')
+ image_list = map(lambda x: x['id'], images_list)
+ for image in self.created_images:
+ self.assertTrue(image in image_list)
diff --git a/tempest/tests/object_storage/base.py b/tempest/tests/object_storage/base.py
index e8933d0..2492d8b 100644
--- a/tempest/tests/object_storage/base.py
+++ b/tempest/tests/object_storage/base.py
@@ -18,7 +18,6 @@
import testtools
from tempest import clients
-import tempest.config
from tempest import exceptions
from tempest.tests.identity.base import DataGenerator
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index f087aff..dad6309 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
from tempest.tests.object_storage import base
diff --git a/tempest/tests/volume/test_volumes_snapshots.py b/tempest/tests/volume/test_volumes_snapshots.py
index 75ea155..3acc5f6 100644
--- a/tempest/tests/volume/test_volumes_snapshots.py
+++ b/tempest/tests/volume/test_volumes_snapshots.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
from tempest.tests.volume import base