Require container & disk formats on image create

Fixes lp 933702

For images created via the glance CLI, the container and disk formats
were previously defaulted if not explicitly set. However if created via
the python or REST APIs, these attributes were not defaulted if unset.

There is no real sensible default for these formats, so now an image
create fails with 400 "Bad Request" if the format metadata are missing.

Also we ensure unset image metadata are not reported in x-image-meta-*
headers in order to disambiguate None and empty string values.

Change-Id: I8189383f5f9adf42a8cdac7f8dc7e9327baf46da
This commit is contained in:
Eoghan Glynn 2012-02-17 13:46:49 +00:00
parent f8f9f17112
commit 62c913c3ad
17 changed files with 291 additions and 170 deletions

View File

@ -141,7 +141,7 @@ def print_image_formatted(client, image):
print "Container format: %s" % image['container_format'] print "Container format: %s" % image['container_format']
print "Minimum Ram Required (MB): %s" % image['min_ram'] print "Minimum Ram Required (MB): %s" % image['min_ram']
print "Minimum Disk Required (GB): %s" % image['min_disk'] print "Minimum Disk Required (GB): %s" % image['min_disk']
if image['owner']: if image.get('owner'):
print "Owner: %s" % image['owner'] print "Owner: %s" % image['owner']
if len(image['properties']) > 0: if len(image['properties']) > 0:
for k, v in image['properties'].items(): for k, v in image['properties'].items():
@ -173,10 +173,9 @@ is_public Optional. If specified, interpreted as a boolean value
protected Optional. If specified, interpreted as a boolean value protected Optional. If specified, interpreted as a boolean value
and enables or disables deletion protection. and enables or disables deletion protection.
The default value is False. The default value is False.
disk_format Optional. Possible values are 'vhd','vmdk','raw', 'qcow2', disk_format Required. Possible values are 'vhd','vmdk','raw', 'qcow2',
and 'ami'. Default value is 'raw'. and 'ami'.
container_format Optional. Possible values are 'ovf' and 'ami'. container_format Required. Possible values are 'ovf' and 'ami'.
Default value is 'ovf'.
location Optional. When specified, should be a readable location location Optional. When specified, should be a readable location
in the form of a URI: $STORE://LOCATION. For example, if in the form of a URI: $STORE://LOCATION. For example, if
the image data is stored in a file on the local the image data is stored in a file on the local
@ -202,7 +201,8 @@ EXAMPLES
location=http://images.ubuntu.org/images/lucid-10.04-i686.iso \\ location=http://images.ubuntu.org/images/lucid-10.04-i686.iso \\
distro="Ubuntu 10.04 LTS" distro="Ubuntu 10.04 LTS"
%(prog)s add name="My Image" distro="Ubuntu 10.04 LTS" < /tmp/myimage.iso""" %(prog)s add name="My Image" disk_format=raw container_format=ovf \\
distro="Ubuntu 10.04 LTS" < /tmp/myimage.iso"""
c = get_client(options) c = get_client(options)
try: try:
@ -220,10 +220,13 @@ EXAMPLES
fields.pop('is_public', False)), fields.pop('is_public', False)),
'protected': utils.bool_from_string( 'protected': utils.bool_from_string(
fields.pop('protected', False)), fields.pop('protected', False)),
'disk_format': fields.pop('disk_format', 'raw'),
'min_disk': fields.pop('min_disk', 0), 'min_disk': fields.pop('min_disk', 0),
'min_ram': fields.pop('min_ram', 0), 'min_ram': fields.pop('min_ram', 0),
'container_format': fields.pop('container_format', 'ovf')} }
for format in ['disk_format', 'container_format']:
if format in fields:
image_meta[format] = fields.pop(format)
# Strip any args that are not supported # Strip any args that are not supported
unsupported_fields = ['status', 'size'] unsupported_fields = ['status', 'size']

View File

