Skip to content

Commit 64c2a1a

Browse files
committed
Add 'server shelve --offload', 'server shelve --wait' options
The '--offload' option allows us to explicitly request that the server be offloaded once shelved or if already shelved. The '--wait' option allows us to wait for the shelve and/or offload operations to complete before returning. It is implied when attempting to offload a server than is not yet shelved. Change-Id: Id226831e3c09bc95c34b222151b27391a844b073 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1 parent 9583447 commit 64c2a1a

5 files changed

Lines changed: 221 additions & 13 deletions

File tree

doc/source/cli/data/nova.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ service-force-down,compute service set --force,Force service to down.
114114
service-list,compute service list,Show a list of all running services.
115115
set-password,server set --root-password,Change the admin password for a server.
116116
shelve,server shelve,Shelve a server.
117-
shelve-offload,,Remove a shelved server from the compute node.
117+
shelve-offload,shelve --offload,Remove a shelved server from the compute node.
118118
show,server show,Show details about the given server.
119119
ssh,server ssh,SSH into a server.
120120
start,server start,Start the server(s).

openstackclient/compute/v2/server.py

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3585,25 +3585,115 @@ def take_action(self, parsed_args):
35853585

35863586

35873587
class ShelveServer(command.Command):
3588-
_description = _("Shelve server(s)")
3588+
"""Shelve and optionally offload server(s).
3589+
3590+
Shelving a server creates a snapshot of the server and stores this
3591+
snapshot before shutting down the server. This shelved server can then be
3592+
offloaded or deleted from the host, freeing up remaining resources on the
3593+
host, such as network interfaces. Shelved servers can be unshelved,
3594+
restoring the server from the snapshot. Shelving is therefore useful where
3595+
users wish to retain the UUID and IP of a server, without utilizing other
3596+
resources or disks.
3597+
3598+
Most clouds are configured to automatically offload shelved servers
3599+
immediately or after a small delay. For clouds where this is not
3600+
configured, or where the delay is larger, offloading can be manually
3601+
specified. This is an admin-only operation by default.
3602+
"""
35893603

35903604
def get_parser(self, prog_name):
35913605
parser = super(ShelveServer, self).get_parser(prog_name)
35923606
parser.add_argument(
3593-
'server',
3607+
'servers',
35943608
metavar='<server>',
35953609
nargs='+',
35963610
help=_('Server(s) to shelve (name or ID)'),
35973611
)
3612+
parser.add_argument(
3613+
'--offload',
3614+
action='store_true',
3615+
default=False,
3616+
help=_(
3617+
'Remove the shelved server(s) from the host (admin only). '
3618+
'Invoking this option on an unshelved server(s) will result '
3619+
'in the server being shelved first'
3620+
),
3621+
)
3622+
parser.add_argument(
3623+
'--wait',
3624+
action='store_true',
3625+
default=False,
3626+
help=_('Wait for shelve and/or offload operation to complete'),
3627+
)
35983628
return parser
35993629

36003630
def take_action(self, parsed_args):
3631+
3632+
def _show_progress(progress):
3633+
if progress:
3634+
self.app.stdout.write('\rProgress: %s' % progress)
3635+
self.app.stdout.flush()
3636+
36013637
compute_client = self.app.client_manager.compute
3602-
for server in parsed_args.server:
3603-
utils.find_resource(
3638+
3639+
for server in parsed_args.servers:
3640+
server_obj = utils.find_resource(
3641+
compute_client.servers,
3642+
server,
3643+
)
3644+
if server_obj.status.lower() in ('shelved', 'shelved_offloaded'):
3645+
continue
3646+
3647+
server_obj.shelve()
3648+
3649+
# if we don't hav to wait, either because it was requested explicitly
3650+
# or is required implicitly, then our job is done
3651+
if not parsed_args.wait and not parsed_args.offload:
3652+
return
3653+
3654+
for server in parsed_args.servers:
3655+
# TODO(stephenfin): We should wait for these in parallel using e.g.
3656+
# https://review.opendev.org/c/openstack/osc-lib/+/762503/
3657+
if not utils.wait_for_status(
3658+
compute_client.servers.get, server_obj.id,
3659+
success_status=('shelved', 'shelved_offloaded'),
3660+
callback=_show_progress,
3661+
):
3662+
LOG.error(_('Error shelving server: %s'), server_obj.id)
3663+
self.app.stdout.write(
3664+
_('Error shelving server: %s\n') % server_obj.id)
3665+
raise SystemExit
3666+
3667+
if not parsed_args.offload:
3668+
return
3669+
3670+
for server in parsed_args.servers:
3671+
server_obj = utils.find_resource(
36043672
compute_client.servers,
36053673
server,
3606-
).shelve()
3674+
)
3675+
if server_obj.status.lower() == 'shelved_offloaded':
3676+
continue
3677+
3678+
server_obj.shelve_offload()
3679+
3680+
if not parsed_args.wait:
3681+
return
3682+
3683+
for server in parsed_args.servers:
3684+
# TODO(stephenfin): We should wait for these in parallel using e.g.
3685+
# https://review.opendev.org/c/openstack/osc-lib/+/762503/
3686+
if not utils.wait_for_status(
3687+
compute_client.servers.get, server_obj.id,
3688+
success_status=('shelved_offloaded',),
3689+
callback=_show_progress,
3690+
):
3691+
LOG.error(
3692+
_('Error offloading shelved server %s'), server_obj.id)
3693+
self.app.stdout.write(
3694+
_('Error offloading shelved server: %s\n') % (
3695+
server_obj.id))
3696+
raise SystemExit
36073697

