Add delete_on_termination to volume-attach API

Add the 'delete_on_termination' field to the volume attach API to support
configuring whether to delete the data volume when the instance is destroyed.

To avoid upgrade impact issues with older computes, the
'delete_on_termination' field is set in the API rather than when the BDM
is created in the compute service.

Implements: blueprint support-delete-on-termination-in-server-attach-volume
Change-Id: I55731b1822a4e32909665a2872d80895cb5b12f7
This commit is contained in:
zhangbailin 2019-07-27 18:16:36 +08:00
parent f7f5e1846c
commit e5b47543cf
24 changed files with 348 additions and 29 deletions

View File

@ -39,6 +39,7 @@ Response
- serverId: server_id
- volumeId: volumeId_resp
- tag: device_tag_bdm_attachment_resp
- delete_on_termination: delete_on_termination_attachments_resp
**Example List volume attachments for an instance: JSON response**
@ -79,6 +80,7 @@ Request
- volumeId: volumeId
- device: device
- tag: device_tag_bdm_attachment
- delete_on_termination: delete_on_termination_attachments_req
**Example Attach a volume to an instance: JSON request**
@ -90,6 +92,11 @@ Request
.. literalinclude:: ../../doc/api_samples/os-volumes/v2.49/attach-volume-to-server-req.json
:language: javascript
**Example Attach a volume to an instance with "delete_on_termination" (v2.79): JSON request**
.. literalinclude:: ../../doc/api_samples/os-volumes/v2.79/attach-volume-to-server-req.json
:language: javascript
Response
--------
@ -101,6 +108,7 @@ Response
- serverId: server_id
- volumeId: volumeId_resp
- tag: device_tag_bdm_attachment_resp
- delete_on_termination: delete_on_termination_attachments_resp
**Example Attach a volume to an instance: JSON response**
@ -112,6 +120,11 @@ Response
.. literalinclude:: ../../doc/api_samples/os-volumes/v2.70/attach-volume-to-server-resp.json
:language: javascript
**Example Attach a volume with "delete_on_termination" (v2.79): JSON response**
.. literalinclude:: ../../doc/api_samples/os-volumes/v2.79/attach-volume-to-server-resp.json
:language: javascript
Show a detail of a volume attachment
====================================
@ -142,6 +155,7 @@ Response
- serverId: server_id
- volumeId: volumeId_resp
- tag: device_tag_bdm_attachment_resp
- delete_on_termination: delete_on_termination_attachments_resp
**Example Show a detail of a volume attachment: JSON response**

View File

@ -2248,6 +2248,22 @@ delete_on_termination:
in: body
required: false
type: boolean
delete_on_termination_attachments_req:
description: |
To delete the attached volume when the server is destroyed, specify ``true``.
Otherwise, specify ``false``. Default: ``false``
in: body
required: false
type: boolean
min_version: 2.79
delete_on_termination_attachments_resp:
description: |
A flag indicating if the attached volume will be deleted when the server is
deleted.
in: body
required: true
type: boolean
min_version: 2.79
deleted:
description: |
A boolean indicates whether this aggregate is deleted or not, if it has
@ -5148,9 +5164,7 @@ os-extended-volumes:volumes_attached:
os-extended-volumes:volumes_attached.delete_on_termination:
description: |
A flag indicating if the attached volume will be deleted
when the server is deleted. By default this is False and
can only be set when creating a volume while creating a
server, which is commonly referred to as boot from volume.
when the server is deleted. By default this is False.
in: body
required: true
type: boolean
@ -5158,9 +5172,7 @@ os-extended-volumes:volumes_attached.delete_on_termination:
os-extended-volumes:volumes_attached.delete_on_termination_update_rebuild:
description: |
A flag indicating if the attached volume will be deleted
when the server is deleted. By default this is False and
can only be set when creating a volume while creating a
server, which is commonly referred to as boot from volume.
when the server is deleted. By default this is False.
in: body
required: true
type: boolean

View File

@ -0,0 +1,7 @@
{
"volumeAttachment": {
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"tag": "foo",
"delete_on_termination": true
}
}

View File