@ -131,8 +131,8 @@ like so::
name A name for the image. name A name for the image.
is_public If specified, interpreted as a boolean value is_public If specified, interpreted as a boolean value
and sets or unsets the image's availability to the public. and sets or unsets the image's availability to the public.
disk_format Format of the disk image disk_format Format of the disk image (required)
container_format Format of the container container_format Format of the container (required)
All other field names are considered to be custom properties so be careful All other field names are considered to be custom properties so be careful
to spell field names correctly. :) to spell field names correctly. :)
@ -186,21 +186,27 @@ that the image should be public -- anyone should be able to fetch it.
Here is how we'd upload this image to Glance. Change example IP number to your Here is how we'd upload this image to Glance. Change example IP number to your
server IP number.:: server IP number.::
$> glance add name="My Image" is_public=true < /tmp/images/myimage.iso \ $> glance add name="My Image" is_public=true \
--host=65.114.169.29 container_format=ovf disk_format=raw \
--host=65.114.169.29 < /tmp/images/myimage.iso
Note that the disk container formats are no longer defaulted and are thus
strictly required.
If Glance was able to successfully upload and store your VM image data and If Glance was able to successfully upload and store your VM image data and
metadata attributes, you would see something like this:: metadata attributes, you would see something like this::
$> glance add name="My Image" is_public=true < /tmp/images/myimage.iso \ $> glance add name="My Image" is_public=true \
--host=65.114.169.29 container_format=ovf disk_format=raw \
--host=65.114.169.29 < /tmp/images/myimage.iso
Added new image with ID: 991baaf9-cc0d-4183-a201-8facdf1a1430 Added new image with ID: 991baaf9-cc0d-4183-a201-8facdf1a1430
You can use the ``--verbose`` (or ``-v``) command-line option to print some more You can use the ``--verbose`` (or ``-v``) command-line option to print some more
information about the metadata that was saved with the image:: information about the metadata that was saved with the image::
$> glance --verbose add name="My Image" is_public=true < \ $> glance --verbose add name="My Image" is_public=true \
/tmp/images/myimage.iso --host=65.114.169.29 container_format=ovf disk_format=raw \
--host=65.114.169.29 < /tmp/images/myimage.iso
Added new image with ID: 541424be-27b1-49d6-a55b-6430b8ae0f5f Added new image with ID: 541424be-27b1-49d6-a55b-6430b8ae0f5f
Returned the following metadata for the new image: Returned the following metadata for the new image:
container_format => ovf container_format => ovf
@ -221,8 +227,9 @@ information about the metadata that was saved with the image::
If you are unsure about what will be added, you can use the ``--dry-run`` If you are unsure about what will be added, you can use the ``--dry-run``
command-line option, which will simply show you what *would* have happened:: command-line option, which will simply show you what *would* have happened::
$> glance --dry-run add name="Foo" distro="Ubuntu" is_public=True < \ $> glance --dry-run add name="Foo" distro="Ubuntu" is_public=True \
/tmp/images/myimage.iso --host=65.114.169.29 container_format=ovf disk_format=raw \
--host=65.114.169.29 < /tmp/images/myimage.iso
Dry run. We would have done the following: Dry run. We would have done the following:
Add new image with metadata: Add new image with metadata:
container_format => ovf container_format => ovf
@ -243,42 +250,46 @@ Examples of uploading different kinds of images
To upload an EC2 tarball VM image:: To upload an EC2 tarball VM image::
$> glance add name="ubuntu-10.10-amd64" is_public=true < \ $> glance add name="ubuntu-10.10-amd64" is_public=true \
/root/maverick-server-uec-amd64.tar.gz container_format=ovf disk_format=raw \
< /root/maverick-server-uec-amd64.tar.gz
To upload an EC2 tarball VM image with an associated property (e.g., distro):: To upload an EC2 tarball VM image with an associated property (e.g., distro)::
$> glance add name="ubuntu-10.10-amd64" is_public=true \ $> glance add name="ubuntu-10.10-amd64" is_public=true \
container_format=ovf disk_format=raw \
distro="ubuntu 10.10" < /root/maverick-server-uec-amd64.tar.gz distro="ubuntu 10.10" < /root/maverick-server-uec-amd64.tar.gz
To upload an EC2 tarball VM image from a URL:: To upload an EC2 tarball VM image from a URL::
$> glance add name="uubntu-10.04-amd64" is_public=true \ $> glance add name="uubntu-10.04-amd64" is_public=true \
container_format=ovf disk_format=raw \
location="http://uec-images.ubuntu.com/lucid/current/\ location="http://uec-images.ubuntu.com/lucid/current/\
lucid-server-uec-amd64.tar.gz" lucid-server-uec-amd64.tar.gz"
To upload a qcow2 image:: To upload a qcow2 image::
$> glance add name="ubuntu-11.04-amd64" is_public=true \ $> glance add name="ubuntu-11.04-amd64" is_public=true \
distro="ubuntu 11.04" disk_format="qcow2" < /data/images/rock_natty.qcow2 container_format=ovf disk_format=qcow2 \
distro="ubuntu 11.04" < /data/images/rock_natty.qcow2
To upload a kernel file, ramdisk file and filesystem image file:: To upload a kernel file, ramdisk file and filesystem image file::
$> glance add --disk-format=aki --container-format=aki \ $> glance add disk_format=aki container_format=aki \
./maverick-server-uec-amd64-vmlinuz-virtual \ ./maverick-server-uec-amd64-vmlinuz-virtual \
maverick-server-uec-amd64-vmlinuz-virtual maverick-server-uec-amd64-vmlinuz-virtual
$> glance add --disk-format=ari --container-format=ari \ $> glance add disk_format=ari container_format=ari \
./maverick-server-uec-amd64-loader maverick-server-uec-amd64-loader ./maverick-server-uec-amd64-loader maverick-server-uec-amd64-loader
# Determine what the ids associated with the kernel and ramdisk files # Determine what the ids associated with the kernel and ramdisk files
$> glance index $> glance index
# Assuming the ids are 7 and 8: # Assuming the ids are 7 and 8:
$> glance add --disk-format=ami --container-format=ami --kernel=7 \ $> glance add disk_format=ami container_format=ami --kernel=7 \
--ramdisk=8 ./maverick-server-uec-amd64.img maverick-server-uec-amd64.img --ramdisk=8 ./maverick-server-uec-amd64.img maverick-server-uec-amd64.img
To upload a raw image file:: To upload a raw image file::
$> glance add --disk_format=raw ./maverick-server-uec-amd64.img \ $> glance add disk_format=raw container_format=ovf \
maverick-server-uec-amd64.img_v2 ./maverick-server-uec-amd64.img maverick-server-uec-amd64.img_v2
Register a virtual machine image in another location Register a virtual machine image in another location

View File

@ -181,8 +181,8 @@ following shows an example of the HTTP headers returned from the above
x-image-meta-uri http://glance.example.com/images/71c675ab-d94f-49cd-a114-e12490b328d9 x-image-meta-uri http://glance.example.com/images/71c675ab-d94f-49cd-a114-e12490b328d9
x-image-meta-name Ubuntu 10.04 Plain 5GB x-image-meta-name Ubuntu 10.04 Plain 5GB
x-image-meta-disk-format vhd x-image-meta-disk_format vhd
x-image-meta-container-format ovf x-image-meta-container_format ovf
x-image-meta-size 5368709120 x-image-meta-size 5368709120
x-image-meta-checksum c2e5db72bd7fd153f53ede5da5a06de3 x-image-meta-checksum c2e5db72bd7fd153f53ede5da5a06de3
x-image-meta-created_at 2010-02-03 09:34:01 x-image-meta-created_at 2010-02-03 09:34:01
@ -244,8 +244,8 @@ returned from the above ``GET`` request::
x-image-meta-uri http://glance.example.com/images/71c675ab-d94f-49cd-a114-e12490b328d9 x-image-meta-uri http://glance.example.com/images/71c675ab-d94f-49cd-a114-e12490b328d9
x-image-meta-name Ubuntu 10.04 Plain 5GB x-image-meta-name Ubuntu 10.04 Plain 5GB
x-image-meta-disk-format vhd x-image-meta-disk_format vhd
x-image-meta-container-format ovf x-image-meta-container_format ovf
x-image-meta-size 5368709120 x-image-meta-size 5368709120
x-image-meta-checksum c2e5db72bd7fd153f53ede5da5a06de3 x-image-meta-checksum c2e5db72bd7fd153f53ede5da5a06de3
x-image-meta-created_at 2010-02-03 09:34:01 x-image-meta-created_at 2010-02-03 09:34:01
@ -353,14 +353,14 @@ The list of metadata headers that Glance accepts are listed below.
store that is marked default. See the configuration option ``default_store`` store that is marked default. See the configuration option ``default_store``
for more information. for more information.
* ``x-image-meta-disk-format`` * ``x-image-meta-disk_format``
This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``, This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``,
``raw``, ``iso``, ``vhd``, ``vdi``, ``qcow2``, or ``vmdk``. ``raw``, ``iso``, ``vhd``, ``vdi``, ``qcow2``, or ``vmdk``.
For more information, see :doc:`About Disk and Container Formats <formats>` For more information, see :doc:`About Disk and Container Formats <formats>`
* ``x-image-meta-container-format`` * ``x-image-meta-container_format``
This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``, This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``,
``bare``, or ``ovf``. ``bare``, or ``ovf``.

