From 72e2e31fea41bc3c16083cce6abea51c028f259c Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Mon, 8 Aug 2011 12:56:30 -0400 Subject: [PATCH] Add @skip_if_disabled decorator to test.utils and integrate it into the base functional API test case. The S3 functional test case now uses test_api.TestApi as its base class and the setUp() method sets the disabled and disabled_message attributes that the @skip_if_disabled decorator uses. The S3 test case now tests all the methods that are tested for the filesystem store driver in test_api.TestApi Removed the test.functional.test_s3.conf file as it was no longer needed. Change-Id: I308d7a655c8c26339d0e2560634421935bbc8b5d --- .gitignore | 1 + glance/tests/functional/__init__.py | 13 +- glance/tests/functional/test_api.py | 13 +- glance/tests/functional/test_s3.conf | 21 -- glance/tests/functional/test_s3.py | 387 ++------------------------- glance/tests/utils.py | 17 +- 6 files changed, 61 insertions(+), 391 deletions(-) delete mode 100644 glance/tests/functional/test_s3.conf diff --git a/.gitignore b/.gitignore index 505ea84f20..410f8fc5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +*.swp *.log .glance-venv tests.sqlite diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py index 34645a41af..6c6d84e856 100644 --- a/glance/tests/functional/__init__.py +++ b/glance/tests/functional/__init__.py @@ -252,6 +252,8 @@ class FunctionalTest(unittest.TestCase): servers and clients and not just the stubbed out interfaces """ + disabled = False + def setUp(self): self.test_id = random.randint(0, 100000) @@ -276,11 +278,12 @@ class FunctionalTest(unittest.TestCase): self.files_to_destroy = [] def tearDown(self): - self.cleanup() - # We destroy the test data store between each test case, - # and recreate it, which ensures that we have no side-effects - # from the tests - self._reset_database() + if not self.disabled: + self.cleanup() + # We destroy the test data store between each test case, + # and recreate it, which ensures that we have no side-effects + # from the tests + self._reset_database() def _reset_database(self): conn_string = self.registry_server.sql_connection diff --git a/glance/tests/functional/test_api.py b/glance/tests/functional/test_api.py index cab92f8fc0..f204074fe2 100644 --- a/glance/tests/functional/test_api.py +++ b/glance/tests/functional/test_api.py @@ -24,16 +24,17 @@ import os import tempfile from glance.tests import functional -from glance.tests.utils import execute +from glance.tests.utils import execute, skip_if_disabled FIVE_KB = 5 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024 -class TestApiHttplib2(functional.FunctionalTest): +class TestApi(functional.FunctionalTest): """Functional tests using httplib2 against the API server""" + @skip_if_disabled def test_get_head_simple_post(self): """ We test the following sequential series of actions: @@ -273,6 +274,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_queued_process_flow(self): """ We test the process flow where a user registers an image @@ -391,6 +393,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_version_variations(self): """ We test that various calls to the images and root endpoints are @@ -549,6 +552,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_size_greater_2G_mysql(self): """ A test against the actual datastore backend for the registry @@ -592,6 +596,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_traceback_not_consumed(self): """ A test that errors coming from the POST API do not @@ -620,6 +625,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_filtered_images(self): """ Set up four test images and ensure each query param filter works @@ -852,6 +858,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_limited_images(self): """ Ensure marker and limit query params work @@ -940,6 +947,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_ordered_images(self): """ Set up three test images and ensure each query param filter works @@ -1050,6 +1058,7 @@ class TestApiHttplib2(functional.FunctionalTest): self.stop_servers() + @skip_if_disabled def test_duplicate_image_upload(self): """ Upload initial image, then attempt to upload duplicate image diff --git a/glance/tests/functional/test_s3.conf b/glance/tests/functional/test_s3.conf deleted file mode 100644 index 0df63af61b..0000000000 --- a/glance/tests/functional/test_s3.conf +++ /dev/null @@ -1,21 +0,0 @@ -[DEFAULT] - -# Set the following to Amazon S3 credentials that you want -# to use while functional testing the S3 backend. - -# Address where the S3 authentication service lives -s3_store_host = s3.amazonaws.com - -# User to authenticate against the S3 authentication service -s3_store_access_key = <20-char AWS access key> - -# Auth key for the user authenticating against the -# S3 authentication service -s3_store_secret_key = <40-char AWS secret key> - -# Container within the account that the account should use -# for storing images in S3. Note that S3 has a flat namespace, -# so you need a unique bucket name for your glance images. An -# easy way to do this is append your lower-cased AWS access key -# to "glance" -s3_store_bucket = <20-char AWS access key - lowercased>glance diff --git a/glance/tests/functional/test_s3.py b/glance/tests/functional/test_s3.py index 27b44c6765..1ab68c3d91 100644 --- a/glance/tests/functional/test_s3.py +++ b/glance/tests/functional/test_s3.py @@ -36,23 +36,21 @@ import os import tempfile import unittest -from glance.tests import functional +from glance.tests.functional import test_api from glance.tests.utils import execute +from glance.tests import utils FIVE_KB = 5 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024 -class TestS3(functional.FunctionalTest): +class TestS3(test_api.TestApi): """Functional tests for the S3 backend""" - # Test machines can set the GLANCE_TEST_MIGRATIONS_CONF variable + # Test machines can set the GLANCE_TEST_S3_CONF variable # to override the location of the config file for migration testing - CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF', - os.path.join('glance', 'tests', - 'functional', - 'test_s3.conf')) + CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF') def setUp(self): """ @@ -61,11 +59,16 @@ class TestS3(functional.FunctionalTest): If the connection fails, mark all tests to skip. """ self.inited = False - self.skip_tests = True + self.disabled = True if self.inited: return + if not self.CONFIG_FILE_PATH: + self.disabled_message = "GLANCE_TEST_S3_CONF environ not set." + self.inited = True + return + if os.path.exists(TestS3.CONFIG_FILE_PATH): cp = ConfigParser.RawConfigParser() try: @@ -74,8 +77,8 @@ class TestS3(functional.FunctionalTest): for key, value in defaults.items(): self.__dict__[key] = value except ConfigParser.ParsingError, e: - print ("Failed to read test_s3.conf config file. " - "Got error: %s" % e) + self.disabled_message = ("Failed to read test_s3.conf config " + "file. Got error: %s" % e) super(TestS3, self).setUp() self.inited = True return @@ -89,8 +92,8 @@ class TestS3(functional.FunctionalTest): secret_key = self.s3_store_secret_key bucket_name = self.s3_store_bucket except AttributeError, e: - print ("Failed to find required configuration options for " - "S3 store. Got error: %s" % e) + self.disabled_message = ("Failed to find required configuration " + "options for S3 store. Got error: %s" % e) self.inited = True super(TestS3, self).setUp() return @@ -104,16 +107,17 @@ class TestS3(functional.FunctionalTest): if bucket.name == bucket_name: self.bucket = bucket except S3ResponseError, e: - print ("Failed to connect to S3 with credentials," - "to find bucket. Got error: %s" % e) + self.disabled_message = ("Failed to connect to S3 with " + "credentials, to find bucket. " + "Got error: %s" % e) self.inited = True super(TestS3, self).setUp() return except TypeError, e: # This hack is necessary because of a bug in boto 1.9b: # http://code.google.com/p/boto/issues/detail?id=540 - print ("Failed to connect to S3 with credentials. " - "Got error: %s" % e) + self.disabled_message = ("Failed to connect to S3 with " + "credentials. Got error: %s" % e) self.inited = True super(TestS3, self).setUp() return @@ -124,21 +128,22 @@ class TestS3(functional.FunctionalTest): try: self.bucket = s3_conn.create_bucket(bucket_name) except boto.exception.S3ResponseError, e: - print ("Failed to create bucket. Got error: %s" % e) + self.disabled_message = ("Failed to create bucket. " + "Got error: %s" % e) self.inited = True super(TestS3, self).setUp() return else: self.clear_bucket() - self.skip_tests = False + self.disabled = False self.inited = True self.default_store = 's3' super(TestS3, self).setUp() def tearDown(self): - if not self.skip_tests: + if not self.disabled: self.clear_bucket() super(TestS3, self).tearDown() @@ -150,346 +155,7 @@ class TestS3(functional.FunctionalTest): for key in keys: key.delete() - def test_add_list_delete_list(self): - """ - We test the following: - - 0. GET /images - - Verify no public images - 1. GET /images/detail - - Verify no public images - 2. HEAD /images/1 - - Verify 404 returned - 3. POST /images with public image named Image1 with a location - attribute and no custom properties - - Verify 201 returned - 4. HEAD /images/1 - - Verify HTTP headers have correct information we just added - 5. GET /images/1 - - Verify all information on image we just added is correct - 6. GET /images - - Verify the image we just added is returned - 7. GET /images/detail - - Verify the image we just added is returned - 8. PUT /images/1 with custom properties of "distro" and "arch" - - Verify 200 returned - 9. GET /images/1 - - Verify updated information about image was stored - 10. PUT /images/1 - - Remove a previously existing property. - 11. PUT /images/1 - - Add a previously deleted property. - """ - if self.skip_tests: - return True - - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. GET /images - # Verify no public images - cmd = "curl http://0.0.0.0:%d/v1/images" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('{"images": []}', out.strip()) - - # 1. GET /images/detail - # Verify no public images - cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('{"images": []}', out.strip()) - - # 2. HEAD /images/1 - # Verify 404 returned - cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 404 Not Found", status_line) - - # 3. POST /images with public image named Image1 - # attribute and no custom properties. Verify a 200 OK is returned - image_data = "*" * FIVE_KB - - cmd = ("curl -i -X POST " - "-H 'Expect: ' " # Necessary otherwise sends 100 Continue - "-H 'Content-Type: application/octet-stream' " - "-H 'X-Image-Meta-Name: Image1' " - "-H 'X-Image-Meta-Is-Public: True' " - "--data-binary \"%s\" " - "http://0.0.0.0:%d/v1/images") % (image_data, api_port) - - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 201 Created", status_line, out) - - # 4. HEAD /images - # Verify image found now - cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 200 OK", status_line) - self.assertTrue("X-Image-Meta-Name: Image1" in out) - - # 5. GET /images/1 - # Verify all information on image we just added is correct - - cmd = "curl -i http://0.0.0.0:%d/v1/images/1" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - - self.assertEqual("HTTP/1.1 200 OK", lines.pop(0)) - - # Handle the headers - image_headers = {} - std_headers = {} - other_lines = [] - for line in lines: - if line.strip() == '': - continue - if line.startswith("X-Image"): - pieces = line.split(":") - key = pieces[0].strip() - value = ":".join(pieces[1:]).strip() - image_headers[key] = value - elif ':' in line: - pieces = line.split(":") - key = pieces[0].strip() - value = ":".join(pieces[1:]).strip() - std_headers[key] = value - else: - other_lines.append(line) - - expected_image_headers = { - 'X-Image-Meta-Id': '1', - 'X-Image-Meta-Name': 'Image1', - 'X-Image-Meta-Is_public': 'True', - 'X-Image-Meta-Status': 'active', - 'X-Image-Meta-Disk_format': '', - 'X-Image-Meta-Container_format': '', - 'X-Image-Meta-Size': str(FIVE_KB), - 'X-Image-Meta-Location': 's3://%s:%s@%s/%s/1' % ( - self.s3_store_access_key, - self.s3_store_secret_key, - self.s3_store_host, - self.s3_store_bucket)} - - expected_std_headers = { - 'Content-Length': str(FIVE_KB), - 'Content-Type': 'application/octet-stream'} - - for expected_key, expected_value in expected_image_headers.items(): - self.assertTrue(expected_key in image_headers, - "Failed to find key %s in image_headers" - % expected_key) - self.assertEqual(expected_value, image_headers[expected_key], - "For key '%s' expected header value '%s'. Got '%s'" - % (expected_key, - expected_value, - image_headers[expected_key])) - - for expected_key, expected_value in expected_std_headers.items(): - self.assertTrue(expected_key in std_headers, - "Failed to find key %s in std_headers" - % expected_key) - self.assertEqual(expected_value, std_headers[expected_key], - "For key '%s' expected header value '%s'. Got '%s'" - % (expected_key, - expected_value, - std_headers[expected_key])) - - # Now the image data... - expected_image_data = "*" * FIVE_KB - - # Should only be a single "line" left, and - # that's the image data - self.assertEqual(1, len(other_lines)) - self.assertEqual(expected_image_data, other_lines[0]) - - # 6. GET /images - # Verify no public images - cmd = "curl http://0.0.0.0:%d/v1/images" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - expected_result = {"images": [ - {"container_format": None, - "disk_format": None, - "id": 1, - "name": "Image1", - "checksum": "c2e5db72bd7fd153f53ede5da5a06de3", - "size": 5120}]} - self.assertEqual(expected_result, json.loads(out.strip())) - - # 7. GET /images/detail - # Verify image and all its metadata - cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - expected_image = { - "status": "active", - "name": "Image1", - "deleted": False, - "container_format": None, - "disk_format": None, - "id": 1, - 'location': 's3://%s:%s@%s/%s/1' % ( - self.s3_store_access_key, - self.s3_store_secret_key, - self.s3_store_host, - self.s3_store_bucket), - "is_public": True, - "deleted_at": None, - "properties": {}, - "size": 5120} - - image = json.loads(out.strip())['images'][0] - - for expected_key, expected_value in expected_image.items(): - self.assertTrue(expected_key in image, - "Failed to find key %s in image" - % expected_key) - self.assertEqual(expected_value, expected_image[expected_key], - "For key '%s' expected header value '%s'. Got '%s'" - % (expected_key, - expected_value, - image[expected_key])) - - # 8. PUT /images/1 with custom properties of "distro" and "arch" - # Verify 200 returned - - cmd = ("curl -i -X PUT " - "-H 'X-Image-Meta-Property-Distro: Ubuntu' " - "-H 'X-Image-Meta-Property-Arch: x86_64' " - "http://0.0.0.0:%d/v1/images/1") % api_port - - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 200 OK", status_line) - - # 9. GET /images/detail - # Verify image and all its metadata - cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - expected_image = { - "status": "active", - "name": "Image1", - "deleted": False, - "container_format": None, - "disk_format": None, - "id": 1, - 'location': 's3://%s:%s@%s/%s/1' % ( - self.s3_store_access_key, - self.s3_store_secret_key, - self.s3_store_host, - self.s3_store_bucket), - "is_public": True, - "deleted_at": None, - "properties": {'distro': 'Ubuntu', 'arch': 'x86_64'}, - "size": 5120} - - image = json.loads(out.strip())['images'][0] - - for expected_key, expected_value in expected_image.items(): - self.assertTrue(expected_key in image, - "Failed to find key %s in image" - % expected_key) - self.assertEqual(expected_value, image[expected_key], - "For key '%s' expected header value '%s'. Got '%s'" - % (expected_key, - expected_value, - image[expected_key])) - - # 10. PUT /images/1 and remove a previously existing property. - cmd = ("curl -i -X PUT " - "-H 'X-Image-Meta-Property-Arch: x86_64' " - "http://0.0.0.0:%d/v1/images/1") % api_port - - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 200 OK", status_line) - - cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - image = json.loads(out.strip())['images'][0] - self.assertEqual(1, len(image['properties'])) - self.assertEqual('x86_64', image['properties']['arch']) - - # 11. PUT /images/1 and add a previously deleted property. - cmd = ("curl -i -X PUT " - "-H 'X-Image-Meta-Property-Distro: Ubuntu' " - "-H 'X-Image-Meta-Property-Arch: x86_64' " - "http://0.0.0.0:%d/v1/images/1") % api_port - - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - lines = out.split("\r\n") - status_line = lines[0] - - self.assertEqual("HTTP/1.1 200 OK", status_line) - - cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - image = json.loads(out.strip())['images'][0] - self.assertEqual(2, len(image['properties'])) - self.assertEqual('x86_64', image['properties']['arch']) - self.assertEqual('Ubuntu', image['properties']['distro']) - - self.stop_servers() - + @utils.skip_if_disabled def test_delete_not_existing(self): """ We test the following: @@ -499,9 +165,6 @@ class TestS3(functional.FunctionalTest): 1. DELETE /images/1 - Verify 404 """ - if self.skip_tests: - return True - self.cleanup() self.start_servers(**self.__dict__.copy()) diff --git a/glance/tests/utils.py b/glance/tests/utils.py index 897937cffb..4f1d93febb 100644 --- a/glance/tests/utils.py +++ b/glance/tests/utils.py @@ -17,6 +17,7 @@ """Common utilities used in testing""" +import functools import os import socket import subprocess @@ -39,7 +40,7 @@ class skip_test(object): class skip_if(object): - """Decorator that skips a test if contition is true.""" + """Decorator that skips a test if condition is true.""" def __init__(self, condition, msg): self.condition = condition self.message = msg @@ -72,6 +73,20 @@ class skip_unless(object): return _skipper +def skip_if_disabled(func): + """Decorator that skips a test if test case is disabled.""" + @functools.wraps(func) + def wrapped(*a, **kwargs): + func.__test__ = False + test_obj = a[0] + message = getattr(test_obj, 'disabled_message', + 'Test disabled') + if test_obj.disabled: + raise nose.SkipTest(message) + func(*a, **kwargs) + return wrapped + + def execute(cmd, raise_error=True): """ Executes a command in a subprocess. Returns a tuple