From cd396b8b61ed7496f4166a2237b27aa0a138f6e5 Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Tue, 30 Jul 2019 19:52:00 +0800 Subject: [PATCH] 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 --- doc/source/cli/nova.rst | 8 ++++- novaclient/__init__.py | 2 +- novaclient/tests/unit/v2/fakes.py | 11 ++++++ novaclient/tests/unit/v2/test_shell.py | 36 +++++++++++++++++++ novaclient/tests/unit/v2/test_volumes.py | 31 ++++++++++++++++ novaclient/v2/shell.py | 13 +++++++ novaclient/v2/volumes.py | 30 +++++++++++++++- .../microversion-v2_79-f13bc0414743dc16.yaml | 16 +++++++++ 8 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index ab3107581..007e9686d 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -3849,7 +3849,8 @@ nova volume-attach .. code-block:: console - usage: nova volume-attach [--tag ] [] + usage: nova volume-attach [--delete-on-termination] [--tag ] + [] Attach a volume to a server. @@ -3870,6 +3871,11 @@ Attach a volume to a server. ``--tag `` 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 diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 3e5daf213..a4f9b4eec 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.78") +API_MAX_VERSION = api_versions.APIVersion("2.79") diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index d6fbf129d..dd60d7f4a 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -2115,6 +2115,11 @@ class FakeSessionClient(base_client.SessionClient): if self.api_version >= api_versions.APIVersion('2.70'): # Include the "tag" field in the response. 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}) 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. for attachment in attachments['volumeAttachments']: 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) def get_servers_1234_os_volume_attachments_Work(self, **kw): diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 928a9de42..a57c37948 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -3825,6 +3825,42 @@ class ShellTest(utils.TestCase): 'tag': 'test-tag'}}) 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): self.run_command('volume-update sample-server Work Work') self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', diff --git a/novaclient/tests/unit/v2/test_volumes.py b/novaclient/tests/unit/v2/test_volumes.py index 932b71d79..fbc55ddf7 100644 --- a/novaclient/tests/unit/v2/test_volumes.py +++ b/novaclient/tests/unit/v2/test_volumes.py @@ -14,6 +14,7 @@ # under the License. import mock +import six from novaclient import api_versions from novaclient.tests.unit import utils @@ -126,3 +127,33 @@ class VolumesV249Test(VolumesTest): volume_id=None, attachment_id="Work") 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)) diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index acaf17833..2dfd5c92d 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -2633,6 +2633,13 @@ def _translate_volume_attachments_keys(collection): default=None, help=_('Tag for the attached volume.'), 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): """Attach a volume to a server.""" if args.device == 'auto': @@ -2642,6 +2649,9 @@ def do_volume_attach(cs, args): if 'tag' in args and 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, args.volume, args.device, @@ -2699,6 +2709,9 @@ def do_volume_attachments(cs, args): fields = ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID'] if cs.api_version >= api_versions.APIVersion('2.70'): 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) diff --git a/novaclient/v2/volumes.py b/novaclient/v2/volumes.py index d6208cbd1..8fc755658 100644 --- a/novaclient/v2/volumes.py +++ b/novaclient/v2/volumes.py @@ -55,7 +55,7 @@ class VolumeManager(base.Manager): return self._create("/servers/%s/os-volume_attachments" % server_id, 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, tag=None): """ @@ -75,6 +75,34 @@ class VolumeManager(base.Manager): return self._create("/servers/%s/os-volume_attachments" % server_id, 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): """ Swaps the existing volume attachment to point to a new volume. diff --git a/releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml b/releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml new file mode 100644 index 000000000..ca81f58de --- /dev/null +++ b/releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml @@ -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