Skip to content

Commit ca7f23d

Browse files
committed
compute: Add 'server volume update' command
We're not going to expose the ability to swap volumes since that's a things humans should not generally use. From the API docs [1]: When updating volumeId, this API is typically meant to only be used as part of a larger orchestrated volume migration operation initiated in the block storage service via the os-retype or os-migrate_volume volume actions. Direct usage of this API to update volumeId is not recommended and may result in needing to hard reboot the server to update details within the guest such as block storage serial IDs. Furthermore, updating volumeId via this API is only implemented by certain compute drivers. We *do* want users to have the ability to change the delete on termination behavior though, so that's what we expose. [1] https://docs.openstack.org/api-ref/compute/?expanded=update-a-volume-attachment-detail#update-a-volume-attachment Change-Id: I50938e1237b4d298521b26a5f9cb90c018dfebaf Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1 parent d33eb3e commit ca7f23d

7 files changed

Lines changed: 201 additions & 10 deletions

File tree

lower-constraints.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ python-cinderclient==3.3.0
7171
python-dateutil==2.5.3
7272
python-keystoneclient==3.22.0
7373
python-mimeparse==1.6.0
74-
python-novaclient==15.1.0
74+
python-novaclient==17.0.0
7575
python-subunit==1.0.0
7676
pytz==2013.6
7777
PyYAML==3.13

openstackclient/compute/v2/server.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -513,27 +513,27 @@ def get_parser(self, prog_name):
513513
'--tag',
514514
metavar='<tag>',
515515
help=_(
516-
"Tag for the attached volume. "
517-
"(Supported by API versions '2.49' - '2.latest')"
516+
'Tag for the attached volume '
517+
'(supported by --os-compute-api-version 2.49 or above)'
518518
),
519519
)
520+
# TODO(stephenfin): These should be called 'delete-on-termination' and
521+
# 'preserve-on-termination'
520522
termination_group = parser.add_mutually_exclusive_group()
521523
termination_group.add_argument(
522524
'--enable-delete-on-termination',
523525
action='store_true',
524526
help=_(
525-
"Specify if the attached volume should be deleted when the "
526-
"server is destroyed. "
527-
"(Supported by API versions '2.79' - '2.latest')"
527+
'Delete the volume when the server is destroyed '
528+
'(supported by --os-compute-api-version 2.79 or above)'
528529
),
529530
)
530531
termination_group.add_argument(
531532
'--disable-delete-on-termination',
532533
action='store_true',
533534
help=_(
534-
"Specify if the attached volume should not be deleted when "
535-
"the server is destroyed. "
536-
"(Supported by API versions '2.79' - '2.latest')"
535+
'Do not delete the volume when the server is destroyed '
536+
'(supported by --os-compute-api-version 2.79 or above)'
537537
),
538538
)
539539
return parser

openstackclient/compute/v2/server_volume.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from novaclient import api_versions
1818
from osc_lib.command import command
19+
from osc_lib import exceptions
1920
from osc_lib import utils
2021

2122
from openstackclient.i18n import _
@@ -71,3 +72,69 @@ def take_action(self, parsed_args):
7172
) for s in volumes
7273
),
7374
)
75+
76+
77+
class UpdateServerVolume(command.Command):
78+
"""Update a volume attachment on the server."""
79+
80+
def get_parser(self, prog_name):
81+
parser = super(UpdateServerVolume, self).get_parser(prog_name)
82+
parser.add_argument(
83+
'server',
84+
help=_('Server to update volume for (name or ID)'),
85+
)
86+
parser.add_argument(
87+
'volume',
88+
help=_('Volume (ID)'),
89+
)
90+
termination_group = parser.add_mutually_exclusive_group()
91+
termination_group.add_argument(
92+
'--delete-on-termination',
93+
action='store_true',
94+
dest='delete_on_termination',
95+
default=None,
96+
help=_(
97+
'Delete the volume when the server is destroyed '
98+
'(supported by --os-compute-api-version 2.85 or above)'
99+
),
100+
)
101+
termination_group.add_argument(
102+
'--preserve-on-termination',
103+
action='store_false',
104+
dest='delete_on_termination',
105+
help=_(
106+
'Preserve the volume when the server is destroyed '
107+
'(supported by --os-compute-api-version 2.85 or above)'
108+
),
109+
)
110+
return parser
111+
112+
def take_action(self, parsed_args):
113+
114+
compute_client = self.app.client_manager.compute
115+
116+
if parsed_args.delete_on_termination is not None:
117+
if compute_client.api_version < api_versions.APIVersion('2.85'):
118+
msg = _(
119+
'--os-compute-api-version 2.85 or greater is required to '
120+
'support the --(no-)delete-on-termination option'
121+
)
122+
raise exceptions.CommandError(msg)
123+
124+
server = utils.find_resource(
125+
compute_client.servers,
126+
parsed_args.server,
127+
)
128+
129+
# NOTE(stephenfin): This may look silly, and that's because it is.
130+
# This API was originally used only for the swapping volumes, which
131+
# is an internal operation that should only be done by
132+
# orchestration software rather than a human. We're not going to
133+
# expose that, but we are going to expose the ability to change the
134+
# delete on termination behavior.
135+
compute_client.volumes.update_server_volume(
136+
server.id,
137+
parsed_args.volume,
138+
parsed_args.volume,
139+
delete_on_termination=parsed_args.delete_on_termination,
140+
)

