Skip to content

Commit 6e0699c

Browse files
committed
Microversion 2.91: Support specifying destination host to unshelve
This patch adds a new parameter ``--host`` to ``openstack server unshelve`` command. This can help administrators to specify an ``host`` to unshelve a shelve offloaded server. And add new parameter ``--no-availability-zone`` to unpin a server availability These parameters are available in the 2.91 microversion. Depends-On: https://review.opendev.org/c/openstack/python-novaclient/+/831651 Implements: blueprint unshelve-to-host Change-Id: I7986adc7563f63bcd4b3caf5eb7bc4329b4e1eca
1 parent 27b2496 commit 6e0699c

3 files changed

Lines changed: 222 additions & 3 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4564,13 +4564,30 @@ def get_parser(self, prog_name):
45644564
nargs='+',
45654565
help=_('Server(s) to unshelve (name or ID)'),
45664566
)
4567-
parser.add_argument(
4567+
group = parser.add_mutually_exclusive_group()
4568+
group.add_argument(
45684569
'--availability-zone',
45694570
default=None,
45704571
help=_('Name of the availability zone in which to unshelve a '
45714572
'SHELVED_OFFLOADED server (supported by '
45724573
'--os-compute-api-version 2.77 or above)'),
45734574
)
4575+
group.add_argument(
4576+
'--no-availability-zone',
4577+
action='store_true',
4578+
default=False,
4579+
help=_('Unpin the availability zone of a SHELVED_OFFLOADED '
4580+
'server. Server will be unshelved on a host without '
4581+
'availability zone constraint (supported by '
4582+
'--os-compute-api-version 2.91 or above)'),
4583+
)
4584+
parser.add_argument(
4585+
'--host',
4586+
default=None,
4587+
help=_('Name of the destination host in which to unshelve a '
4588+
'SHELVED_OFFLOADED server (supported by '
4589+
'--os-compute-api-version 2.91 or above)'),
4590+
)
45744591
parser.add_argument(
45754592
'--wait',
45764593
action='store_true',
@@ -4599,6 +4616,26 @@ def _show_progress(progress):
45994616

46004617
kwargs['availability_zone'] = parsed_args.availability_zone
46014618

4619+
if parsed_args.host:
4620+
if compute_client.api_version < api_versions.APIVersion('2.91'):
4621+
msg = _(
4622+
'--os-compute-api-version 2.91 or greater is required '
4623+
'to support the --host option'
4624+
)
4625+
raise exceptions.CommandError(msg)
4626+
4627+
kwargs['host'] = parsed_args.host
4628+
4629+
if parsed_args.no_availability_zone:
4630+
if compute_client.api_version < api_versions.APIVersion('2.91'):
4631+
msg = _(
4632+
'--os-compute-api-version 2.91 or greater is required '
4633+
'to support the --no-availability-zone option'
4634+
)
4635+
raise exceptions.CommandError(msg)
4636+
4637+
kwargs['availability_zone'] = None
4638+
46024639
for server in parsed_args.server:
46034640
server_obj = utils.find_resource(
46044641
compute_client.servers,

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

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ def setup_sdk_volumes_mock(self, count):
161161
return volumes
162162

163163
def run_method_with_servers(self, method_name, server_count):
164+
# Starting with v2.91, the nova api needs to be call with a sentinel
165+
# as availability_zone=None will unpin the server az.
166+
_sentinel = object()
167+
164168
servers = self.setup_servers_mock(server_count)
165169

166170
arglist = []
@@ -183,7 +187,11 @@ def run_method_with_servers(self, method_name, server_count):
183187
method.assert_called_with(reason=None)
184188
elif method_name == 'unshelve':
185189
version = self.app.client_manager.compute.api_version
186-
if version >= api_versions.APIVersion('2.77'):
190+
if version >= api_versions.APIVersion('2.91'):
191+
method.assert_called_with(availability_zone=_sentinel,
192+
host=None)
193+
elif (version >= api_versions.APIVersion('2.77') and
194+
version < api_versions.APIVersion('2.91')):
187195
method.assert_called_with(availability_zone=None)
188196
else:
189197
method.assert_called_with()
@@ -8204,7 +8212,23 @@ def test_unshelve_one_server(self):
82048212
def test_unshelve_multi_servers(self):
82058213
self.run_method_with_servers('unshelve', 3)
82068214

8207-
def test_unshelve_with_specified_az(self):
8215+
def test_unshelve_v277(self):
8216+
self.app.client_manager.compute.api_version = \
8217+
api_versions.APIVersion('2.77')
8218+
8219+
server = compute_fakes.FakeServer.create_one_server(
8220+
attrs=self.attrs, methods=self.methods)
8221+
self.servers_mock.get.return_value = server
8222+
arglist = [server.id]
8223+
verifylist = [('server', [server.id])]
8224+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8225+
8226+
self.cmd.take_action(parsed_args)
8227+
8228+
self.servers_mock.get.assert_called_with(server.id)
8229+
server.unshelve.assert_called_with()
8230+
8231+
def test_unshelve_with_specified_az_v277(self):
82088232
self.app.client_manager.compute.api_version = \
82098233
api_versions.APIVersion('2.77')
82108234

@@ -8248,6 +8272,157 @@ def test_unshelve_with_specified_az_pre_v277(self):
82488272
self.assertIn(
82498273
'--os-compute-api-version 2.77 or greater is required', str(ex))
82508274

8275+
def test_unshelve_v291(self):
8276+
self.app.client_manager.compute.api_version = (
8277+
api_versions.APIVersion('2.91'))
8278+
8279+
server = compute_fakes.FakeServer.create_one_server(
8280+
attrs=self.attrs, methods=self.methods)
8281+
self.servers_mock.get.return_value = server
8282+
arglist = [server.id]
8283+
verifylist = [('server', [server.id])]
8284+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8285+
8286+
self.cmd.take_action(parsed_args)
8287+
8288+
self.servers_mock.get.assert_called_with(server.id)
8289+
server.unshelve.assert_called_with()
8290+
8291+
def test_unshelve_with_specified_az_v291(self):
8292+
self.app.client_manager.compute.api_version = (
8293+
api_versions.APIVersion('2.91'))
8294+
8295+
server = compute_fakes.FakeServer.create_one_server(
8296+
attrs=self.attrs, methods=self.methods)
8297+
self.servers_mock.get.return_value = server
8298+
arglist = [
8299+
'--availability-zone', "foo-az",
8300+
server.id,
8301+
]
8302+
verifylist = [
8303+
('availability_zone', "foo-az"),
8304+
('server', [server.id])
8305+
]
8306+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8307+
8308+
self.cmd.take_action(parsed_args)
8309+
8310+
self.servers_mock.get.assert_called_with(server.id)
8311+
server.unshelve.assert_called_with(availability_zone="foo-az")
8312+
8313+
def test_unshelve_with_specified_host_v291(self):
8314+
self.app.client_manager.compute.api_version = (
8315+
api_versions.APIVersion('2.91'))
8316+
8317+
server = compute_fakes.FakeServer.create_one_server(
8318+
attrs=self.attrs, methods=self.methods)
8319+
self.servers_mock.get.return_value = server
8320+
arglist = [
8321+
'--host', "server1",
8322+
server.id,
8323+
]
8324+
verifylist = [
8325+
('host', "server1"),
8326+
('server', [server.id])
8327+
]
8328+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8329+
8330+
self.cmd.take_action(parsed_args)
8331+
8332+
self.servers_mock.get.assert_called_with(server.id)
8333+
server.unshelve.assert_called_with(host="server1")
8334+
8335+
def test_unshelve_with_unpin_az_v291(self):
8336+
self.app.client_manager.compute.api_version = (
8337+
api_versions.APIVersion('2.91'))
8338+
8339+
server = compute_fakes.FakeServer.create_one_server(
8340+
attrs=self.attrs, methods=self.methods)
8341+
self.servers_mock.get.return_value = server
8342+
arglist = ['--no-availability-zone', server.id]
8343+
verifylist = [
8344+
('no_availability_zone', True),
8345+
('server', [server.id])
8346+
]
8347+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8348+
8349+
self.cmd.take_action(parsed_args)
8350+
8351+
self.servers_mock.get.assert_called_with(server.id)
8352+
server.unshelve.assert_called_with(availability_zone=None)
8353+
8354+
def test_unshelve_with_specified_az_and_host_v291(self):
8355+
self.app.client_manager.compute.api_version = (
8356+
api_versions.APIVersion('2.91'))
8357+
8358+
server = compute_fakes.FakeServer.create_one_server(
8359+
attrs=self.attrs, methods=self.methods)
8360+
self.servers_mock.get.return_value = server
8361+
arglist = [
8362+
'--host', "server1",
8363+
'--availability-zone', "foo-az",
8364+
server.id,
8365+
]
8366+
verifylist = [
8367+
('host', "server1"),
8368+
('availability_zone', "foo-az"),
8369+
('server', [server.id])
8370+
]
8371+
8372+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8373+
8374+
self.cmd.take_action(parsed_args)
8375+
8376+
self.servers_mock.get.assert_called_with(server.id)
8377+
8378+
def test_unshelve_with_unpin_az_and_host_v291(self):
8379+
self.app.client_manager.compute.api_version = (
8380+
api_versions.APIVersion('2.91'))
8381+
8382+
server = compute_fakes.FakeServer.create_one_server(
8383+
attrs=self.attrs, methods=self.methods)
8384+
self.servers_mock.get.return_value = server
8385+
arglist = [
8386+
'--host', "server1",
8387+
'--no-availability-zone',
8388+
server.id,
8389+
]
8390+
verifylist = [
8391+
('host', "server1"),
8392+
('no_availability_zone', True),
8393+
('server', [server.id])
8394+
]
8395+
8396+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
8397+
8398+
self.cmd.take_action(parsed_args)
8399+
8400+
self.servers_mock.get.assert_called_with(server.id)
8401+
8402+
def test_unshelve_fails_with_unpin_az_and_az_v291(self):
8403+
self.app.client_manager.compute.api_version = (
8404+
api_versions.APIVersion('2.91'))
8405+
8406+
server = compute_fakes.FakeServer.create_one_server(
8407+
attrs=self.attrs, methods=self.methods)
8408+
self.servers_mock.get.return_value = server
8409+
arglist = [
8410+
'--availability-zone', "foo-az",
8411+
'--no-availability-zone',
8412+
server.id,
8413+
]
8414+
verifylist = [
8415+
('availability_zone', "foo-az"),
8416+
('no_availability_zone', True),
8417+
('server', [server.id])
8418+
]
8419+
8420+
ex = self.assertRaises(utils.ParserException,
8421+
self.check_parser,
8422+
self.cmd, arglist, verifylist)
8423+
self.assertIn('argument --no-availability-zone: not allowed '
8424+
'with argument --availability-zone', str(ex))
8425+
82518426
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
82528427
def test_unshelve_with_wait(self, mock_wait_for_status):
82538428
server = compute_fakes.FakeServer.create_one_server(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- Add ``--host`` and ``--no-availability-zone`` options to the
4+
``server unshelve`` command to enable administrators to specify a
5+
destination host or unset the availability zone during a server
6+
unshelve, respectively. Both options require the server to be
7+
shelved offload and ``--os-compute-api-version 2.91`` or greater.

0 commit comments

Comments
 (0)