View File

@ -77,12 +77,10 @@ def image_meta_to_http_headers(image_meta):
""" """
headers = {} headers = {}
for k, v in image_meta.items(): for k, v in image_meta.items():
if v is None: if v is not None:
v = ''
if k == 'properties': if k == 'properties':
for pk, pv in v.items(): for pk, pv in v.items():
if pv is None: if pv is not None:
pv = ''
headers["x-image-meta-property-%s" headers["x-image-meta-property-%s"
% pk.lower()] = unicode(pv) % pk.lower()] = unicode(pv)
else: else:

View File

@ -313,11 +313,11 @@ def validate_image(values):
msg = "Invalid image status '%s' for image." % status msg = "Invalid image status '%s' for image." % status
raise exception.Invalid(msg) raise exception.Invalid(msg)
if disk_format and disk_format not in DISK_FORMATS: if not disk_format or disk_format not in DISK_FORMATS:
msg = "Invalid disk format '%s' for image." % disk_format msg = "Invalid disk format '%s' for image." % disk_format
raise exception.Invalid(msg) raise exception.Invalid(msg)
if container_format and container_format not in CONTAINER_FORMATS: if not container_format or container_format not in CONTAINER_FORMATS:
msg = "Invalid container format '%s' for image." % container_format msg = "Invalid container format '%s' for image." % container_format
raise exception.Invalid(msg) raise exception.Invalid(msg)

View File

