Skip to content

Commit

Permalink
Support zero-size image creation via the v1 API
Browse files Browse the repository at this point in the history
Addresses LP 1025353 for the v1 API.

Transition image status to active immediately on creation
(as opposed to leaving it queued forever) if the size is set
to zero from the get-go.

The v2 implementation is left unchanged for now, as the image
status does not appear to ever transition from queued to active
in that case.

This change allows an image to be created that simply acts as
a properties bucket, but requires no image data. For example,
an image created from a booted-from-volume instance where only
the kernel, ramdisk ID, and block device mappings are required.

Change-Id: I61e96f3fe5f5245fec791170b4a8b4c72135c3de
  • Loading branch information
Eoghan Glynn committed Jul 16, 2012
1 parent 00a7683 commit 6873195
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 7 deletions.
7 changes: 5 additions & 2 deletions doc/source/statuses.rst
Expand Up @@ -22,7 +22,8 @@ Images in Glance can be in one the following statuses:
* ``queued``

The image identifier has been reserved for an image in the Glance
registry. No image data has been uploaded to Glance.
registry. No image data has been uploaded to Glance and the image
size was not explicitly set to zero on creation.

* ``saving``

Expand All @@ -34,7 +35,9 @@ Images in Glance can be in one the following statuses:

* ``active``

Denotes an image that is fully available in Glance.
Denotes an image that is fully available in Glance. This occurs when
the image data is uploaded, or the image size is explicitly set to
zero on creation.

* ``killed``

Expand Down
16 changes: 11 additions & 5 deletions glance/api/v1/images.py
Expand Up @@ -272,12 +272,16 @@ def show(self, req, id):
self._enforce(req, 'get_image')
image_meta = self.get_active_image_meta_or_404(req, id)

image_iterator, size = self._get_from_store(image_meta['location'])
image_meta['size'] = size or image_meta['size']
if image_meta.get('size') == 0:
image_iterator = iter([])
else:
image_iterator, size = self._get_from_store(image_meta['location'])
image_iterator = utils.cooperative_iter(image_iterator)
image_meta['size'] = size or image_meta['size']

del image_meta['location']
return {
'image_iterator': utils.cooperative_iter(image_iterator),
'image_iterator': image_iterator,
'image_meta': image_meta,
}

Expand All @@ -295,6 +299,10 @@ def _reserve(self, req, image_meta):
:raises HTTPBadRequest if image metadata is not valid
"""
location = self._external_source(image_meta, req)

image_meta['status'] = ('active' if image_meta.get('size') == 0
else 'queued')

if location:
store = get_store_from_location(location)
# check the store exists before we hit the registry, but we
Expand All @@ -309,8 +317,6 @@ def _reserve(self, req, image_meta):
# to a non-zero value during upload
image_meta['size'] = image_meta.get('size', 0)

image_meta['status'] = 'queued'

try:
image_meta = registry.add_image_metadata(req.context, image_meta)
return image_meta
Expand Down
42 changes: 42 additions & 0 deletions glance/tests/functional/v1/test_api.py
Expand Up @@ -445,6 +445,48 @@ def test_size_greater_2G_mysql(self):

self.stop_servers()

@skip_if_disabled
def test_zero_initial_size(self):
"""
A test to ensure that an image with size explicitly set to zero
has status that immediately transitions to active.
"""

self.cleanup()
self.start_servers(**self.__dict__.copy())

# 1. POST /images with public image named Image1
# attribute and a size of zero.
# Verify a 201 OK is returned
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Size': '0',
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-disk_format': 'raw',
'X-image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(response.status, 201)

# 2. HEAD image-location
# Verify image size is zero and the status is active
path = response.get('location')
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(response.status, 200)
self.assertEqual(response['x-image-meta-size'], '0')
self.assertEqual(response['x-image-meta-status'], 'active')

# 3. GET image-location
# Verify image content is empty
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(response.status, 200)
self.assertEqual(len(content), 0)

self.stop_servers()

@skip_if_disabled
def test_traceback_not_consumed(self):
"""
Expand Down
24 changes: 24 additions & 0 deletions glance/tests/unit/v1/test_api.py
Expand Up @@ -2158,6 +2158,30 @@ def test_add_image_size_too_big(self):
res = req.get_response(self.api)
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)

def test_add_image_zero_size(self):
"""Tests creating an active image with explicitly zero size"""
fixture_headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-size': '0',
'x-image-meta-name': 'empty image'}

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.CREATED)

res_body = json.loads(res.body)['image']
self.assertEquals('active', res_body['status'])
image_id = res_body['id']

# GET empty image
req = webob.Request.blank("/images/%s" % image_id)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 200)
self.assertEqual(len(res.body), 0)

def test_add_image_bad_store(self):
"""Tests raises BadRequest for invalid store header"""
fixture_headers = {'x-image-meta-store': 'bad',
Expand Down

0 comments on commit 6873195

Please sign in to comment.