Rework exceptions in Tempest
* Add base exception class similar to other OS projects
* Catch certain HTTP errors and raise exceptions in base
client classes
* Fixes LP Bug#899701 by adding tearDownClass() method
to the test_list_images.ListImagesTest class to destroy
images and instances the test case creates
Change-Id: I0f616813539b31da27e5106a59c2ca3765b1919f
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 690738f..ea09cc0 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -105,6 +105,10 @@
req_url = "%s/%s" % (self.base_url, url)
resp, body = self.http_obj.request(req_url, method,
headers=headers, body=body)
+
+ if resp.status == 404:
+ raise exceptions.NotFound(body)
+
if resp.status == 400:
body = json.loads(body)
raise exceptions.BadRequest(body['badRequest']['message'])
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 73798eb..f1becda 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -1,56 +1,64 @@
-class TimeoutException(Exception):
- """Exception on timeout"""
- def __init__(self, message):
- self.message = message
+class TempestException(Exception):
+ """
+ Base Tempest Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = "An unknown exception occurred"
+
+ def __init__(self, *args, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+ if len(args) > 0:
+ # If there is a non-kwarg parameter, assume it's the error
+ # message or reason description and tack it on to the end
+ # of the exception message
+ # Convert all arguments into their string representations...
+ args = ["%s" % arg for arg in args]
+ self._error_string = (self._error_string +
+ "\nDetails: %s" % '\n'.join(args))
def __str__(self):
- return repr(self.message)
+ return self._error_string
-class BuildErrorException(Exception):
- """Server entered ERROR status unintentionally"""
- def __init__(self, message):
- self.message = message
-
- def __str__(self):
- return repr(self.message)
+class NotFound(TempestException):
+ message = "Object not found"
-class BadRequest(Exception):
- def __init__(self, message):
- self.message = message
-
- def __str__(self):
- return repr(self.message)
+class TimeoutException(TempestException):
+ message = "Request timed out"
-class AuthenticationFailure(Exception):
- msg = ("Authentication with user %(user)s and password "
- "%(password)s failed.")
-
- def __init__(self, **kwargs):
- self.message = self.msg % kwargs
+class BuildErrorException(TempestException):
+ message = "Server %(server_id)s failed to build and is in ERROR status"
-class EndpointNotFound(Exception):
- def __init__(self, message):
- self.message = message
-
- def __str__(self):
- return repr(self.message)
+class BadRequest(TempestException):
+ message = "Bad request"
-class OverLimit(Exception):
- def __init__(self, message):
- self.message = message
-
- def __str__(self):
- return repr(self.message)
+class AuthenticationFailure(TempestException):
+ message = ("Authentication with user %(user)s and password "
+ "%(password)s failed")
-class ComputeFault(Exception):
- def __init__(self, message):
- self.message = message
+class EndpointNotFound(TempestException):
+ message = "Endpoint not found"
- def __str__(self):
- return repr(self.message)
+
+class OverLimit(TempestException):
+ message = "Quota exceeded"
+
+
+class ComputeFault(TempestException):
+ message = "Got compute fault"
+
+
+class Duplicate(TempestException):
+ message = "An object with that identifier already exists"
diff --git a/tempest/services/nova/json/servers_client.py b/tempest/services/nova/json/servers_client.py
index 2be30c5..4f2e257 100644
--- a/tempest/services/nova/json/servers_client.py
+++ b/tempest/services/nova/json/servers_client.py
@@ -55,6 +55,7 @@
post_body = json.dumps({'server': post_body})
resp, body = self.client.post('servers', post_body, self.headers)
+
body = json.loads(body)
return resp, body['server']
@@ -140,11 +141,10 @@
resp, body = self.get_server(server_id)
server_status = body['status']
- if(server_status == 'ERROR'):
- message = 'Server %s entered ERROR status.' % server_id
- raise exceptions.BuildErrorException(message)
+ if server_status == 'ERROR':
+ raise exceptions.BuildErrorException(server_id=server_id)
- if (int(time.time()) - start >= self.build_timeout):
+ if int(time.time()) - start >= self.build_timeout:
message = 'Server %s failed to reach ACTIVE status within the \
required time (%s s).' % (server_id, self.build_timeout)
raise exceptions.TimeoutException(message)
diff --git a/tempest/tests/test_flavors.py b/tempest/tests/test_flavors.py
index aadcc17..70ae9db 100644
--- a/tempest/tests/test_flavors.py
+++ b/tempest/tests/test_flavors.py
@@ -1,7 +1,10 @@
-from nose.plugins.attrib import attr
-from tempest import openstack
import unittest2 as unittest
+from nose.plugins.attrib import attr
+
+from tempest import exceptions
+from tempest import openstack
+
class FlavorsTest(unittest.TestCase):
@@ -40,9 +43,5 @@
@attr(type='negative')
def test_get_non_existant_flavor(self):
"""flavor details are not returned for non existant flavors"""
- try:
- resp, flavor = self.client.get_flavor_details(999)
- except:
- pass
- else:
- self.fail('Should not get details for a non-existant flavor')
+ self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
+ 999)
diff --git a/tempest/tests/test_list_images.py b/tempest/tests/test_list_images.py
index 363678b..1e30651 100644
--- a/tempest/tests/test_list_images.py
+++ b/tempest/tests/test_list_images.py
@@ -1,7 +1,10 @@
+import unittest2 as unittest
+
from nose.plugins.attrib import attr
+
+from tempest import exceptions
from tempest import openstack
from tempest.common.utils.data_utils import rand_name
-import unittest2 as unittest
def _parse_image_id(image_ref):
@@ -32,7 +35,7 @@
cls.flavor_ref)
cls.servers_client.wait_for_server_status(cls.server2['id'], 'ACTIVE')
- #Create images to be used in the filter tests
+ # Create images to be used in the filter tests
image1_name = rand_name('image')
resp, body = cls.client.create_image(cls.server1['id'], image1_name)
cls.image1_id = _parse_image_id(resp['location'])
@@ -54,12 +57,26 @@
cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
resp, cls.image3 = cls.client.get_image(cls.image3_id)
+ @classmethod
+ def tearDownClass(cls):
+ cls.client.delete_image(cls.image1_id)
+ cls.client.delete_image(cls.image2_id)
+ cls.client.delete_image(cls.image3_id)
+ cls.servers_client.delete_server(cls.server1['id'])
+ cls.servers_client.delete_server(cls.server2['id'])
+
@attr(type='smoke')
def test_get_image(self):
"""Returns the correct details for a single image"""
resp, image = self.client.get_image(self.image_ref)
self.assertEqual(self.image_ref, image['id'])
+ @attr(type='negative')
+ def test_get_image_not_existing(self):
+ """Check raises a NotFound"""
+ self.assertRaises(exceptions.NotFound, self.client.get_image,
+ "nonexistingimageid")
+
@attr(type='smoke')
def test_list_images(self):
"""The list of all images should contain the image"""
@@ -91,13 +108,16 @@
self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ @unittest.skip('Skipping until Nova Bug 912837 is fixed')
@attr(type='positive')
def test_list_images_filter_by_server_id(self):
"""The images should contain images filtered by server id"""
params = {'server': self.server1['id']}
resp, images = self.client.list_images(params)
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]),
+ "Failed to find image %s in images. Got images %s" %
+ (self.image1_id, images))
self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
diff --git a/tempest/tests/test_list_servers.py b/tempest/tests/test_list_servers.py
index 59c31a6..8917f48 100644
--- a/tempest/tests/test_list_servers.py
+++ b/tempest/tests/test_list_servers.py
@@ -1,7 +1,9 @@
import unittest2 as unittest
-from tempest import exceptions
+import nose.plugins.skip
+
from tempest import openstack
+from tempest import exceptions
from tempest.common.utils.data_utils import rand_name
from tempest.tests import utils
@@ -12,6 +14,7 @@
def setUpClass(cls):
cls.os = openstack.Manager()
cls.client = cls.os.servers_client
+ cls.images_client = cls.os.images_client
cls.config = cls.os.config
cls.image_ref = cls.config.env.image_ref
cls.flavor_ref = cls.config.env.flavor_ref
@@ -28,6 +31,24 @@
else:
cls.image_ref_alt = cls.image_ref
+ # Do some sanity checks here. If one of the images does
+ # not exist, or image_ref and image_ref_alt are the same,
+ # fail early since the tests won't work...
+ if cls.image_ref != cls.image_ref_alt:
+ cls.image_ref_alt_different = True
+
+ try:
+ cls.images_client.get_image(cls.image_ref)
+ except exceptions.NotFound:
+ raise RuntimeError("Image %s (image_ref) was not found!" %
+ cls.image_ref)
+
+ try:
+ cls.images_client.get_image(cls.image_ref_alt)
+ except exceptions.NotFound:
+ raise RuntimeError("Image %s (image_ref_alt) was not found!" %
+ cls.image_ref_alt)
+
cls.s1_name = rand_name('server')
resp, server = cls.client.create_server(cls.s1_name, cls.image_ref,
cls.flavor_ref)