@ -25,7 +25,7 @@ import tempfile
from glance.common import utils from glance.common import utils
from glance.tests import functional from glance.tests import functional
from glance.tests.utils import execute, skip_if_disabled from glance.tests.utils import execute, skip_if_disabled, minimal_headers
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
FIVE_GB = 5 * 1024 * 1024 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024
@ -86,9 +86,7 @@ class TestApi(functional.FunctionalTest):
# 2. POST /images with public image named Image1 # 2. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned # attribute and no custom properties. Verify a 200 OK is returned
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -124,8 +122,8 @@ class TestApi(functional.FunctionalTest):
'x-image-meta-name': 'Image1', 'x-image-meta-name': 'Image1',
'x-image-meta-is_public': 'True', 'x-image-meta-is_public': 'True',
'x-image-meta-status': 'active', 'x-image-meta-status': 'active',
'x-image-meta-disk_format': '', 'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': '', 'x-image-meta-container_format': 'ovf',
'x-image-meta-size': str(FIVE_KB)} 'x-image-meta-size': str(FIVE_KB)}
expected_std_headers = { expected_std_headers = {
@ -157,8 +155,8 @@ class TestApi(functional.FunctionalTest):
self.assertEqual(response.status, 200) self.assertEqual(response.status, 200)
expected_result = {"images": [ expected_result = {"images": [
{"container_format": None, {"container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"name": "Image1", "name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3", "checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
@ -176,8 +174,8 @@ class TestApi(functional.FunctionalTest):
"status": "active", "status": "active",
"name": "Image1", "name": "Image1",
"deleted": False, "deleted": False,
"container_format": None, "container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"is_public": True, "is_public": True,
"deleted_at": None, "deleted_at": None,
@ -217,8 +215,8 @@ class TestApi(functional.FunctionalTest):
"status": "active", "status": "active",
"name": "Image1", "name": "Image1",
"deleted": False, "deleted": False,
"container_format": None, "container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"is_public": True, "is_public": True,
"deleted_at": None, "deleted_at": None,
@ -314,9 +312,7 @@ class TestApi(functional.FunctionalTest):
# 1. POST /images with public image named Image1 # 1. POST /images with public image named Image1
# with no location or image data # with no location or image data
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers) response, content = http.request(path, 'POST', headers=headers)
@ -324,8 +320,8 @@ class TestApi(functional.FunctionalTest):
data = json.loads(content) data = json.loads(content)
self.assertEqual(data['image']['checksum'], None) self.assertEqual(data['image']['checksum'], None)
self.assertEqual(data['image']['size'], 0) self.assertEqual(data['image']['size'], 0)
self.assertEqual(data['image']['container_format'], None) self.assertEqual(data['image']['container_format'], 'ovf')
self.assertEqual(data['image']['disk_format'], None) self.assertEqual(data['image']['disk_format'], 'raw')
self.assertEqual(data['image']['name'], "Image1") self.assertEqual(data['image']['name'], "Image1")
self.assertEqual(data['image']['is_public'], True) self.assertEqual(data['image']['is_public'], True)
@ -341,8 +337,8 @@ class TestApi(functional.FunctionalTest):
self.assertEqual(data['images'][0]['id'], image_id) self.assertEqual(data['images'][0]['id'], image_id)
self.assertEqual(data['images'][0]['checksum'], None) self.assertEqual(data['images'][0]['checksum'], None)
self.assertEqual(data['images'][0]['size'], 0) self.assertEqual(data['images'][0]['size'], 0)
self.assertEqual(data['images'][0]['container_format'], None) self.assertEqual(data['images'][0]['container_format'], 'ovf')
self.assertEqual(data['images'][0]['disk_format'], None) self.assertEqual(data['images'][0]['disk_format'], 'raw')
self.assertEqual(data['images'][0]['name'], "Image1") self.assertEqual(data['images'][0]['name'], "Image1")
# 3. HEAD /images # 3. HEAD /images
@ -394,8 +390,8 @@ class TestApi(functional.FunctionalTest):
hashlib.md5(image_data).hexdigest()) hashlib.md5(image_data).hexdigest())
self.assertEqual(data['images'][0]['id'], image_id) self.assertEqual(data['images'][0]['id'], image_id)
self.assertEqual(data['images'][0]['size'], FIVE_KB) self.assertEqual(data['images'][0]['size'], FIVE_KB)
self.assertEqual(data['images'][0]['container_format'], None) self.assertEqual(data['images'][0]['container_format'], 'ovf')
self.assertEqual(data['images'][0]['disk_format'], None) self.assertEqual(data['images'][0]['disk_format'], 'raw')
self.assertEqual(data['images'][0]['name'], "Image1") self.assertEqual(data['images'][0]['name'], "Image1")
# DELETE image # DELETE image
@ -592,6 +588,8 @@ class TestApi(functional.FunctionalTest):
'X-Image-Meta-Location': 'http://example.com/fakeimage', 'X-Image-Meta-Location': 'http://example.com/fakeimage',
'X-Image-Meta-Size': str(FIVE_GB), 'X-Image-Meta-Size': str(FIVE_GB),
'X-Image-Meta-Name': 'Image1', 'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-disk_format': 'raw',
'X-image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True'} 'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
@ -633,7 +631,8 @@ class TestApi(functional.FunctionalTest):
response, content = http.request(path, 'POST', response, content = http.request(path, 'POST',
body=test_data_file.name) body=test_data_file.name)
self.assertEqual(response.status, 400) self.assertEqual(response.status, 400)
expected = "Content-Type must be application/octet-stream" expected = ("Failed to reserve image. Got error: "
"Data supplied was not valid. Details: 400 Bad Request")
self.assertTrue(expected in content, self.assertTrue(expected in content,
"Could not find '%s' in '%s'" % (expected, content)) "Could not find '%s' in '%s'" % (expected, content))
@ -1014,27 +1013,21 @@ class TestApi(functional.FunctionalTest):
image_ids = [] image_ids = []
# 1. POST /images with three public images with various attributes # 1. POST /images with three public images with various attributes
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers) response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(response.status, 201) self.assertEqual(response.status, 201)
image_ids.append(json.loads(content)['image']['id']) image_ids.append(json.loads(content)['image']['id'])
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image2')
'X-Image-Meta-Name': 'Image2',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers) response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(response.status, 201) self.assertEqual(response.status, 201)
image_ids.append(json.loads(content)['image']['id']) image_ids.append(json.loads(content)['image']['id'])
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image3')
'X-Image-Meta-Name': 'Image3',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers) response, content = http.request(path, 'POST', headers=headers)
@ -1325,3 +1318,40 @@ class TestApi(functional.FunctionalTest):
expect_launch=False, expect_launch=False,
expected_exitcode=255, expected_exitcode=255,
**self.__dict__.copy()) **self.__dict__.copy())
def _do_test_unset_required_format(self, format):
"""
We test that missing container/disk format fails with 400 "Bad Request"
:see https://bugs.launchpad.net/glance/+bug/933702
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
# POST /images without given format being specified
headers = minimal_headers('Image1')
del headers['X-Image-Meta-' + format]
with tempfile.NamedTemporaryFile() as test_data_file:
test_data_file.write("XXX")
test_data_file.flush()
http = httplib2.Http()
response, content = http.request(path, 'POST',
headers=headers,
body=test_data_file.name)
self.assertEqual(response.status, 400)
type = format.replace('_format', '')
expected = "Details: Invalid %s format 'None' for image" % type
self.assertTrue(expected in content,
"Could not find '%s' in '%s'" % (expected, content))
self.stop_servers()
@skip_if_disabled
def test_unset_container_format(self):
self._do_test_unset_required_format('container_format')
@skip_if_disabled
def test_unset_disk_format(self):
self._do_test_unset_required_format('disk_format')

View File

@ -23,7 +23,7 @@ import tempfile
from glance.common import utils from glance.common import utils
from glance.tests import functional from glance.tests import functional
from glance.tests.utils import execute from glance.tests.utils import execute, minimal_add_command
class TestBinGlance(functional.FunctionalTest): class TestBinGlance(functional.FunctionalTest):
@ -54,8 +54,9 @@ class TestBinGlance(functional.FunctionalTest):
self.assertEqual('', out.strip()) self.assertEqual('', out.strip())
# 1. Add public image # 1. Add public image
cmd = "bin/glance --port=%d add is_public=True"\ cmd = minimal_add_command(api_port,
" name=MyImage location=http://example.com" % api_port 'MyImage',
'location=http://example.com')
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode) self.assertEqual(0, exitcode)
@ -99,8 +100,10 @@ class TestBinGlance(functional.FunctionalTest):
image_file.write("XXX") image_file.write("XXX")
image_file.flush() image_file.flush()
file_name = image_file.name file_name = image_file.name
cmd = "bin/glance --port=%d add is_public=True name=MyImage " \ cmd = minimal_add_command(api_port,
"location=http://example.com < %s" % (api_port, file_name) 'MyImage',
'location=http://example.com < %s' %
file_name)
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode) self.assertEqual(0, exitcode)
@ -153,8 +156,9 @@ class TestBinGlance(functional.FunctionalTest):
image_file.write("XXX") image_file.write("XXX")
image_file.flush() image_file.flush()
image_file_name = image_file.name image_file_name = image_file.name
cmd = "bin/glance --port=%d add is_public=True"\ cmd = minimal_add_command(api_port,
" name=MyImage < %s" % (api_port, image_file_name) 'MyImage',
'< %s' % image_file_name)
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
@ -227,7 +231,10 @@ class TestBinGlance(functional.FunctionalTest):
self.assertEqual('', out.strip()) self.assertEqual('', out.strip())
# 1. Add public image # 1. Add public image
cmd = "bin/glance --port=%d add name=MyImage" % api_port cmd = minimal_add_command(api_port,
'MyImage',
'location=http://example.com',
public=False)
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
@ -355,8 +362,9 @@ class TestBinGlance(functional.FunctionalTest):
# 1. Add some images # 1. Add some images
for i in range(1, 5): for i in range(1, 5):
cmd = "bin/glance --port=%d add is_public=True name=MyName " \ cmd = minimal_add_command(api_port,
" foo=bar" % api_port 'MyImage',
'foo=bar')
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode) self.assertEqual(0, exitcode)
@ -705,7 +713,11 @@ class TestBinGlance(functional.FunctionalTest):
image_file.flush() image_file.flush()
image_file_name = image_file.name image_file_name = image_file.name
cmd = "bin/glance --port=%d add is_public=True"\ cmd = "bin/glance --port=%d add is_public=True"\
" disk_format=raw container_format=ovf " \
" name=MyImage < %s" % (api_port, image_file_name) " name=MyImage < %s" % (api_port, image_file_name)
cmd = minimal_add_command(api_port,
'MyImage',
'< %s' % image_file_name)
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)
@ -771,8 +783,10 @@ class TestBinGlance(functional.FunctionalTest):
self.assertEqual('', out.strip()) self.assertEqual('', out.strip())
# 1. Add public image # 1. Add public image
cmd = "echo testdata | bin/glance --port=%d add is_public=True"\ cmd = ("echo testdata | " +
" protected=True name=MyImage" % api_port minimal_add_command(api_port,
'MyImage',
'protected=True'))
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)

View File

@ -27,7 +27,7 @@ import unittest
from glance.common import utils from glance.common import utils
from glance.tests import functional from glance.tests import functional
from glance.tests.utils import execute from glance.tests.utils import execute, minimal_headers
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
@ -54,9 +54,7 @@ class TestBinGlanceCacheManage(functional.FunctionalTest):
image identifier. image identifier.
""" """
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers(name)
'X-Image-Meta-Name': name,
'X-Image-Meta-Is-Public': 'true'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,

View File

@ -35,7 +35,9 @@ import httplib2
from glance.tests import functional from glance.tests import functional
from glance.tests.utils import (skip_if_disabled, from glance.tests.utils import (skip_if_disabled,
execute, execute,
xattr_writes_supported) xattr_writes_supported,
minimal_headers,
)
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
@ -90,9 +92,7 @@ class BaseCacheMiddlewareTest(object):
# Add an image and verify a 200 OK is returned # Add an image and verify a 200 OK is returned
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -177,6 +177,8 @@ class BaseCacheMiddlewareTest(object):
# Add a remote image and verify a 201 Created is returned # Add a remote image and verify a 201 Created is returned
remote_uri = 'http://%s:%d/images/2' % (remote_ip, remote_port) remote_uri = 'http://%s:%d/images/2' % (remote_ip, remote_port)
headers = {'X-Image-Meta-Name': 'Image2', headers = {'X-Image-Meta-Name': 'Image2',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True', 'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Location': remote_uri} 'X-Image-Meta-Location': remote_uri}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
@ -226,9 +228,8 @@ class BaseCacheManageMiddlewareTest(object):
identifier identifier
""" """
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('%s' % name)
'X-Image-Meta-Name': '%s' % name,
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,

View File

@ -22,7 +22,7 @@ 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, minimal_headers, minimal_add_command
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
FIVE_GB = 5 * 1024 * 1024 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024
@ -44,9 +44,7 @@ class TestMiscellaneous(functional.FunctionalTest):
# 1. POST /images with public image named Image1 # 1. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned # attribute and no custom properties. Verify a 200 OK is returned
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -132,8 +130,9 @@ class TestMiscellaneous(functional.FunctionalTest):
image_file.write("XXX") image_file.write("XXX")
image_file.flush() image_file.flush()
image_file_name = image_file.name image_file_name = image_file.name
cmd = "bin/glance --port=%d add is_public=True name=MyImage "\ cmd = minimal_add_command(self.api_port,
"size=12345 < %s" % (self.api_port, image_file_name) 'MyImage',
'size=12345 < %s' % image_file_name)
exitcode, out, err = execute(cmd) exitcode, out, err = execute(cmd)

View File

@ -23,7 +23,11 @@ import os
from glance.tests import functional from glance.tests import functional
from glance.tests.functional import keystone_utils from glance.tests.functional import keystone_utils
from glance.tests.utils import execute, skip_if_disabled from glance.tests.utils import (execute,
skip_if_disabled,
minimal_headers,
minimal_add_command,
)
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
FIVE_GB = 5 * 1024 * 1024 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024
@ -46,9 +50,8 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
# First, we need to push an image up # First, we need to push an image up
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1', public=False)
'X-Auth-Token': keystone_utils.pattieblack_token, headers['X-Auth-Token'] = keystone_utils.pattieblack_token
'X-Image-Meta-Name': 'Image1'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -298,9 +301,8 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
# Need to push an image up # Need to push an image up
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1', public=False)
'X-Auth-Token': keystone_utils.pattieblack_token, headers['X-Auth-Token'] = keystone_utils.pattieblack_token
'X-Image-Meta-Name': 'Image1'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -478,7 +480,7 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
self.assertEqual(response.status, 200) self.assertEqual(response.status, 200)
self.assertEqual(response['x-image-meta-name'], "Image1") self.assertEqual(response['x-image-meta-name'], "Image1")
self.assertEqual(response['x-image-meta-is_public'], "False") self.assertEqual(response['x-image-meta-is_public'], "False")
self.assertEqual(response['x-image-meta-owner'], '') self.assertFalse('x-image-meta-owner' in response)
# And of course the image itself # And of course the image itself
headers = {'X-Auth-Token': keystone_utils.pattieblack_token} headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
@ -490,7 +492,7 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
self.assertEqual(content, "*" * FIVE_KB) self.assertEqual(content, "*" * FIVE_KB)
self.assertEqual(response['x-image-meta-name'], "Image1") self.assertEqual(response['x-image-meta-name'], "Image1")
self.assertEqual(response['x-image-meta-is_public'], "False") self.assertEqual(response['x-image-meta-is_public'], "False")
self.assertEqual(response['x-image-meta-owner'], '') self.assertFalse('x-image-meta-owner' in response)
# Pattieblack can't change is-public, though # Pattieblack can't change is-public, though
headers = {'X-Auth-Token': keystone_utils.pattieblack_token, headers = {'X-Auth-Token': keystone_utils.pattieblack_token,
@ -531,8 +533,7 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
# Make sure anonymous user can't push up an image # Make sure anonymous user can't push up an image
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1', public=False)
'X-Image-Meta-Name': 'Image1'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -541,9 +542,8 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
# Now push up an image for anonymous user to try to access # Now push up an image for anonymous user to try to access
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1', public=False)
'X-Auth-Token': keystone_utils.pattieblack_token, headers['X-Auth-Token'] = keystone_utils.pattieblack_token
'X-Image-Meta-Name': 'Image1'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers, response, content = http.request(path, 'POST', headers=headers,
@ -644,7 +644,7 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
self.assertEqual(response.status, 200) self.assertEqual(response.status, 200)
self.assertEqual(response['x-image-meta-name'], "Image1") self.assertEqual(response['x-image-meta-name'], "Image1")
self.assertEqual(response['x-image-meta-is_public'], "False") self.assertEqual(response['x-image-meta-is_public'], "False")
self.assertEqual(response['x-image-meta-owner'], '') self.assertFalse('x-image-meta-owner' in response)
# And even the image itself... # And even the image itself...
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
@ -655,7 +655,7 @@ class TestPrivateImagesApi(keystone_utils.KeystoneTests):
self.assertEqual(content, "*" * FIVE_KB) self.assertEqual(content, "*" * FIVE_KB)
self.assertEqual(response['x-image-meta-name'], "Image1") self.assertEqual(response['x-image-meta-name'], "Image1")
self.assertEqual(response['x-image-meta-is_public'], "False") self.assertEqual(response['x-image-meta-is_public'], "False")
self.assertEqual(response['x-image-meta-owner'], '') self.assertFalse('x-image-meta-owner' in response)
# Anonymous still shouldn't be able to make the image # Anonymous still shouldn't be able to make the image
# public... # public...
@ -835,7 +835,7 @@ class TestPrivateImagesCli(keystone_utils.KeystoneTests):
self.assertEqual(response.status, 200) self.assertEqual(response.status, 200)
self.assertEqual(response['x-image-meta-name'], "MyImage") self.assertEqual(response['x-image-meta-name'], "MyImage")
self.assertEqual(response['x-image-meta-is_public'], "True") self.assertEqual(response['x-image-meta-is_public'], "True")
self.assertEqual(response['x-image-meta-owner'], '') self.assertFalse('x-image-meta-owner' in response)
self.stop_servers() self.stop_servers()
@ -850,8 +850,8 @@ class TestPrivateImagesCli(keystone_utils.KeystoneTests):
""" """
Test the CLI with the noauth strategy defaulted to. Test the CLI with the noauth strategy defaulted to.
""" """
cmd = ("bin/glance --port=%d --auth_token=%s add name=MyImage" % suffix = '--auth_token=%s' % keystone_utils.pattieblack_token
(self.api_port, keystone_utils.pattieblack_token)) cmd = minimal_add_command(self.api_port, 'MyImage', suffix, False)
self._do_test_glance_cli(cmd) self._do_test_glance_cli(cmd)
@skip_if_disabled @skip_if_disabled
@ -860,11 +860,12 @@ class TestPrivateImagesCli(keystone_utils.KeystoneTests):
Test the CLI with the keystone (v1) strategy enabled via Test the CLI with the keystone (v1) strategy enabled via
command line switches. command line switches.
""" """
substitutions = (self.api_port, self.auth_port, 'keystone', substitutions = (self.auth_port, 'keystone',
'pattieblack', 'secrete') 'pattieblack', 'secrete')
cmd = ("bin/glance --port=%d --auth_url=http://localhost:%d/v1.0 " suffix = ("--auth_url=http://localhost:%d/v1.0 "
"--auth_strategy=%s --username=%s --password=%s " "--auth_strategy=%s --username=%s --password=%s "
" add name=MyImage" % substitutions) % substitutions)
cmd = minimal_add_command(self.api_port, 'MyImage', suffix, False)
self._do_test_glance_cli(cmd) self._do_test_glance_cli(cmd)
@skip_if_disabled @skip_if_disabled
@ -877,7 +878,7 @@ class TestPrivateImagesCli(keystone_utils.KeystoneTests):
os.environ['OS_AUTH_STRATEGY'] = 'keystone' os.environ['OS_AUTH_STRATEGY'] = 'keystone'
os.environ['OS_AUTH_USER'] = 'pattieblack' os.environ['OS_AUTH_USER'] = 'pattieblack'
os.environ['OS_AUTH_KEY'] = 'secrete' os.environ['OS_AUTH_KEY'] = 'secrete'
cmd = "bin/glance --port=%d add name=MyImage" % self.api_port cmd = minimal_add_command(self.api_port, 'MyImage', public=False)
self._do_test_glance_cli(cmd) self._do_test_glance_cli(cmd)
@skip_if_disabled @skip_if_disabled

View File

@ -22,7 +22,7 @@ import json
from glance.tests import functional from glance.tests import functional
from glance.tests.functional import keystone_utils from glance.tests.functional import keystone_utils
from glance.tests.utils import execute, skip_if_disabled from glance.tests.utils import execute, skip_if_disabled, minimal_headers
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
FIVE_GB = 5 * 1024 * 1024 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024
@ -32,8 +32,7 @@ class TestSharedImagesApi(keystone_utils.KeystoneTests):
def _push_image(self): def _push_image(self):
# First, we need to push an image up # First, we need to push an image up
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1', public=False)
'X-Image-Meta-Name': 'Image1'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
response, content = self._request(path, 'POST', response, content = self._request(path, 'POST',
keystone_utils.pattieblack_token, keystone_utils.pattieblack_token,
@ -378,8 +377,7 @@ class TestSharedImagesCli(keystone_utils.KeystoneTests):
def _push_image(self, name=1): def _push_image(self, name=1):
# First, we need to push an image up # First, we need to push an image up
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers(str(name), public=False)
'X-Image-Meta-Name': str(name)}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
response, content = self._request(path, 'POST', response, content = self._request(path, 'POST',
keystone_utils.pattieblack_token, keystone_utils.pattieblack_token,

View File

@ -45,7 +45,7 @@ from glance.common import exception
from glance.common import utils from glance.common import utils
from glance.store.location import get_location_from_uri from glance.store.location import get_location_from_uri
from glance.tests import functional from glance.tests import functional
from glance.tests.utils import execute, skip_if_disabled from glance.tests.utils import execute, skip_if_disabled, minimal_headers
FIVE_KB = 5 * 1024 FIVE_KB = 5 * 1024
FIVE_GB = 5 * 1024 * 1024 * 1024 FIVE_GB = 5 * 1024 * 1024 * 1024
@ -144,9 +144,7 @@ class TestSSL(functional.FunctionalTest):
# 2. POST /images with public image named Image1 # 2. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned # attribute and no custom properties. Verify a 200 OK is returned
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers, response, content = https.request(path, 'POST', headers=headers,
@ -183,8 +181,8 @@ class TestSSL(functional.FunctionalTest):
'x-image-meta-name': 'Image1', 'x-image-meta-name': 'Image1',
'x-image-meta-is_public': 'True', 'x-image-meta-is_public': 'True',
'x-image-meta-status': 'active', 'x-image-meta-status': 'active',
'x-image-meta-disk_format': '', 'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': '', 'x-image-meta-container_format': 'ovf',
'x-image-meta-size': str(FIVE_KB)} 'x-image-meta-size': str(FIVE_KB)}
expected_std_headers = { expected_std_headers = {
@ -216,8 +214,8 @@ class TestSSL(functional.FunctionalTest):
self.assertEqual(response.status, 200) self.assertEqual(response.status, 200)
expected_result = {"images": [ expected_result = {"images": [
{"container_format": None, {"container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"name": "Image1", "name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3", "checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
@ -235,8 +233,8 @@ class TestSSL(functional.FunctionalTest):
"status": "active", "status": "active",
"name": "Image1", "name": "Image1",
"deleted": False, "deleted": False,
"container_format": None, "container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"is_public": True, "is_public": True,
"deleted_at": None, "deleted_at": None,
@ -276,8 +274,8 @@ class TestSSL(functional.FunctionalTest):
"status": "active", "status": "active",
"name": "Image1", "name": "Image1",
"deleted": False, "deleted": False,
"container_format": None, "container_format": "ovf",
"disk_format": None, "disk_format": "raw",
"id": image_id, "id": image_id,
"is_public": True, "is_public": True,
"deleted_at": None, "deleted_at": None,
@ -366,9 +364,7 @@ class TestSSL(functional.FunctionalTest):
# 1. POST /images with public image named Image1 # 1. POST /images with public image named Image1
# with no location or image data # with no location or image data
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers) response, content = https.request(path, 'POST', headers=headers)
@ -376,8 +372,8 @@ class TestSSL(functional.FunctionalTest):
data = json.loads(content) data = json.loads(content)
self.assertEqual(data['image']['checksum'], None) self.assertEqual(data['image']['checksum'], None)
self.assertEqual(data['image']['size'], 0) self.assertEqual(data['image']['size'], 0)
self.assertEqual(data['image']['container_format'], None) self.assertEqual(data['image']['container_format'], 'ovf')
self.assertEqual(data['image']['disk_format'], None) self.assertEqual(data['image']['disk_format'], 'raw')
self.assertEqual(data['image']['name'], "Image1") self.assertEqual(data['image']['name'], "Image1")
self.assertEqual(data['image']['is_public'], True) self.assertEqual(data['image']['is_public'], True)
@ -393,8 +389,8 @@ class TestSSL(functional.FunctionalTest):
self.assertEqual(data['images'][0]['id'], image_id) self.assertEqual(data['images'][0]['id'], image_id)
self.assertEqual(data['images'][0]['checksum'], None) self.assertEqual(data['images'][0]['checksum'], None)
self.assertEqual(data['images'][0]['size'], 0) self.assertEqual(data['images'][0]['size'], 0)
self.assertEqual(data['images'][0]['container_format'], None) self.assertEqual(data['images'][0]['container_format'], 'ovf')
self.assertEqual(data['images'][0]['disk_format'], None) self.assertEqual(data['images'][0]['disk_format'], 'raw')
self.assertEqual(data['images'][0]['name'], "Image1") self.assertEqual(data['images'][0]['name'], "Image1")
# 3. HEAD /images # 3. HEAD /images
@ -446,8 +442,8 @@ class TestSSL(functional.FunctionalTest):
hashlib.md5(image_data).hexdigest()) hashlib.md5(image_data).hexdigest())
self.assertEqual(data['images'][0]['id'], image_id) self.assertEqual(data['images'][0]['id'], image_id)
self.assertEqual(data['images'][0]['size'], FIVE_KB) self.assertEqual(data['images'][0]['size'], FIVE_KB)
self.assertEqual(data['images'][0]['container_format'], None) self.assertEqual(data['images'][0]['container_format'], 'ovf')
self.assertEqual(data['images'][0]['disk_format'], None) self.assertEqual(data['images'][0]['disk_format'], 'raw')
self.assertEqual(data['images'][0]['name'], "Image1") self.assertEqual(data['images'][0]['name'], "Image1")
self.stop_servers() self.stop_servers()
@ -633,11 +629,9 @@ class TestSSL(functional.FunctionalTest):
# X-Image-Meta-Location attribute to make Glance forego # X-Image-Meta-Location attribute to make Glance forego
# "adding" the image data. # "adding" the image data.
# Verify a 201 OK is returned # Verify a 201 OK is returned
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Location': 'https://example.com/fakeimage', headers['X-Image-Meta-Location'] = 'https://example.com/fakeimage'
'X-Image-Meta-Size': str(FIVE_GB), headers['X-Image-Meta-Size'] = str(FIVE_GB)
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers) response, content = https.request(path, 'POST', headers=headers)
@ -678,7 +672,7 @@ class TestSSL(functional.FunctionalTest):
response, content = https.request(path, 'POST', response, content = https.request(path, 'POST',
body=test_data_file.name) body=test_data_file.name)
self.assertEqual(response.status, 400) self.assertEqual(response.status, 400)
expected = "Content-Type must be application/octet-stream" expected = "Data supplied was not valid. Details: 400 Bad Request"
self.assertTrue(expected in content, self.assertTrue(expected in content,
"Could not find '%s' in '%s'" % (expected, content)) "Could not find '%s' in '%s'" % (expected, content))
@ -956,9 +950,7 @@ class TestSSL(functional.FunctionalTest):
self.assertEqual(content, '{"images": []}') self.assertEqual(content, '{"images": []}')
# 1. POST /images with three public images with various attributes # 1. POST /images with three public images with various attributes
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image1')
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers) response, content = https.request(path, 'POST', headers=headers)
@ -967,9 +959,7 @@ class TestSSL(functional.FunctionalTest):
image_ids = [data['image']['id']] image_ids = [data['image']['id']]
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image2')
'X-Image-Meta-Name': 'Image2',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers) response, content = https.request(path, 'POST', headers=headers)
@ -978,9 +968,7 @@ class TestSSL(functional.FunctionalTest):
image_ids.append(data['image']['id']) image_ids.append(data['image']['id'])
headers = {'Content-Type': 'application/octet-stream', headers = minimal_headers('Image3')
'X-Image-Meta-Name': 'Image3',
'X-Image-Meta-Is-Public': 'True'}
path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port) path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True) https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers) response, content = https.request(path, 'POST', headers=headers)

View File

@ -2194,6 +2194,32 @@ class TestGlanceAPI(base.IsolatedUnitTest):
res = req.get_response(self.api) res = req.get_response(self.api)
self.assertEquals(res.status_int, 401) self.assertEquals(res.status_int, 401)
def _do_test_add_image_missing_format(self, missing):
"""Tests creation of an image with missing format"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
header = 'x-image-meta-' + missing.replace('_', '-')
del fixture_headers[header]
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
self.assertEquals(res.status_int, httplib.BAD_REQUEST)
def test_add_image_missing_disk_format(self):
"""Tests creation of an image with missing disk format"""
self._do_test_add_image_missing_format('disk_format')
def test_add_image_missing_container_type(self):
"""Tests creation of an image with missing container format"""
self._do_test_add_image_missing_format('container_format')
def test_register_and_upload(self): def test_register_and_upload(self):
""" """
Test that the process of registering an image with Test that the process of registering an image with
@ -2714,6 +2740,8 @@ class TestGlanceAPI(base.IsolatedUnitTest):
# We will stop the process after the reservation stage, then # We will stop the process after the reservation stage, then
# try to delete the image. # try to delete the image.
fixture_headers = {'x-image-meta-store': 'file', fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'} 'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images") req = webob.Request.blank("/images")
@ -2735,6 +2763,8 @@ class TestGlanceAPI(base.IsolatedUnitTest):
def test_delete_protected_image(self): def test_delete_protected_image(self):
fixture_headers = {'x-image-meta-store': 'file', fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-name': 'fake image #3', 'x-image-meta-name': 'fake image #3',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-protected': 'True'} 'x-image-meta-protected': 'True'}
req = webob.Request.blank("/images") req = webob.Request.blank("/images")

View File

@ -145,3 +145,32 @@ class UtilsTestCase(unittest.TestCase):
self.assertTrue(ciphertext != plaintext) self.assertTrue(ciphertext != plaintext)
text = crypt.urlsafe_decrypt(key, ciphertext) text = crypt.urlsafe_decrypt(key, ciphertext)
self.assertTrue(plaintext == text) self.assertTrue(plaintext == text)
def test_empty_metadata_headers(self):
"""Ensure unset metadata is not encoded in HTTP headers"""
metadata = {
'foo': 'bar',
'snafu': None,
'bells': 'whistles',
'unset': None,
'empty': '',
'properties': {
'distro': '',
'arch': None,
'user': 'nobody',
},
}
headers = utils.image_meta_to_http_headers(metadata)
self.assertFalse('x-image-meta-snafu' in headers)
self.assertFalse('x-image-meta-uset' in headers)
self.assertFalse('x-image-meta-snafu' in headers)
self.assertFalse('x-image-meta-property-arch' in headers)
self.assertEquals(headers.get('x-image-meta-foo'), 'bar')
self.assertEquals(headers.get('x-image-meta-bells'), 'whistles')
self.assertEquals(headers.get('x-image-meta-empty'), '')
self.assertEquals(headers.get('x-image-meta-property-distro'), '')
self.assertEquals(headers.get('x-image-meta-property-user'), 'nobody')

View File

@ -230,7 +230,10 @@ class TestHelpers(unittest.TestCase):
response.headers = headers response.headers = headers
result = utils.get_image_meta_from_headers(response) result = utils.get_image_meta_from_headers(response)
for k, v in fixture.iteritems(): for k, v in fixture.iteritems():
if v is not None:
self.assertEqual(v, result[k]) self.assertEqual(v, result[k])
else:
self.assertFalse(k in result)
def test_boolean_header_values(self): def test_boolean_header_values(self):
""" """

View File

@ -310,3 +310,21 @@ def xattr_writes_supported(path):
os.unlink(fake_filepath) os.unlink(fake_filepath)
return result return result
def minimal_headers(name, public=True):
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': name,
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
}
if public:
headers['X-Image-Meta-Is-Public'] = 'True'
return headers
def minimal_add_command(port, name, suffix='', public=True):
visibility = 'is_public=True' if public else ''
return ("bin/glance --port=%d add %s"
" disk_format=raw container_format=ovf"
" name=%s %s" % (port, visibility, name, suffix))