@ -0,0 +1,10 @@
{
"volumeAttachment": {
"device": "/dev/vdd",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"serverId": "521b9e49-4855-4a9a-8474-759a86c1b12d",
"tag": "foo",
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"delete_on_termination": true
}
}

View File

@ -0,0 +1,20 @@
{
"volumeAttachments": [
{
"device": "/dev/sdd",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"serverId": "fb6077e6-c10d-4e81-87fa-cb0f8c103051",
"tag": "foo",
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"delete_on_termination": false
},
{
"device": "/dev/sdc",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f804",
"serverId": "fb6077e6-c10d-4e81-87fa-cb0f8c103051",
"tag": null,
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804",
"delete_on_termination": false
}
]
}

View File

@ -0,0 +1,5 @@
{
"volumeAttachment": {
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f805"
}
}

View File

@ -0,0 +1,10 @@
{
"volumeAttachment": {
"device": "/dev/sdd",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"serverId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"tag": "foo",
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"delete_on_termination": false
}
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.78",
"version": "2.79",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.78",
"version": "2.79",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -205,6 +205,12 @@ REST_API_VERSION_HISTORY = """REST API Version History:
shelved offload server.
* 2.78 - Adds new API ``GET /servers/{server_id}/topology`` which shows
NUMA topology of a given server.
* 2.79 - Adds support for specifying ``delete_on_termination`` field in the
request body to
``POST /servers/{server_id}/os-volume_attachments`` and exposes
this via the response from
``GET /servers/{server_id}/os-volume_attachments`` and
``GET /servers/{server_id}/os-volume_attachments/{volume_id}``.
"""
# The minimum and maximum versions of the API supported
@ -213,7 +219,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.78"
_MAX_API_VERSION = "2.79"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -1011,3 +1011,18 @@ The default behavior is configurable using two new policies:
* ``compute:server:topology:index``
* ``compute:server:topology:host:index``
2.79
----
API microversion 2.79 adds support for specifying the ``delete_on_termination``
field in the request body when attaching a volume to a server, to support
configuring whether to delete the data volume when the server is destroyed.
Also, ``delete_on_termination`` is added to the GET responses when showing
attached volumes.
The affected APIs are as follows:
* ``POST /servers/{server_id}/os-volume_attachments``
* ``GET /servers/{server_id}/os-volume_attachments``
* ``GET /servers/{server_id}/os-volume_attachments/{volume_id}``

View File

@ -87,6 +87,10 @@ create_volume_attachment_v249 = copy.deepcopy(create_volume_attachment)
create_volume_attachment_v249['properties']['volumeAttachment'][
'properties']['tag'] = parameter_types.tag
create_volume_attachment_v279 = copy.deepcopy(create_volume_attachment_v249)
create_volume_attachment_v279['properties']['volumeAttachment'][
'properties']['delete_on_termination'] = parameter_types.boolean
update_volume_attachment = copy.deepcopy(create_volume_attachment)
del update_volume_attachment['properties']['volumeAttachment'][
'properties']['device']

View File

@ -207,12 +207,16 @@ class VolumeController(wsgi.Controller):
return wsgi.ResponseObject(result, headers=dict(location=location))
def _translate_attachment_detail_view(bdm, show_tag=False):
def _translate_attachment_detail_view(bdm, show_tag=False,
show_delete_on_termination=False):
"""Maps keys for attachment details view.
:param bdm: BlockDeviceMapping object for an attached volume
:param show_tag: True if the "tag" field should be in the response, False
to exclude the "tag" field from the response
:param show_delete_on_termination: True if the "delete_on_termination"
field should be in the response, False to exclude the
"delete_on_termination" field from the response
"""
d = _translate_attachment_summary_view(
@ -221,6 +225,9 @@ def _translate_attachment_detail_view(bdm, show_tag=False):
if show_tag:
d['tag'] = bdm.tag
if show_delete_on_termination:
d['delete_on_termination'] = bdm.delete_on_termination
return d
@ -282,9 +289,13 @@ class VolumeAttachmentController(wsgi.Controller):
results = []
show_tag = api_version_request.is_supported(req, '2.70')
show_delete_on_termination = api_version_request.is_supported(
req, '2.79')
for bdm in limited_list:
if bdm.volume_id:
va = _translate_attachment_detail_view(bdm, show_tag=show_tag)
va = _translate_attachment_detail_view(
bdm, show_tag=show_tag,
show_delete_on_termination=show_delete_on_termination)
results.append(va)
return {'volumeAttachments': results}
@ -308,13 +319,18 @@ class VolumeAttachmentController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=msg)
show_tag = api_version_request.is_supported(req, '2.70')
show_delete_on_termination = api_version_request.is_supported(
req, '2.79')
return {'volumeAttachment': _translate_attachment_detail_view(
bdm, show_tag=show_tag)}
bdm, show_tag=show_tag,
show_delete_on_termination=show_delete_on_termination)}
# TODO(mriedem): This API should return a 202 instead of a 200 response.
@wsgi.expected_errors((400, 403, 404, 409))
@validation.schema(volumes_schema.create_volume_attachment, '2.0', '2.48')
@validation.schema(volumes_schema.create_volume_attachment_v249, '2.49')
@validation.schema(volumes_schema.create_volume_attachment_v249, '2.49',
'2.78')
@validation.schema(volumes_schema.create_volume_attachment_v279, '2.79')
def create(self, req, server_id, body):
"""Attach a volume to an instance."""
context = req.environ['nova.context']
@ -323,6 +339,8 @@ class VolumeAttachmentController(wsgi.Controller):
volume_id = body['volumeAttachment']['volumeId']
device = body['volumeAttachment'].get('device')
tag = body['volumeAttachment'].get('tag')
delete_on_termination = body['volumeAttachment'].get(
'delete_on_termination', False)
instance = common.get_instance(self.compute_api, context, server_id)
@ -335,7 +353,8 @@ class VolumeAttachmentController(wsgi.Controller):
supports_multiattach = common.supports_multiattach_volume(req)
device = self.compute_api.attach_volume(
context, instance, volume_id, device, tag=tag,
supports_multiattach=supports_multiattach)
supports_multiattach=supports_multiattach,
delete_on_termination=delete_on_termination)
except exception.VolumeNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message())
except (exception.InstanceIsLocked,
@ -367,6 +386,8 @@ class VolumeAttachmentController(wsgi.Controller):
attachment['device'] = device
if api_version_request.is_supported(req, '2.70'):
attachment['tag'] = tag
if api_version_request.is_supported(req, '2.79'):
attachment['delete_on_termination'] = delete_on_termination
return {'volumeAttachment': attachment}
@wsgi.response(202)

View File

@ -3977,7 +3977,7 @@ class API(base.Base):
def _create_volume_bdm(self, context, instance, device, volume,
disk_bus, device_type, is_local_creation=False,
tag=None):
tag=None, delete_on_termination=False):
volume_id = volume['id']
if is_local_creation:
# when the creation is done locally we can't specify the device
@ -3996,7 +3996,8 @@ class API(base.Base):
instance_uuid=instance.uuid, boot_index=None,
volume_id=volume_id,
device_name=None, guest_format=None,
disk_bus=disk_bus, device_type=device_type)
disk_bus=disk_bus, device_type=device_type,
delete_on_termination=delete_on_termination)
volume_bdm.create()
else:
# NOTE(vish): This is done on the compute host because we want
@ -4008,6 +4009,8 @@ class API(base.Base):
context, instance, device, volume_id, disk_bus=disk_bus,
device_type=device_type, tag=tag,
multiattach=volume['multiattach'])
volume_bdm.delete_on_termination = delete_on_termination
volume_bdm.save()
return volume_bdm
def _check_volume_already_attached_to_instance(self, context, instance,
@ -4056,7 +4059,8 @@ class API(base.Base):
# override it.
def _attach_volume(self, context, instance, volume, device,
disk_bus, device_type, tag=None,
supports_multiattach=False):
supports_multiattach=False,
delete_on_termination=False):
"""Attach an existing volume to an existing instance.
This method is separated to make it possible for cells version
@ -4064,7 +4068,8 @@ class API(base.Base):
"""
volume_bdm = self._create_volume_bdm(
context, instance, device, volume, disk_bus=disk_bus,
device_type=device_type, tag=tag)
device_type=device_type, tag=tag,
delete_on_termination=delete_on_termination)
try:
self._check_attach_and_reserve_volume(context, volume, instance,
volume_bdm,
@ -4079,7 +4084,8 @@ class API(base.Base):
return volume_bdm.device_name
def _attach_volume_shelved_offloaded(self, context, instance, volume,
device, disk_bus, device_type):
device, disk_bus, device_type,
delete_on_termination):
"""Attach an existing volume to an instance in shelved offloaded state.
Attaching a volume for an instance in shelved offloaded state requires
@ -4109,7 +4115,8 @@ class API(base.Base):
volume_bdm = self._create_volume_bdm(
context, instance, device, volume, disk_bus=disk_bus,
device_type=device_type, is_local_creation=True)
device_type=device_type, is_local_creation=True,
delete_on_termination=delete_on_termination)
try:
self._check_attach_and_reserve_volume(context, volume, instance,
volume_bdm)
@ -4131,7 +4138,8 @@ class API(base.Base):
vm_states.SHELVED_OFFLOADED])
def attach_volume(self, context, instance, volume_id, device=None,
disk_bus=None, device_type=None, tag=None,
supports_multiattach=False):
supports_multiattach=False,
delete_on_termination=False):
"""Attach an existing volume to an existing instance."""
# NOTE(vish): Fail fast if the device is not going to pass. This
# will need to be removed along with the test if we
@ -4172,11 +4180,13 @@ class API(base.Base):
volume,
device,
disk_bus,
device_type)
device_type,
delete_on_termination)
return self._attach_volume(context, instance, volume, device,
disk_bus, device_type, tag=tag,
supports_multiattach=supports_multiattach)
supports_multiattach=supports_multiattach,
delete_on_termination=delete_on_termination)
# TODO(stephenfin): Fold this back in now that cells v1 no longer needs to
# override it.

View File

@ -0,0 +1,7 @@
{
"volumeAttachment": {
"volumeId": "%(volume_id)s",
"tag": "%(tag)s",
"delete_on_termination": true
}
}

View File

@ -0,0 +1,10 @@
{
"volumeAttachment": {
"device": "%(device)s",
"id": "%(volume_id)s",
"serverId": "%(uuid)s",
"tag": "%(tag)s",
"volumeId": "%(volume_id)s",
"delete_on_termination": true
}
}

View File

@ -0,0 +1,20 @@
{
"volumeAttachments": [
{
"device": "/dev/sdd",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"serverId": "%(uuid)s",
"tag": "%(tag)s",
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"delete_on_termination": false
},
{
"device": "/dev/sdc",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f804",
"serverId": "%(uuid)s",
"tag": null,
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804",
"delete_on_termination": false
}
]
}

View File

@ -0,0 +1,5 @@
{
"volumeAttachment": {
"volumeId": "%(volume_id)s"
}
}

View File

@ -0,0 +1,10 @@
{
"volumeAttachment": {
"device": "/dev/sdd",
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"serverId": "%(uuid)s",
"tag": "%(tag)s",
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
"delete_on_termination": false
}
}

View File

@ -209,6 +209,11 @@ class VolumeAttachmentsSample(test_servers.ServersSampleBase):
"""
return {}
# TODO(mriedem): There is really no good reason we should have to stub
# so much of this DB and compute service code when we can just use the
# CinderFixture. The stubs make these tests very brittle and potentially
# false regarding how the API/compute service interaction works.
def _stub_db_bdms_get_all_by_instance(self, server_id):
def fake_bdms_get_all_by_instance(context, instance_uuid,
@ -240,7 +245,7 @@ class VolumeAttachmentsSample(test_servers.ServersSampleBase):
{'id': 1, 'volume_id': self.OLD_VOLUME_ID,
'instance_uuid': instance_uuid, 'source_type': 'volume',
'destination_type': 'volume', 'device_name': '/dev/sdd',
'tag': tag})
'tag': tag, 'delete_on_termination': False})
)
def _stub_compute_api_get(self):
@ -264,9 +269,14 @@ class VolumeAttachmentsSample(test_servers.ServersSampleBase):
device_name = '/dev/vdd'
bdm = objects.BlockDeviceMapping()
bdm['device_name'] = device_name
bdm['delete_on_termination'] = True
self.stub_out(
'nova.compute.manager.ComputeManager.reserve_block_device_name',
lambda *a, **k: bdm)
# 2.79+ will save the delete_on_termination value on the BDM after
# reserve_block_device_name "creates" the BDM.
self.stub_out('nova.objects.BlockDeviceMapping.save',
lambda *a, **k: None)
self.stub_out(
'nova.compute.manager.ComputeManager.attach_volume',
lambda *a, **k: None)
@ -366,3 +376,11 @@ class VolumeAttachmentsSampleV270(VolumeAttachmentsSampleV249):
self.OLD_VOLUME_ID: 'foo',
self.NEW_VOLUME_ID: None
}
class VolumeAttachmentsSampleV279(VolumeAttachmentsSampleV270):
"""Microversion 2.79 adds the "delete_on_termination" parameter to the
request and response body.
"""
microversion = '2.79'
scenarios = [('v2_79', {'api_major_version': 'v2.1'})]

