disk/container_format required on image activate
Fixes lp 937216 The initial change[1] to require the container and disk formats be explicitly set was enforced on the initial image POST, regardless of whether the image content accompanied that request (either in as the entity-body, or externally specified via x-image-meta-location). This turned out to be overly restrictive, as these formats may not be known to the caller until the image content is supplied via a subsequent PUT on the new image. Now we only enforce the strict requirement for these formats to be non-empty when the image is activated (as opposed to the initial enqueue). [1] https://github.com/openstack/glance/commit/62c913c3 Change-Id: I89e068f35fd7da427b547b18cdea4ae84ab3ec87
This commit is contained in:
parent
62c913c3ad
commit
5f31371330
@ -444,8 +444,18 @@ class Controller(controller.BaseController):
|
||||
image_meta = {}
|
||||
image_meta['location'] = location
|
||||
image_meta['status'] = 'active'
|
||||
return registry.update_image_metadata(req.context, image_id,
|
||||
image_meta)
|
||||
|
||||
try:
|
||||
return registry.update_image_metadata(req.context,
|
||||
image_id,
|
||||
image_meta)
|
||||
except exception.Invalid, e:
|
||||
msg = (_("Failed to activate image. Got error: %(e)s")
|
||||
% locals())
|
||||
for line in msg.split('\n'):
|
||||
logger.error(line)
|
||||
self.notifier.error('image.update', msg)
|
||||
raise HTTPBadRequest(msg, request=req, content_type="text/plain")
|
||||
|
||||
def _kill(self, req, image_id):
|
||||
"""
|
||||
|
@ -313,11 +313,21 @@ def validate_image(values):
|
||||
msg = "Invalid image status '%s' for image." % status
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if not disk_format or disk_format not in DISK_FORMATS:
|
||||
def _required_format_absent(format, formats):
|
||||
activating = status == 'active'
|
||||
unrecognized = format not in formats
|
||||
# We don't mind having format = None when we're just registering
|
||||
# an image, but if the image is being activated, make sure that the
|
||||
# format is valid. Conversely if the format happens to be set on
|
||||
# registration, it must be one of the recognized formats.
|
||||
return ((activating and (not format or unrecognized))
|
||||
or (not activating and format and unrecognized))
|
||||
|
||||
if _required_format_absent(disk_format, DISK_FORMATS):
|
||||
msg = "Invalid disk format '%s' for image." % disk_format
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if not container_format or container_format not in CONTAINER_FORMATS:
|
||||
if _required_format_absent(container_format, CONTAINER_FORMATS):
|
||||
msg = "Invalid container format '%s' for image." % container_format
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
|
@ -631,8 +631,7 @@ class TestApi(functional.FunctionalTest):
|
||||
response, content = http.request(path, 'POST',
|
||||
body=test_data_file.name)
|
||||
self.assertEqual(response.status, 400)
|
||||
expected = ("Failed to reserve image. Got error: "
|
||||
"Data supplied was not valid. Details: 400 Bad Request")
|
||||
expected = "Content-Type must be application/octet-stream"
|
||||
self.assertTrue(expected in content,
|
||||
"Could not find '%s' in '%s'" % (expected, content))
|
||||
|
||||
@ -1319,7 +1318,7 @@ class TestApi(functional.FunctionalTest):
|
||||
expected_exitcode=255,
|
||||
**self.__dict__.copy())
|
||||
|
||||
def _do_test_unset_required_format(self, format):
|
||||
def _do_test_post_image_content_missing_format(self, format):
|
||||
"""
|
||||
We test that missing container/disk format fails with 400 "Bad Request"
|
||||
|
||||
@ -1349,9 +1348,60 @@ class TestApi(functional.FunctionalTest):
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_unset_container_format(self):
|
||||
self._do_test_unset_required_format('container_format')
|
||||
def _do_test_post_image_content_missing_diskformat(self):
|
||||
self._do_test_post_image_content_missing_format('container_format')
|
||||
|
||||
@skip_if_disabled
|
||||
def test_unset_disk_format(self):
|
||||
self._do_test_unset_required_format('disk_format')
|
||||
def _do_test_post_image_content_missing_disk_format(self):
|
||||
self._do_test_post_image_content_missing_format('disk_format')
|
||||
|
||||
def _do_test_put_image_content_missing_format(self, format):
|
||||
"""
|
||||
We test that missing container/disk format only fails with
|
||||
400 "Bad Request" when the image content is PUT (i.e. not
|
||||
on the original POST of a queued image).
|
||||
|
||||
:see https://bugs.launchpad.net/glance/+bug/937216
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# POST queued image
|
||||
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||
headers = {
|
||||
'X-Image-Meta-Name': 'Image1',
|
||||
'X-Image-Meta-Is-Public': 'True',
|
||||
}
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers)
|
||||
self.assertEqual(response.status, 201)
|
||||
data = json.loads(content)
|
||||
image_id = data['image']['id']
|
||||
|
||||
# PUT image content images without given format being specified
|
||||
path = ("http://%s:%d/v1/images/%s" %
|
||||
("0.0.0.0", self.api_port, image_id))
|
||||
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, 'PUT',
|
||||
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 _do_test_put_image_content_missing_diskformat(self):
|
||||
self._do_test_put_image_content_missing_format('container_format')
|
||||
|
||||
@skip_if_disabled
|
||||
def _do_test_put_image_content_missing_disk_format(self):
|
||||
self._do_test_put_image_content_missing_format('disk_format')
|
||||
|
@ -672,7 +672,7 @@ class TestSSL(functional.FunctionalTest):
|
||||
response, content = https.request(path, 'POST',
|
||||
body=test_data_file.name)
|
||||
self.assertEqual(response.status, 400)
|
||||
expected = "Data supplied was not valid. Details: 400 Bad Request"
|
||||
expected = "Content-Type must be application/octet-stream"
|
||||
self.assertTrue(expected in content,
|
||||
"Could not find '%s' in '%s'" % (expected, content))
|
||||
|
||||
|
@ -2194,7 +2194,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, 401)
|
||||
|
||||
def _do_test_add_image_missing_format(self, missing):
|
||||
def _do_test_post_image_content_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',
|
||||
@ -2209,16 +2209,58 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
||||
req.method = 'POST'
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
req.body = "chunk00000remainder"
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, httplib.BAD_REQUEST)
|
||||
|
||||
def test_add_image_missing_disk_format(self):
|
||||
def test_post_image_content_missing_disk_format(self):
|
||||
"""Tests creation of an image with missing disk format"""
|
||||
self._do_test_add_image_missing_format('disk_format')
|
||||
self._do_test_post_image_content_missing_format('disk_format')
|
||||
|
||||
def test_add_image_missing_container_type(self):
|
||||
def test_post_image_content_missing_container_type(self):
|
||||
"""Tests creation of an image with missing container format"""
|
||||
self._do_test_add_image_missing_format('container_format')
|
||||
self._do_test_post_image_content_missing_format('container_format')
|
||||
|
||||
def _do_test_put_image_content_missing_format(self, missing):
|
||||
"""Tests delayed activation 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.CREATED)
|
||||
|
||||
res_body = json.loads(res.body)['image']
|
||||
self.assertEquals('queued', res_body['status'])
|
||||
image_id = res_body['id']
|
||||
|
||||
req = webob.Request.blank("/images/%s" % image_id)
|
||||
req.method = 'PUT'
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
req.body = "chunk00000remainder"
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, httplib.BAD_REQUEST)
|
||||
|
||||
def test_put_image_content_missing_disk_format(self):
|
||||
"""Tests delayed activation of image with missing disk format"""
|
||||
self._do_test_put_image_content_missing_format('disk_format')
|
||||
|
||||
def test_put_image_content_missing_container_type(self):
|
||||
"""Tests delayed activation of image with missing container format"""
|
||||
self._do_test_put_image_content_missing_format('container_format')
|
||||
|
||||
def test_register_and_upload(self):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user