Microversion 2.79: Add delete_on_termination to volume-attach API

Support add 'delete_on_termination' field to the voume attach API to
support configuring whether to delete the data volume when the server
is destroyed.

* Updating the ``nova volume-attachments`` command to show the
  ``delete_on_termination`` value if 2.79 or greater is used.
* The '--delete-on-termination' option is added to the `nova volume-attach`
  CLI.

Depends-On: https://review.opendev.org/#/c/673133/
Part of blueprint support-delete-on-termination-in-server-attach-volume

Change-Id: I8dcf2fd21a2fd99ca4e05bd953fbbe026be3a619
This commit is contained in:
zhangbailin 2019-07-30 19:52:00 +08:00 committed by Matt Riedemann
parent aae95dcc7a
commit cd396b8b61
8 changed files with 144 additions and 3 deletions

View File

@ -3849,7 +3849,8 @@ nova volume-attach
.. code-block:: console .. code-block:: console
usage: nova volume-attach [--tag <tag>] <server> <volume> [<device>] usage: nova volume-attach [--delete-on-termination] [--tag <tag>]
<server> <volume> [<device>]
Attach a volume to a server. Attach a volume to a server.
@ -3870,6 +3871,11 @@ Attach a volume to a server.
``--tag <tag>`` ``--tag <tag>``
Tag for the attached volume. (Supported by API versions '2.49' - '2.latest') Tag for the attached volume. (Supported by API versions '2.49' - '2.latest')
``--delete-on-termination``
Specify if the attached volume sholud be deleted when the server is
destroyed. By default the attached volume is not deleted when the server is
destroyed. (Supported by API versions '2.79' - '2.latest')
.. _nova_volume-attachments: .. _nova_volume-attachments:
nova volume-attachments nova volume-attachments

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise # when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some # the client may break due to server side new version may include some
# backward incompatible change. # backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.78") API_MAX_VERSION = api_versions.APIVersion("2.79")

View File

@ -2115,6 +2115,11 @@ class FakeSessionClient(base_client.SessionClient):
if self.api_version >= api_versions.APIVersion('2.70'): if self.api_version >= api_versions.APIVersion('2.70'):
# Include the "tag" field in the response. # Include the "tag" field in the response.
attachment['tag'] = 'test-tag' attachment['tag'] = 'test-tag'
if self.api_version >= api_versions.APIVersion('2.79'):
# Include the "delete_on_termination" field in the
# response.
attachment['delete_on_termination'] = True
return (200, FAKE_RESPONSE_HEADERS, {"volumeAttachment": attachment}) return (200, FAKE_RESPONSE_HEADERS, {"volumeAttachment": attachment})
def put_servers_1234_os_volume_attachments_Work(self, **kw): def put_servers_1234_os_volume_attachments_Work(self, **kw):
@ -2139,6 +2144,12 @@ class FakeSessionClient(base_client.SessionClient):
# Include the "tag" field in each attachment. # Include the "tag" field in each attachment.
for attachment in attachments['volumeAttachments']: for attachment in attachments['volumeAttachments']:
attachment['tag'] = 'test-tag' attachment['tag'] = 'test-tag'
if self.api_version >= api_versions.APIVersion('2.79'):
# Include the "delete_on_termination" field in each
# attachment.
for attachment in attachments['volumeAttachments']:
attachment['delete_on_termination'] = True
return (200, FAKE_RESPONSE_HEADERS, attachments) return (200, FAKE_RESPONSE_HEADERS, attachments)
def get_servers_1234_os_volume_attachments_Work(self, **kw): def get_servers_1234_os_volume_attachments_Work(self, **kw):

View File

@ -3825,6 +3825,42 @@ class ShellTest(utils.TestCase):
'tag': 'test-tag'}}) 'tag': 'test-tag'}})
self.assertIn('test-tag', out) self.assertIn('test-tag', out)
def test_volume_attachments_pre_v2_79(self):
out = self.run_command(
'volume-attachments 1234', api_version='2.78')[0]
self.assert_called('GET', '/servers/1234/os-volume_attachments')
self.assertNotIn('DELETE ON TERMINATION', out)
def test_volume_attachments_v2_79(self):
out = self.run_command(
'volume-attachments 1234', api_version='2.79')[0]
self.assert_called('GET', '/servers/1234/os-volume_attachments')
self.assertIn('DELETE ON TERMINATION', out)
def test_volume_attach_with_delete_on_termination_pre_v2_79(self):
self.assertRaises(
SystemExit, self.run_command,
'volume-attach --delete-on-termination sample-server '
'Work /dev/vdb', api_version='2.78')
def test_volume_attach_with_delete_on_termination_v2_79(self):
out = self.run_command(
'volume-attach --delete-on-termination sample-server '
'2 /dev/vdb', api_version='2.79')[0]
self.assert_called('POST', '/servers/1234/os-volume_attachments',
{'volumeAttachment':
{'device': '/dev/vdb',
'volumeId': '2',
'delete_on_termination': True}})
self.assertIn('delete_on_termination', out)
def test_volume_attach_without_delete_on_termination(self):
self.run_command('volume-attach sample-server Work',
api_version='2.79')
self.assert_called('POST', '/servers/1234/os-volume_attachments',
{'volumeAttachment':
{'volumeId': 'Work'}})
def test_volume_update(self): def test_volume_update(self):
self.run_command('volume-update sample-server Work Work') self.run_command('volume-update sample-server Work Work')
self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work',

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
import mock import mock
import six
from novaclient import api_versions from novaclient import api_versions
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
@ -126,3 +127,33 @@ class VolumesV249Test(VolumesTest):
volume_id=None, volume_id=None,
attachment_id="Work") attachment_id="Work")
mock_warn.assert_called_once() mock_warn.assert_called_once()
class VolumesV279Test(VolumesV249Test):
api_version = "2.79"
def test_create_server_volume_with_delete_on_termination(self):
v = self.cs.volumes.create_server_volume(
server_id=1234,
volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983',
device='/dev/vdb',
tag='tag1',
delete_on_termination=True
)
self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST)
self.cs.assert_called(
'POST', '/servers/1234/os-volume_attachments',
{'volumeAttachment': {
'volumeId': '15e59938-07d5-11e1-90e3-e3dffe0c5983',
'device': '/dev/vdb',
'tag': 'tag1',
'delete_on_termination': True}})
self.assertIsInstance(v, volumes.Volume)
def test_create_server_volume_with_delete_on_termination_pre_v279(self):
self.cs.api_version = api_versions.APIVersion('2.78')
ex = self.assertRaises(
TypeError, self.cs.volumes.create_server_volume, "1234",
volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983',
delete_on_termination=True)
self.assertIn('delete_on_termination', six.text_type(ex))