36083698

36093699
class ShowServer(command.ShowOne):

openstackclient/tests/functional/common/test_help.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class HelpTests(base.TestCase):
4343
('server resize', 'Scale server to a new flavor'),
4444
('server resume', 'Resume server(s)'),
4545
('server set', 'Set server properties'),
46-
('server shelve', 'Shelve server(s)'),
46+
('server shelve', 'Shelve and optionally offload server(s)'),
4747
('server show', 'Show server details'),
4848
('server ssh', 'SSH to server'),
4949
('server start', 'Start server(s).'),

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

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6434,16 +6434,126 @@ def setUp(self):
64346434
# Get the command object to test
64356435
self.cmd = server.ShelveServer(self.app, None)
64366436

6437-
# Set shelve method to be tested.
6438-
self.methods = {
6437+
def test_shelve(self):
6438+
server_info = {'status': 'ACTIVE'}
6439+
server_methods = {
64396440
'shelve': None,
6441+
'shelve_offload': None,
64406442
}
64416443

6442-
def test_shelve_one_server(self):
6443-
self.run_method_with_servers('shelve', 1)
6444+
server = compute_fakes.FakeServer.create_one_server(
6445+
attrs=server_info, methods=server_methods)
6446+
self.servers_mock.get.return_value = server
64446447

6445-
def test_shelve_multi_servers(self):
6446-
self.run_method_with_servers('shelve', 3)
6448+
arglist = [server.name]
6449+
verifylist = [
6450+
('servers', [server.name]),
6451+
('wait', False),
6452+
('offload', False),
6453+
]
6454+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6455+
6456+
result = self.cmd.take_action(parsed_args)
6457+
self.assertIsNone(result)
6458+
6459+
self.servers_mock.get.assert_called_once_with(server.name)
6460+
server.shelve.assert_called_once_with()
6461+
server.shelve_offload.assert_not_called()
6462+
6463+
def test_shelve_already_shelved(self):
6464+
server_info = {'status': 'SHELVED'}
6465+
server_methods = {
6466+
'shelve': None,
6467+
'shelve_offload': None,
6468+
}
6469+
6470+
server = compute_fakes.FakeServer.create_one_server(
6471+
attrs=server_info, methods=server_methods)
6472+
self.servers_mock.get.return_value = server
6473+
6474+
arglist = [server.name]
6475+
verifylist = [
6476+
('servers', [server.name]),
6477+
('wait', False),
6478+
('offload', False),
6479+
]
6480+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6481+
6482+
result = self.cmd.take_action(parsed_args)
6483+
self.assertIsNone(result)
6484+
6485+
self.servers_mock.get.assert_called_once_with(server.name)
6486+
server.shelve.assert_not_called()
6487+
server.shelve_offload.assert_not_called()
6488+
6489+
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
6490+
def test_shelve_with_wait(self, mock_wait_for_status):
6491+
server_info = {'status': 'ACTIVE'}
6492+
server_methods = {
6493+
'shelve': None,
6494+
'shelve_offload': None,
6495+
}
6496+
6497+
server = compute_fakes.FakeServer.create_one_server(
6498+
attrs=server_info, methods=server_methods)
6499+
self.servers_mock.get.return_value = server
6500+
6501+
arglist = ['--wait', server.name]
6502+
verifylist = [
6503+
('servers', [server.name]),
6504+
('wait', True),
6505+
('offload', False),
6506+
]
6507+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6508+
6509+
result = self.cmd.take_action(parsed_args)
6510+
self.assertIsNone(result)
6511+
6512+
self.servers_mock.get.assert_called_once_with(server.name)
6513+
server.shelve.assert_called_once_with()
6514+
server.shelve_offload.assert_not_called()
6515+
mock_wait_for_status.assert_called_once_with(
6516+
self.servers_mock.get,
6517+
server.id,
6518+
callback=mock.ANY,
6519+
success_status=('shelved', 'shelved_offloaded'),
6520+
)
6521+
6522+
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
6523+
def test_shelve_offload(self, mock_wait_for_status):
6524+
server_info = {'status': 'ACTIVE'}
6525+
server_methods = {
6526+
'shelve': None,
6527+
'shelve_offload': None,
6528+
}
6529+
6530+
server = compute_fakes.FakeServer.create_one_server(
6531+
attrs=server_info, methods=server_methods)
6532+
self.servers_mock.get.return_value = server
6533+
6534+
arglist = ['--offload', server.name]
6535+
verifylist = [
6536+
('servers', [server.name]),
6537+
('wait', False),
6538+
('offload', True),
6539+
]
6540+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6541+
6542+
result = self.cmd.take_action(parsed_args)
6543+
self.assertIsNone(result)
6544+
6545+
self.servers_mock.get.assert_has_calls([
6546+
mock.call(server.name),
6547+
mock.call(server.name),
6548+
])
6549+
server.shelve.assert_called_once_with()
6550+
server.shelve_offload.assert_called_once_with()
6551+
mock_wait_for_status.assert_called_once_with(
6552+
self.servers_mock.get,
6553+
server.id,
6554+
callback=mock.ANY,
6555+
success_status=('shelved', 'shelved_offloaded'),
6556+
)
64476557

64486558

64496559
class TestServerShow(TestServer):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
Add support for ``--offload`` and ``--wait`` options for ``server shelve``.
5+
``--offload`` allows users to explicitly request offloading of a shelved
6+
server in environments where automatic offloading is not configured, while
7+
``--wait`` allows users to wait for the shelve and/or shelve offload
8+
operations to complete.

0 commit comments

Comments
 (0)