View File

@ -572,7 +572,8 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
self.stub_out('nova.compute.api.API.attach_volume',
lambda self, context, instance, volume_id,
device, tag=None,
supports_multiattach=False: None)
supports_multiattach=False,
delete_on_termination=False: None)
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A,
'device': '/dev/fake'}}
result = self.attachments.create(self.req, FAKE_UUID, body=body)
@ -649,7 +650,8 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
mock_attach_volume.assert_called_once_with(
self.req.environ['nova.context'],
test.MatchType(objects.Instance), FAKE_UUID_A, '/dev/fake',
supports_multiattach=supports_multiattach, tag=None)
supports_multiattach=supports_multiattach,
delete_on_termination=False, tag=None)
def test_attach_volume_bad_id(self):
self.stub_out('nova.compute.api.API.attach_volume',
@ -993,6 +995,102 @@ class VolumeAttachTestsV2_75(VolumeAttachTestsV21):
req, FAKE_UUID)
class VolumeAttachTestsV279(VolumeAttachTestsV2_75):
microversion = '2.79'
def setUp(self):
super(VolumeAttachTestsV279, self).setUp()
self.controller = volumes_v21.VolumeAttachmentController()
self.expected_show = {'volumeAttachment':
{'device': '/dev/fake0',
'serverId': FAKE_UUID,
'id': FAKE_UUID_A,
'volumeId': FAKE_UUID_A,
'tag': None,
'delete_on_termination': False
}}
def _get_req(self, body, microversion=None):
req = fakes.HTTPRequest.blank(
'/v2/servers/id/os-volume_attachments/uuid',
version=microversion or self.microversion)
req.body = jsonutils.dump_as_bytes(body)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
return req
def test_create_volume_attach_pre_v279(self):
"""Tests the case that the user tries to attach a volume with
delete_on_termination field, but before using microversion 2.79.
"""
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A,
'delete_on_termination': False}}
req = self._get_req(body, microversion='2.78')
ex = self.assertRaises(exception.ValidationError,
self.controller.create,
req, FAKE_UUID, body=body)
self.assertIn("Additional properties are not allowed",
six.text_type(ex))
@mock.patch('nova.compute.api.API.attach_volume', return_value=None)
def test_attach_volume_pre_v279(self, mock_attach_volume):
"""Before microversion 2.79, attach a volume will not contain
'delete_on_termination' field in the response.
"""
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A}}
req = self._get_req(body, microversion='2.78')
result = self.attachments.create(req, FAKE_UUID, body=body)
self.assertNotIn('delete_on_termination', result['volumeAttachment'])
mock_attach_volume.assert_called_once_with(
req.environ['nova.context'], test.MatchType(objects.Instance),
FAKE_UUID_A, None, tag=None, supports_multiattach=True,
delete_on_termination=False)
@mock.patch('nova.compute.api.API.attach_volume', return_value=None)
def test_attach_volume_with_delete_on_termination_default_value(
self, mock_attach_volume):
"""Test attach a volume doesn't specify 'delete_on_termination' in
the request, you will be get it's default value in the response.
The delete_on_termination's default value is 'False'.
"""
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A}}
req = self._get_req(body)
result = self.attachments.create(req, FAKE_UUID, body=body)
self.assertFalse(result['volumeAttachment']['delete_on_termination'])
mock_attach_volume.assert_called_once_with(
req.environ['nova.context'], test.MatchType(objects.Instance),
FAKE_UUID_A, None, tag=None, supports_multiattach=True,
delete_on_termination=False)
def test_create_volume_attach_invalid_delete_on_termination_empty(self):
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A,
'delete_on_termination': None}}
req = self._get_req(body)
ex = self.assertRaises(exception.ValidationError,
self.controller.create,
req, FAKE_UUID, body=body)
self.assertIn("Invalid input for field/attribute "
"delete_on_termination.", six.text_type(ex))
def test_create_volume_attach_invalid_delete_on_termination_value(self):
""""Test the case that the user tries to set the delete_on_termination
value not in the boolean or string-boolean check, the valid boolean
value are:
[True, 'True', 'TRUE', 'true', '1', 'ON', 'On', 'on', 'YES', 'Yes',
'yes', False, 'False', 'FALSE', 'false', '0', 'OFF', 'Off', 'off',
'NO', 'No', 'no']
"""
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A,
'delete_on_termination': 'foo'}}
req = self._get_req(body)
ex = self.assertRaises(exception.ValidationError,
self.controller.create,
req, FAKE_UUID, body=body)
self.assertIn("Invalid input for field/attribute "
"delete_on_termination.", six.text_type(ex))
class SwapVolumeMultiattachTestCase(test.NoDBTestCase):
@mock.patch('nova.api.openstack.common.get_instance')