openstackclient/tests/unit/compute/v2/test_server_volume.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#
1313

1414
from novaclient import api_versions
15+
from osc_lib import exceptions
1516

1617
from openstackclient.compute.v2 import server_volume
1718
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -165,3 +166,122 @@ def test_server_volume_list_with_delete_on_attachment(self):
165166
)
166167
self.servers_volumes_mock.get_server_volumes.assert_called_once_with(
167168
self.server.id)
169+
170+
171+
class TestServerVolumeUpdate(TestServerVolume):
172+
173+
def setUp(self):
174+
super().setUp()
175+
176+
self.server = compute_fakes.FakeServer.create_one_server()
177+
self.servers_mock.get.return_value = self.server
178+
179+
# Get the command object to test
180+
self.cmd = server_volume.UpdateServerVolume(self.app, None)
181+
182+
def test_server_volume_update(self):
183+
184+
arglist = [
185+
self.server.id,
186+
'foo',
187+
]
188+
verifylist = [
189+
('server', self.server.id),
190+
('volume', 'foo'),
191+
('delete_on_termination', None),
192+
]
193+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
194+
195+
result = self.cmd.take_action(parsed_args)
196+
197+
# This is a no-op
198+
self.servers_volumes_mock.update_server_volume.assert_not_called()
199+
self.assertIsNone(result)
200+
201+
def test_server_volume_update_with_delete_on_termination(self):
202+
self.app.client_manager.compute.api_version = \
203+
api_versions.APIVersion('2.85')
204+
205+
arglist = [
206+
self.server.id,
207+
'foo',
208+
'--delete-on-termination',
209+
]
210+
verifylist = [
211+
('server', self.server.id),
212+
('volume', 'foo'),
213+
('delete_on_termination', True),
214+
]
215+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
216+
217+
result = self.cmd.take_action(parsed_args)
218+
219+
self.servers_volumes_mock.update_server_volume.assert_called_once_with(
220+
self.server.id, 'foo', 'foo',
221+
delete_on_termination=True)
222+
self.assertIsNone(result)
223+
224+
def test_server_volume_update_with_preserve_on_termination(self):
225+
self.app.client_manager.compute.api_version = \
226+
api_versions.APIVersion('2.85')
227+
228+
arglist = [
229+
self.server.id,
230+
'foo',
231+
'--preserve-on-termination',
232+
]
233+
verifylist = [
234+
('server', self.server.id),
235+
('volume', 'foo'),
236+
('delete_on_termination', False),
237+
]
238+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
239+
240+
result = self.cmd.take_action(parsed_args)
241+
242+
self.servers_volumes_mock.update_server_volume.assert_called_once_with(
243+
self.server.id, 'foo', 'foo',
244+
delete_on_termination=False)
245+
self.assertIsNone(result)
246+
247+
def test_server_volume_update_with_delete_on_termination_pre_v285(self):
248+
self.app.client_manager.compute.api_version = \
249+
api_versions.APIVersion('2.84')
250+
251+
arglist = [
252+
self.server.id,
253+
'foo',
254+
'--delete-on-termination',
255+
]
256+
verifylist = [
257+
('server', self.server.id),
258+
('volume', 'foo'),
259+
('delete_on_termination', True),
260+
]
261+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
262+
263+
self.assertRaises(
264+
exceptions.CommandError,
265+
self.cmd.take_action,
266+
parsed_args)
267+
268+
def test_server_volume_update_with_preserve_on_termination_pre_v285(self):
269+
self.app.client_manager.compute.api_version = \
270+
api_versions.APIVersion('2.84')
271+
272+
arglist = [
273+
self.server.id,
274+
'foo',
275+
'--preserve-on-termination',
276+
]
277+
verifylist = [
278+
('server', self.server.id),
279+
('volume', 'foo'),
280+
('delete_on_termination', False),
281+
]
282+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
283+
284+
self.assertRaises(
285+
exceptions.CommandError,
286+
self.cmd.take_action,
287+
parsed_args)

releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ features:
33
- |
44
Add ``server volume list`` command, to list the volumes attached to an
55
instance.
6+
- |
7+
Add ``server volume update`` command, to update the volumes attached to
8+
an instance.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ openstacksdk>=0.52.0 # Apache-2.0
99
osc-lib>=2.3.0 # Apache-2.0
1010
oslo.i18n>=3.15.3 # Apache-2.0
1111
python-keystoneclient>=3.22.0 # Apache-2.0
12-
python-novaclient>=15.1.0 # Apache-2.0
12+
python-novaclient>=17.0.0 # Apache-2.0
1313
python-cinderclient>=3.3.0 # Apache-2.0
1414
stevedore>=2.0.1 # Apache-2.0

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ openstack.compute.v2 =
154154
server_image_create = openstackclient.compute.v2.server_image:CreateServerImage
155155

156156
server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume
157+
server_volume_update = openstackclient.compute.v2.server_volume:UpdateServerVolume
157158

158159
usage_list = openstackclient.compute.v2.usage:ListUsage
159160
usage_show = openstackclient.compute.v2.usage:ShowUsage

0 commit comments

Comments
 (0)