View File

@ -2633,6 +2633,13 @@ def _translate_volume_attachments_keys(collection):
default=None, default=None,
help=_('Tag for the attached volume.'), help=_('Tag for the attached volume.'),
start_version="2.49") start_version="2.49")
@utils.arg(
'--delete-on-termination',
action='store_true',
default=False,
help=_('Specify if the attached volume should be deleted '
'when the server is destroyed.'),
start_version="2.79")
def do_volume_attach(cs, args): def do_volume_attach(cs, args):
"""Attach a volume to a server.""" """Attach a volume to a server."""
if args.device == 'auto': if args.device == 'auto':
@ -2642,6 +2649,9 @@ def do_volume_attach(cs, args):
if 'tag' in args and args.tag: if 'tag' in args and args.tag:
update_kwargs['tag'] = args.tag update_kwargs['tag'] = args.tag
if 'delete_on_termination' in args and args.delete_on_termination:
update_kwargs['delete_on_termination'] = args.delete_on_termination
volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id, volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id,
args.volume, args.volume,
args.device, args.device,
@ -2699,6 +2709,9 @@ def do_volume_attachments(cs, args):
fields = ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID'] fields = ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID']
if cs.api_version >= api_versions.APIVersion('2.70'): if cs.api_version >= api_versions.APIVersion('2.70'):
fields.append('TAG') fields.append('TAG')
# Microversion >= 2.79 returns the delete_on_termination value.
if cs.api_version >= api_versions.APIVersion('2.79'):
fields.append('DELETE ON TERMINATION')
utils.print_list(volumes, fields) utils.print_list(volumes, fields)

View File

@ -55,7 +55,7 @@ class VolumeManager(base.Manager):
return self._create("/servers/%s/os-volume_attachments" % server_id, return self._create("/servers/%s/os-volume_attachments" % server_id,
body, "volumeAttachment") body, "volumeAttachment")
@api_versions.wraps("2.49") @api_versions.wraps("2.49", "2.78")
def create_server_volume(self, server_id, volume_id, device=None, def create_server_volume(self, server_id, volume_id, device=None,
tag=None): tag=None):
""" """
@ -75,6 +75,34 @@ class VolumeManager(base.Manager):
return self._create("/servers/%s/os-volume_attachments" % server_id, return self._create("/servers/%s/os-volume_attachments" % server_id,
body, "volumeAttachment") body, "volumeAttachment")
@api_versions.wraps("2.79")
def create_server_volume(self, server_id, volume_id, device=None,
tag=None, delete_on_termination=False):
"""
Attach a volume identified by the volume ID to the given server ID
:param server_id: The ID of the server.
:param volume_id: The ID of the volume to attach.
:param device: The device name (optional).
:param tag: The tag (optional).
:param delete_on_termination: Marked whether to delete the attached
volume when the server is deleted
(optional).
:rtype: :class:`Volume`
"""
# TODO(mriedem): Move this body construction into a private common
# helper method for all versions of create_server_volume to use.
body = {'volumeAttachment': {'volumeId': volume_id}}
if device is not None:
body['volumeAttachment']['device'] = device
if tag is not None:
body['volumeAttachment']['tag'] = tag
if delete_on_termination:
body['volumeAttachment']['delete_on_termination'] = (
delete_on_termination)
return self._create("/servers/%s/os-volume_attachments" % server_id,
body, "volumeAttachment")
def update_server_volume(self, server_id, src_volid, dest_volid): def update_server_volume(self, server_id, src_volid, dest_volid):
""" """
Swaps the existing volume attachment to point to a new volume. Swaps the existing volume attachment to point to a new volume.

View File

@ -0,0 +1,16 @@
---
features:
- |
Added support for `microversion 2.79`_ which includes the following
changes:
- The ``--delete-on-termination`` option is added to the
``nova volume-attach`` CLI.
- A ``DELETE ON TERMINATION`` column is added to the
``nova volume-attachments`` table.
- New kwarg called ``delete_on_termination`` added to the python API
binding:
- ``novaclient.v2.volumes.VolumeManager.create_server_volume()``
.. _microversion 2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id71