2.45: Remove Location header from createImage and createBackup responses
This changes the response for the createImage and createBackup server action APIs to no longer return a Location header and instead returns a json dict body with the snapshot image ID. This is done in a new microversion. Implements blueprint remove-create-image-location-header-response Closes-Bug: #1679285 Change-Id: Idc899ee76b8265b1c9e0871b6c7c277424cdd442
This commit is contained in:
parent
ec1794b18c
commit
66b0cf3337
@ -3,9 +3,14 @@ image_location:
|
|||||||
description: |
|
description: |
|
||||||
The image location URL of the image or backup created, HTTP header
|
The image location URL of the image or backup created, HTTP header
|
||||||
"Location: <image location URL>" will be returned.
|
"Location: <image location URL>" will be returned.
|
||||||
|
|
||||||
|
.. note:: The URL returned may not be accessible to users and should not
|
||||||
|
be relied upon. Use microversion 2.45 or simply parse the image ID out
|
||||||
|
of the URL in the Location response header.
|
||||||
in: header
|
in: header
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
max_version: 2.44
|
||||||
tag_location:
|
tag_location:
|
||||||
description: |
|
description: |
|
||||||
The location of the tag. It's individual tag URL which can be used for
|
The location of the tag. It's individual tag URL which can be used for
|
||||||
@ -4582,6 +4587,13 @@ snapshot_id_optional:
|
|||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
snapshot_id_resp_2_45:
|
||||||
|
description: |
|
||||||
|
The UUID for the resulting image snapshot.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
min_version: 2.45
|
||||||
snapshot_name:
|
snapshot_name:
|
||||||
description: |
|
description: |
|
||||||
The snapshot name.
|
The snapshot name.
|
||||||
|
@ -244,7 +244,7 @@ Request
|
|||||||
- rotation: backup_rotation
|
- rotation: backup_rotation
|
||||||
- metadata: metadata
|
- metadata: metadata
|
||||||
|
|
||||||
**Example Create Server Back Up (Createbackup Action)**
|
**Example Create Server Back Up (createBackup Action)**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-create-backup/create-backup-req.json
|
.. literalinclude:: ../../doc/api_samples/os-create-backup/create-backup-req.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
@ -254,7 +254,13 @@ Response
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- image_location: image_location
|
- Location: image_location
|
||||||
|
- image_id: snapshot_id_resp_2_45
|
||||||
|
|
||||||
|
**Example Create Server Back Up (v2.45)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-create-backup/v2.45/create-backup-resp.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
Create Image (createImage Action)
|
Create Image (createImage Action)
|
||||||
@ -329,6 +335,12 @@ Response
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- Location: image_location
|
- Location: image_location
|
||||||
|
- image_id: snapshot_id_resp_2_45
|
||||||
|
|
||||||
|
**Example Create Image (v2.45)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/servers/v2.45/server-action-create-image-resp.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
Lock Server (lock Action)
|
Lock Server (lock Action)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"createBackup": {
|
||||||
|
"name": "Backup 1",
|
||||||
|
"backup_type": "weekly",
|
||||||
|
"rotation": 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"image_id": "0e7761dd-ee98-41f0-ba35-05994e446431"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"image_id": "0e7761dd-ee98-41f0-ba35-05994e446431"
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"createImage" : {
|
||||||
|
"name" : "foo-image",
|
||||||
|
"metadata": {
|
||||||
|
"meta_var": "meta_val"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.44",
|
"version": "2.45",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.44",
|
"version": "2.45",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,10 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 2.43 - Deprecate os-hosts API
|
* 2.43 - Deprecate os-hosts API
|
||||||
* 2.44 - The servers action addFixedIp, removeFixedIp, addFloatingIp,
|
* 2.44 - The servers action addFixedIp, removeFixedIp, addFloatingIp,
|
||||||
removeFloatingIp and os-virtual-interfaces APIs are deprecated.
|
removeFloatingIp and os-virtual-interfaces APIs are deprecated.
|
||||||
|
* 2.45 - The createImage and createBackup APIs no longer return a Location
|
||||||
|
header in the response for the snapshot image, they now return a
|
||||||
|
json dict in the response body with an image_id key and uuid
|
||||||
|
value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
@ -113,7 +117,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||||
# support is fully merged. It does not affect the V2 API.
|
# support is fully merged. It does not affect the V2 API.
|
||||||
_MIN_API_VERSION = "2.1"
|
_MIN_API_VERSION = "2.1"
|
||||||
_MAX_API_VERSION = "2.44"
|
_MAX_API_VERSION = "2.45"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
# Almost all proxy APIs which related to network, images and baremetal
|
# Almost all proxy APIs which related to network, images and baremetal
|
||||||
|
@ -33,6 +33,7 @@ class CreateBackupController(wsgi.Controller):
|
|||||||
super(CreateBackupController, self).__init__(*args, **kwargs)
|
super(CreateBackupController, self).__init__(*args, **kwargs)
|
||||||
self.compute_api = compute.API()
|
self.compute_api = compute.API()
|
||||||
|
|
||||||
|
@wsgi.response(202)
|
||||||
@extensions.expected_errors((400, 403, 404, 409))
|
@extensions.expected_errors((400, 403, 404, 409))
|
||||||
@wsgi.action('createBackup')
|
@wsgi.action('createBackup')
|
||||||
@validation.schema(create_backup.create_backup_v20, '2.0', '2.0')
|
@validation.schema(create_backup.create_backup_v20, '2.0', '2.0')
|
||||||
@ -78,6 +79,11 @@ class CreateBackupController(wsgi.Controller):
|
|||||||
except exception.InvalidRequest as e:
|
except exception.InvalidRequest as e:
|
||||||
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
||||||
|
|
||||||
|
# Starting with microversion 2.45 we return a response body containing
|
||||||
|
# the snapshot image id without the Location header.
|
||||||
|
if api_version_request.is_supported(req, '2.45'):
|
||||||
|
return {'image_id': image['id']}
|
||||||
|
|
||||||
resp = webob.Response(status_int=202)
|
resp = webob.Response(status_int=202)
|
||||||
|
|
||||||
# build location of newly-created image entity if rotation is not zero
|
# build location of newly-created image entity if rotation is not zero
|
||||||
|
@ -529,3 +529,10 @@ user documentation.
|
|||||||
|
|
||||||
To query attached neutron interfaces for a specific server, the API
|
To query attached neutron interfaces for a specific server, the API
|
||||||
`GET /servers/{server_uuid}/os-interface` can be used.
|
`GET /servers/{server_uuid}/os-interface` can be used.
|
||||||
|
|
||||||
|
2.45
|
||||||
|
----
|
||||||
|
|
||||||
|
The ``createImage`` and ``createBackup`` server action APIs no longer return
|
||||||
|
a ``Location`` header in the response for the snapshot image, they now return
|
||||||
|
a json dict in the response body with an ``image_id`` key and uuid value.
|
||||||
|
@ -1017,6 +1017,11 @@ class ServersController(wsgi.Controller):
|
|||||||
except exception.Invalid as err:
|
except exception.Invalid as err:
|
||||||
raise exc.HTTPBadRequest(explanation=err.format_message())
|
raise exc.HTTPBadRequest(explanation=err.format_message())
|
||||||
|
|
||||||
|
# Starting with microversion 2.45 we return a response body containing
|
||||||
|
# the snapshot image id without the Location header.
|
||||||
|
if api_version_request.is_supported(req, '2.45'):
|
||||||
|
return {'image_id': image['id']}
|
||||||
|
|
||||||
# build location of newly-created image entity
|
# build location of newly-created image entity
|
||||||
image_id = str(image['id'])
|
image_id = str(image['id'])
|
||||||
image_ref = glance.generate_image_url(image_id)
|
image_ref = glance.generate_image_url(image_id)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"createBackup": {
|
||||||
|
"name": "Backup 1",
|
||||||
|
"backup_type": "weekly",
|
||||||
|
"rotation": 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"image_id": "%(uuid)s"
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"image_id": "%(uuid)s"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"createImage" : {
|
||||||
|
"name" : "%(name)s",
|
||||||
|
"metadata": {
|
||||||
|
"meta_var": "meta_val"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,3 +36,21 @@ class CreateBackupSamplesJsonTest(test_servers.ServersSampleBase):
|
|||||||
response = self._do_post('servers/%s/action' % self.uuid,
|
response = self._do_post('servers/%s/action' % self.uuid,
|
||||||
'create-backup-req', {})
|
'create-backup-req', {})
|
||||||
self.assertEqual(202, response.status_code)
|
self.assertEqual(202, response.status_code)
|
||||||
|
# we should have gotten a location header back
|
||||||
|
self.assertIn('location', response.headers)
|
||||||
|
# we should not have gotten a body back
|
||||||
|
self.assertEqual(0, len(response.content))
|
||||||
|
|
||||||
|
|
||||||
|
class CreateBackupSamplesJsonTestv2_45(CreateBackupSamplesJsonTest):
|
||||||
|
"""Tests the createBackup server action API with microversion 2.45."""
|
||||||
|
microversion = '2.45'
|
||||||
|
scenarios = [('v2_45', {'api_major_version': 'v2.1'})]
|
||||||
|
|
||||||
|
def test_post_backup_server(self):
|
||||||
|
# Get api samples to backup server request.
|
||||||
|
response = self._do_post('servers/%s/action' % self.uuid,
|
||||||
|
'create-backup-req', {})
|
||||||
|
self._verify_response('create-backup-resp', {}, response, 202)
|
||||||
|
# assert that no location header was returned
|
||||||
|
self.assertNotIn('location', response.headers)
|
||||||
|
@ -225,8 +225,7 @@ class ServerSortKeysJsonTests(ServersSampleBase):
|
|||||||
200)
|
200)
|
||||||
|
|
||||||
|
|
||||||
class ServersActionsJsonTest(ServersSampleBase):
|
class _ServersActionsJsonTestMixin(object):
|
||||||
|
|
||||||
def _test_server_action(self, uuid, action, req_tpl,
|
def _test_server_action(self, uuid, action, req_tpl,
|
||||||
subs=None, resp_tpl=None, code=202):
|
subs=None, resp_tpl=None, code=202):
|
||||||
subs = subs or {}
|
subs = subs or {}
|
||||||
@ -240,6 +239,10 @@ class ServersActionsJsonTest(ServersSampleBase):
|
|||||||
else:
|
else:
|
||||||
self.assertEqual(code, response.status_code)
|
self.assertEqual(code, response.status_code)
|
||||||
self.assertEqual("", response.text)
|
self.assertEqual("", response.text)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class ServersActionsJsonTest(ServersSampleBase, _ServersActionsJsonTestMixin):
|
||||||
|
|
||||||
def test_server_reboot_hard(self):
|
def test_server_reboot_hard(self):
|
||||||
uuid = self._post_server()
|
uuid = self._post_server()
|
||||||
@ -291,12 +294,6 @@ class ServersActionsJsonTest(ServersSampleBase):
|
|||||||
'server-action-confirm-resize',
|
'server-action-confirm-resize',
|
||||||
code=204)
|
code=204)
|
||||||
|
|
||||||
def test_server_create_image(self):
|
|
||||||
uuid = self._post_server()
|
|
||||||
self._test_server_action(uuid, 'createImage',
|
|
||||||
'server-action-create-image',
|
|
||||||
{'name': 'foo-image'})
|
|
||||||
|
|
||||||
def _wait_for_active_server(self, uuid):
|
def _wait_for_active_server(self, uuid):
|
||||||
"""Wait 10 seconds for the server to be ACTIVE, else fail.
|
"""Wait 10 seconds for the server to be ACTIVE, else fail.
|
||||||
|
|
||||||
@ -370,6 +367,34 @@ class ServersActionsJson219Test(ServersSampleBase):
|
|||||||
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
|
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
|
||||||
|
|
||||||
|
|
||||||
|
class ServersCreateImageJsonTest(ServersSampleBase,
|
||||||
|
_ServersActionsJsonTestMixin):
|
||||||
|
"""Tests the createImage server action API against 2.1."""
|
||||||
|
def test_server_create_image(self):
|
||||||
|
uuid = self._post_server()
|
||||||
|
resp = self._test_server_action(uuid, 'createImage',
|
||||||
|
'server-action-create-image',
|
||||||
|
{'name': 'foo-image'})
|
||||||
|
# we should have gotten a location header back
|
||||||
|
self.assertIn('location', resp.headers)
|
||||||
|
# we should not have gotten a body back
|
||||||
|
self.assertEqual(0, len(resp.content))
|
||||||
|
|
||||||
|
|
||||||
|
class ServersCreateImageJsonTestv2_45(ServersCreateImageJsonTest):
|
||||||
|
"""Tests the createImage server action API against 2.45."""
|
||||||
|
microversion = '2.45'
|
||||||
|
scenarios = [('v2_45', {'api_major_version': 'v2.1'})]
|
||||||
|
|
||||||
|
def test_server_create_image(self):
|
||||||
|
uuid = self._post_server()
|
||||||
|
resp = self._test_server_action(
|
||||||
|
uuid, 'createImage', 'server-action-create-image',
|
||||||
|
{'name': 'foo-image'}, 'server-action-create-image-resp')
|
||||||
|
# assert that no location header was returned
|
||||||
|
self.assertNotIn('location', resp.headers)
|
||||||
|
|
||||||
|
|
||||||
class ServerStartStopJsonTest(ServersSampleBase):
|
class ServerStartStopJsonTest(ServersSampleBase):
|
||||||
|
|
||||||
def _test_server_action(self, uuid, action, req_tpl):
|
def _test_server_action(self, uuid, action, req_tpl):
|
||||||
|
@ -283,6 +283,27 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
|
|||||||
self.assertEqual(202, res.status_int)
|
self.assertEqual(202, res.status_int)
|
||||||
self.assertIn('fake-image-id', res.headers['Location'])
|
self.assertIn('fake-image-id', res.headers['Location'])
|
||||||
|
|
||||||
|
@mock.patch.object(common, 'check_img_metadata_properties_quota')
|
||||||
|
@mock.patch.object(api.API, 'backup', return_value=dict(
|
||||||
|
id='fake-image-id', status='ACTIVE', name='Backup 1', properties={}))
|
||||||
|
def test_create_backup_v2_45(self, mock_backup, mock_check_image):
|
||||||
|
"""Tests the 2.45 microversion to ensure the Location header is not
|
||||||
|
in the response.
|
||||||
|
"""
|
||||||
|
body = {
|
||||||
|
'createBackup': {
|
||||||
|
'name': 'Backup 1',
|
||||||
|
'backup_type': 'daily',
|
||||||
|
'rotation': '1',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
instance = fake_instance.fake_instance_obj(self.context)
|
||||||
|
self.mock_get.return_value = instance
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.45')
|
||||||
|
res = self.controller._create_backup(req, instance['uuid'], body=body)
|
||||||
|
self.assertIsInstance(res, dict)
|
||||||
|
self.assertEqual('fake-image-id', res['image_id'])
|
||||||
|
|
||||||
@mock.patch.object(common, 'check_img_metadata_properties_quota')
|
@mock.patch.object(common, 'check_img_metadata_properties_quota')
|
||||||
@mock.patch.object(api.API, 'backup')
|
@mock.patch.object(api.API, 'backup')
|
||||||
def test_create_backup_raises_conflict_on_invalid_state(self,
|
def test_create_backup_raises_conflict_on_invalid_state(self,
|
||||||
|
@ -901,6 +901,21 @@ class ServerActionsControllerTestV21(test.TestCase):
|
|||||||
glance.generate_image_url('123'),
|
glance.generate_image_url('123'),
|
||||||
location)
|
location)
|
||||||
|
|
||||||
|
def test_create_image_v2_45(self):
|
||||||
|
"""Tests the createImage server action API with the 2.45 microversion
|
||||||
|
where there is a response body but no Location header.
|
||||||
|
"""
|
||||||
|
body = {
|
||||||
|
'createImage': {
|
||||||
|
'name': 'Snapshot 1',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.45')
|
||||||
|
response = self.controller._action_create_image(req, FAKE_UUID,
|
||||||
|
body=body)
|
||||||
|
self.assertIsInstance(response, dict)
|
||||||
|
self.assertEqual('123', response['image_id'])
|
||||||
|
|
||||||
def test_create_image_name_too_long(self):
|
def test_create_image_name_too_long(self):
|
||||||
long_name = 'a' * 260
|
long_name = 'a' * 260
|
||||||
body = {
|
body = {
|
||||||
|
11
releasenotes/notes/microversion-2.45-608ba80a84c8aec8.yaml
Normal file
11
releasenotes/notes/microversion-2.45-608ba80a84c8aec8.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
The 2.45 microversion is introduced which changes the response for the
|
||||||
|
``createImage`` and ``createBackup`` server action APIs to no longer
|
||||||
|
return a ``Location`` response header. With microversion 2.45 those APIs
|
||||||
|
now return a json dict in the response body with a single ``image_id`` key
|
||||||
|
whose value is the snapshot image ID (a uuid). The old ``Location`` header
|
||||||
|
in the response before microversion 2.45 is most likely broken and
|
||||||
|
inaccessible by end users since it relies on the internal Glance API
|
||||||
|
server configuration and does not take into account Glance API versions.
|
Loading…
Reference in New Issue
Block a user