View File

@ -10682,7 +10682,7 @@ class ComputeAPITestCase(BaseTestCase):
volume = {'id': uuids.volume}
self.compute_api._attach_volume_shelved_offloaded(
self.context, instance, volume,
'/dev/vdb', 'ide', 'cdrom')
'/dev/vdb', 'ide', 'cdrom', False)
mock_attach_and_reserve.assert_called_once_with(self.context,
volume,
instance,
@ -10722,7 +10722,7 @@ class ComputeAPITestCase(BaseTestCase):
volume = {'id': uuids.volume}
self.compute_api._attach_volume_shelved_offloaded(
self.context, instance, volume,
'/dev/vdb', 'ide', 'cdrom')
'/dev/vdb', 'ide', 'cdrom', False)
mock_attach_and_reserve.assert_called_once_with(self.context,
volume,
instance,

View File

@ -361,8 +361,10 @@ class _ComputeAPIUnitTestMixIn(object):
self._test_specified_ip_and_multiple_instances_helper(
requested_networks)
@mock.patch('nova.objects.BlockDeviceMapping.save')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name')
def test_create_volume_bdm_call_reserve_dev_name(self, mock_reserve):
def test_create_volume_bdm_call_reserve_dev_name(self, mock_reserve,
mock_bdm_save):
bdm = objects.BlockDeviceMapping(
**fake_block_device.FakeDbBlockDeviceDict(
{
@ -384,6 +386,7 @@ class _ComputeAPIUnitTestMixIn(object):
None)
self.assertTrue(mock_reserve.called)
self.assertEqual(result, bdm)
mock_bdm_save.assert_called_once_with()
@mock.patch.object(objects.BlockDeviceMapping, 'create')
def test_create_volume_bdm_local_creation(self, bdm_create):

View File

@ -0,0 +1,14 @@
---
features:
- |
Microversion 2.79 adds support for specifying the ``delete_on_termination``
field in the request body when attaching a volume to a server, to support
configuring whether to delete the data volume when the server is destroyed.
Also, ``delete_on_termination`` is added to the GET responses when showing
attached volumes.
The affected APIs are as follows:
* ``POST /servers/{server_id}/os-volume_attachments``
* ``GET /servers/{server_id}/os-volume_attachments``
* ``GET /servers/{server_id}/os-volume_attachments/{volume_id}``