Merge "Prevent image status being directly modified via v1"
This commit is contained in:
commit
c7f01f07c6
@ -21,3 +21,6 @@ SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
|
||||
|
||||
# Metadata which only an admin can change once the image is active
|
||||
ACTIVE_IMMUTABLE = ('size', 'checksum')
|
||||
|
||||
# Metadata which cannot be changed (irrespective of the current image state)
|
||||
IMMUTABLE = ('status',)
|
||||
|
@ -58,6 +58,7 @@ _LW = i18n._LW
|
||||
SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
|
||||
SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
|
||||
ACTIVE_IMMUTABLE = glance.api.v1.ACTIVE_IMMUTABLE
|
||||
IMMUTABLE = glance.api.v1.IMMUTABLE
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('disk_formats', 'glance.common.config', group='image_format')
|
||||
@ -940,6 +941,14 @@ class Controller(controller.BaseController):
|
||||
request=req,
|
||||
content_type="text/plain")
|
||||
|
||||
for key in IMMUTABLE:
|
||||
if (image_meta.get(key) is not None and
|
||||
image_meta.get(key) != orig_image_meta.get(key)):
|
||||
msg = _("Forbidden to modify '%s' of image.") % key
|
||||
raise HTTPForbidden(explanation=msg,
|
||||
request=req,
|
||||
content_type="text/plain")
|
||||
|
||||
# The default behaviour for a PUT /images/<IMAGE_ID> is to
|
||||
# override any properties that were previously set. This, however,
|
||||
# leads to a number of issues for the common use case where a caller
|
||||
|
@ -761,3 +761,92 @@ class TestApi(functional.FunctionalTest):
|
||||
self.assertEqual(404, response.status)
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
def test_status_cannot_be_manipulated_directly(self):
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
headers = minimal_headers('Image1')
|
||||
|
||||
# Create a 'queued' image
|
||||
http = httplib2.Http()
|
||||
headers = {'Content-Type': 'application/octet-stream',
|
||||
'X-Image-Meta-Disk-Format': 'raw',
|
||||
'X-Image-Meta-Container-Format': 'bare'}
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=None)
|
||||
self.assertEqual(201, response.status)
|
||||
image = jsonutils.loads(content)['image']
|
||||
self.assertEqual('queued', image['status'])
|
||||
|
||||
# Ensure status of 'queued' image can't be changed
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image['id'])
|
||||
http = httplib2.Http()
|
||||
headers = {'X-Image-Meta-Status': 'active'}
|
||||
response, content = http.request(path, 'PUT', headers=headers)
|
||||
self.assertEqual(403, response.status)
|
||||
response, content = http.request(path, 'HEAD')
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual('queued', response['x-image-meta-status'])
|
||||
|
||||
# We allow 'setting' to the same status
|
||||
http = httplib2.Http()
|
||||
headers = {'X-Image-Meta-Status': 'queued'}
|
||||
response, content = http.request(path, 'PUT', headers=headers)
|
||||
self.assertEqual(200, response.status)
|
||||
response, content = http.request(path, 'HEAD')
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual('queued', response['x-image-meta-status'])
|
||||
|
||||
# Make image active
|
||||
http = httplib2.Http()
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
response, content = http.request(path, 'PUT', headers=headers,
|
||||
body='data')
|
||||
self.assertEqual(200, response.status)
|
||||
image = jsonutils.loads(content)['image']
|
||||
self.assertEqual('active', image['status'])
|
||||
|
||||
# Ensure status of 'active' image can't be changed
|
||||
http = httplib2.Http()
|
||||
headers = {'X-Image-Meta-Status': 'queued'}
|
||||
response, content = http.request(path, 'PUT', headers=headers)
|
||||
self.assertEqual(403, response.status)
|
||||
response, content = http.request(path, 'HEAD')
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual('active', response['x-image-meta-status'])
|
||||
|
||||
# We allow 'setting' to the same status
|
||||
http = httplib2.Http()
|
||||
headers = {'X-Image-Meta-Status': 'active'}
|
||||
response, content = http.request(path, 'PUT', headers=headers)
|
||||
self.assertEqual(200, response.status)
|
||||
response, content = http.request(path, 'HEAD')
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual('active', response['x-image-meta-status'])
|
||||
|
||||
# Create a 'queued' image, ensure 'status' header is ignored
|
||||
http = httplib2.Http()
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
headers = {'Content-Type': 'application/octet-stream',
|
||||
'X-Image-Meta-Status': 'active'}
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=None)
|
||||
self.assertEqual(201, response.status)
|
||||
image = jsonutils.loads(content)['image']
|
||||
self.assertEqual('queued', image['status'])
|
||||
|
||||
# Create an 'active' image, ensure 'status' header is ignored
|
||||
http = httplib2.Http()
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
headers = {'Content-Type': 'application/octet-stream',
|
||||
'X-Image-Meta-Disk-Format': 'raw',
|
||||
'X-Image-Meta-Status': 'queued',
|
||||
'X-Image-Meta-Container-Format': 'bare'}
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body='data')
|
||||
self.assertEqual(201, response.status)
|
||||
image = jsonutils.loads(content)['image']
|
||||
self.assertEqual('active', image['status'])
|
||||
self.stop_servers()
|
||||
|
@ -358,6 +358,8 @@ class TestApi(base.ApiTest):
|
||||
path = "/v1/images"
|
||||
response, content = self.http.request(path, 'POST', headers=headers)
|
||||
self.assertEqual(201, response.status)
|
||||
image = jsonutils.loads(content)['image']
|
||||
self.assertEqual('active', image['status'])
|
||||
|
||||
# 2. HEAD image-location
|
||||
# Verify image size is zero and the status is active
|
||||
|
Loading…
Reference in New Issue
Block a user