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
This commit is contained in:
parent
de78df9020
commit
72e2e31fea
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
*.swp
|
||||||
*.log
|
*.log
|
||||||
.glance-venv
|
.glance-venv
|
||||||
tests.sqlite
|
tests.sqlite
|
||||||
|
@ -252,6 +252,8 @@ class FunctionalTest(unittest.TestCase):
|
|||||||
servers and clients and not just the stubbed out interfaces
|
servers and clients and not just the stubbed out interfaces
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
disabled = False
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
self.test_id = random.randint(0, 100000)
|
self.test_id = random.randint(0, 100000)
|
||||||
@ -276,6 +278,7 @@ class FunctionalTest(unittest.TestCase):
|
|||||||
self.files_to_destroy = []
|
self.files_to_destroy = []
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
if not self.disabled:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
# We destroy the test data store between each test case,
|
# We destroy the test data store between each test case,
|
||||||
# and recreate it, which ensures that we have no side-effects
|
# and recreate it, which ensures that we have no side-effects
|
||||||
|
@ -24,16 +24,17 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from glance.tests import functional
|
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_KB = 5 * 1024
|
||||||
FIVE_GB = 5 * 1024 * 1024 * 1024
|
FIVE_GB = 5 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
class TestApiHttplib2(functional.FunctionalTest):
|
class TestApi(functional.FunctionalTest):
|
||||||
|
|
||||||
"""Functional tests using httplib2 against the API server"""
|
"""Functional tests using httplib2 against the API server"""
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_get_head_simple_post(self):
|
def test_get_head_simple_post(self):
|
||||||
"""
|
"""
|
||||||
We test the following sequential series of actions:
|
We test the following sequential series of actions:
|
||||||
@ -273,6 +274,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_queued_process_flow(self):
|
def test_queued_process_flow(self):
|
||||||
"""
|
"""
|
||||||
We test the process flow where a user registers an image
|
We test the process flow where a user registers an image
|
||||||
@ -391,6 +393,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_version_variations(self):
|
def test_version_variations(self):
|
||||||
"""
|
"""
|
||||||
We test that various calls to the images and root endpoints are
|
We test that various calls to the images and root endpoints are
|
||||||
@ -549,6 +552,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_size_greater_2G_mysql(self):
|
def test_size_greater_2G_mysql(self):
|
||||||
"""
|
"""
|
||||||
A test against the actual datastore backend for the registry
|
A test against the actual datastore backend for the registry
|
||||||
@ -592,6 +596,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_traceback_not_consumed(self):
|
def test_traceback_not_consumed(self):
|
||||||
"""
|
"""
|
||||||
A test that errors coming from the POST API do not
|
A test that errors coming from the POST API do not
|
||||||
@ -620,6 +625,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_filtered_images(self):
|
def test_filtered_images(self):
|
||||||
"""
|
"""
|
||||||
Set up four test images and ensure each query param filter works
|
Set up four test images and ensure each query param filter works
|
||||||
@ -852,6 +858,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_limited_images(self):
|
def test_limited_images(self):
|
||||||
"""
|
"""
|
||||||
Ensure marker and limit query params work
|
Ensure marker and limit query params work
|
||||||
@ -940,6 +947,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_ordered_images(self):
|
def test_ordered_images(self):
|
||||||
"""
|
"""
|
||||||
Set up three test images and ensure each query param filter works
|
Set up three test images and ensure each query param filter works
|
||||||
@ -1050,6 +1058,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
@skip_if_disabled
|
||||||
def test_duplicate_image_upload(self):
|
def test_duplicate_image_upload(self):
|
||||||
"""
|
"""
|
||||||
Upload initial image, then attempt to upload duplicate image
|
Upload initial image, then attempt to upload duplicate image
|
||||||
|
@ -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
|
|
@ -36,23 +36,21 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from glance.tests import functional
|
from glance.tests.functional import test_api
|
||||||
from glance.tests.utils import execute
|
from glance.tests.utils import execute
|
||||||
|
from glance.tests import utils
|
||||||
|
|
||||||
FIVE_KB = 5 * 1024
|
FIVE_KB = 5 * 1024
|
||||||
FIVE_GB = 5 * 1024 * 1024 * 1024
|
FIVE_GB = 5 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
class TestS3(functional.FunctionalTest):
|
class TestS3(test_api.TestApi):
|
||||||
|
|
||||||
"""Functional tests for the S3 backend"""
|
"""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
|
# to override the location of the config file for migration testing
|
||||||
CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF',
|
CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF')
|
||||||
os.path.join('glance', 'tests',
|
|
||||||
'functional',
|
|
||||||
'test_s3.conf'))
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""
|
"""
|
||||||
@ -61,11 +59,16 @@ class TestS3(functional.FunctionalTest):
|
|||||||
If the connection fails, mark all tests to skip.
|
If the connection fails, mark all tests to skip.
|
||||||
"""
|
"""
|
||||||
self.inited = False
|
self.inited = False
|
||||||
self.skip_tests = True
|
self.disabled = True
|
||||||
|
|
||||||
if self.inited:
|
if self.inited:
|
||||||
return
|
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):
|
if os.path.exists(TestS3.CONFIG_FILE_PATH):
|
||||||
cp = ConfigParser.RawConfigParser()
|
cp = ConfigParser.RawConfigParser()
|
||||||
try:
|
try:
|
||||||
@ -74,8 +77,8 @@ class TestS3(functional.FunctionalTest):
|
|||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
self.__dict__[key] = value
|
self.__dict__[key] = value
|
||||||
except ConfigParser.ParsingError, e:
|
except ConfigParser.ParsingError, e:
|
||||||
print ("Failed to read test_s3.conf config file. "
|
self.disabled_message = ("Failed to read test_s3.conf config "
|
||||||
"Got error: %s" % e)
|
"file. Got error: %s" % e)
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
self.inited = True
|
self.inited = True
|
||||||
return
|
return
|
||||||
@ -89,8 +92,8 @@ class TestS3(functional.FunctionalTest):
|
|||||||
secret_key = self.s3_store_secret_key
|
secret_key = self.s3_store_secret_key
|
||||||
bucket_name = self.s3_store_bucket
|
bucket_name = self.s3_store_bucket
|
||||||
except AttributeError, e:
|
except AttributeError, e:
|
||||||
print ("Failed to find required configuration options for "
|
self.disabled_message = ("Failed to find required configuration "
|
||||||
"S3 store. Got error: %s" % e)
|
"options for S3 store. Got error: %s" % e)
|
||||||
self.inited = True
|
self.inited = True
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
return
|
return
|
||||||
@ -104,16 +107,17 @@ class TestS3(functional.FunctionalTest):
|
|||||||
if bucket.name == bucket_name:
|
if bucket.name == bucket_name:
|
||||||
self.bucket = bucket
|
self.bucket = bucket
|
||||||
except S3ResponseError, e:
|
except S3ResponseError, e:
|
||||||
print ("Failed to connect to S3 with credentials,"
|
self.disabled_message = ("Failed to connect to S3 with "
|
||||||
"to find bucket. Got error: %s" % e)
|
"credentials, to find bucket. "
|
||||||
|
"Got error: %s" % e)
|
||||||
self.inited = True
|
self.inited = True
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
return
|
return
|
||||||
except TypeError, e:
|
except TypeError, e:
|
||||||
# This hack is necessary because of a bug in boto 1.9b:
|
# This hack is necessary because of a bug in boto 1.9b:
|
||||||
# http://code.google.com/p/boto/issues/detail?id=540
|
# http://code.google.com/p/boto/issues/detail?id=540
|
||||||
print ("Failed to connect to S3 with credentials. "
|
self.disabled_message = ("Failed to connect to S3 with "
|
||||||
"Got error: %s" % e)
|
"credentials. Got error: %s" % e)
|
||||||
self.inited = True
|
self.inited = True
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
return
|
return
|
||||||
@ -124,21 +128,22 @@ class TestS3(functional.FunctionalTest):
|
|||||||
try:
|
try:
|
||||||
self.bucket = s3_conn.create_bucket(bucket_name)
|
self.bucket = s3_conn.create_bucket(bucket_name)
|
||||||
except boto.exception.S3ResponseError, e:
|
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
|
self.inited = True
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.clear_bucket()
|
self.clear_bucket()
|
||||||
|
|
||||||
self.skip_tests = False
|
self.disabled = False
|
||||||
self.inited = True
|
self.inited = True
|
||||||
self.default_store = 's3'
|
self.default_store = 's3'
|
||||||
|
|
||||||
super(TestS3, self).setUp()
|
super(TestS3, self).setUp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if not self.skip_tests:
|
if not self.disabled:
|
||||||
self.clear_bucket()
|
self.clear_bucket()
|
||||||
super(TestS3, self).tearDown()
|
super(TestS3, self).tearDown()
|
||||||
|
|
||||||
@ -150,346 +155,7 @@ class TestS3(functional.FunctionalTest):
|
|||||||
for key in keys:
|
for key in keys:
|
||||||
key.delete()
|
key.delete()
|
||||||
|
|
||||||
def test_add_list_delete_list(self):
|
@utils.skip_if_disabled
|
||||||
"""
|
|
||||||
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()
|
|
||||||
|
|
||||||
def test_delete_not_existing(self):
|
def test_delete_not_existing(self):
|
||||||
"""
|
"""
|
||||||
We test the following:
|
We test the following:
|
||||||
@ -499,9 +165,6 @@ class TestS3(functional.FunctionalTest):
|
|||||||
1. DELETE /images/1
|
1. DELETE /images/1
|
||||||
- Verify 404
|
- Verify 404
|
||||||
"""
|
"""
|
||||||
if self.skip_tests:
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers(**self.__dict__.copy())
|
self.start_servers(**self.__dict__.copy())
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
"""Common utilities used in testing"""
|
"""Common utilities used in testing"""
|
||||||
|
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -39,7 +40,7 @@ class skip_test(object):
|
|||||||
|
|
||||||
|
|
||||||
class skip_if(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):
|
def __init__(self, condition, msg):
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
self.message = msg
|
self.message = msg
|
||||||
@ -72,6 +73,20 @@ class skip_unless(object):
|
|||||||
return _skipper
|
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):
|
def execute(cmd, raise_error=True):
|
||||||
"""
|
"""
|
||||||
Executes a command in a subprocess. Returns a tuple
|
Executes a command in a subprocess. Returns a tuple
|
||||||
|
Loading…
Reference in New Issue
Block a user