Microversion 2.73: Support adding the reason behind a server lock
This patch adds a new parameter ``--reason`` to ``nova lock`` command and ``--locked`` filtering/sorting parameter to ``nova list`` command. This can help users to provide a reason when locking the server and to filter/sort instances based on their locked or value from 2.73 microversion. Implements blueprint add-locked-reason Depends-On: https://review.opendev.org/#/c/648662/ Change-Id: I438e6db2dd5000ba388d0a0f1c8ab74b96b47a71
This commit is contained in:
parent
fe4138aea4
commit
a1ac69c69a
@ -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.72")
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.73")
|
||||
|
@ -454,6 +454,8 @@ class V1(Base):
|
||||
pass
|
||||
elif action == 'migrate':
|
||||
return None
|
||||
elif action == 'lock':
|
||||
return None
|
||||
elif action == 'rebuild':
|
||||
body = body[action]
|
||||
adminPass = body.get('adminPass', 'randompassword')
|
||||
|
@ -774,7 +774,7 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
|
||||
none_actions = ['revertResize', 'os-stop', 'os-start',
|
||||
'forceDelete', 'restore', 'pause', 'unpause', 'unlock',
|
||||
'unrescue', 'resume', 'suspend', 'lock', 'shelve',
|
||||
'unrescue', 'resume', 'suspend', 'shelve',
|
||||
'shelveOffload', 'unshelve', 'resetNetwork']
|
||||
type_actions = ['os-getVNCConsole', 'os-getSPICEConsole',
|
||||
'os-getRDPConsole']
|
||||
@ -836,6 +836,22 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
# host can be optional
|
||||
expected.add('host')
|
||||
assert set(body[action].keys()) == expected
|
||||
elif action == 'lock':
|
||||
if self.api_version < api_versions.APIVersion("2.73"):
|
||||
assert body[action] is None
|
||||
else:
|
||||
# In 2.73 and above, we allow body to be one of these:
|
||||
# a) {'lock': None}
|
||||
# b) {'lock': {}}
|
||||
# c) {'lock': {locked_reason': 'blah'}}
|
||||
if body[action] is not None:
|
||||
expected = set()
|
||||
if 'locked_reason' in body[action].keys():
|
||||
# reason can be optional
|
||||
expected.add('locked_reason')
|
||||
assert set(body[action].keys()) == expected
|
||||
else:
|
||||
assert body[action] is None
|
||||
elif action == 'rebuild':
|
||||
body = body[action]
|
||||
adminPass = body.get('adminPass', 'randompassword')
|
||||
|
@ -1703,3 +1703,26 @@ class ServersV268Test(ServersV267Test):
|
||||
ex = self.assertRaises(TypeError, self.cs.servers.live_migrate,
|
||||
host='hostname', force=True)
|
||||
self.assertIn('force', six.text_type(ex))
|
||||
|
||||
|
||||
class ServersV273Test(ServersV268Test):
|
||||
|
||||
api_version = "2.73"
|
||||
|
||||
def test_lock_server(self):
|
||||
s = self.cs.servers.get(1234)
|
||||
ret = s.lock()
|
||||
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action',
|
||||
{'lock': None})
|
||||
ret = s.lock(reason='zombie-apocalypse')
|
||||
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action',
|
||||
{'lock': {'locked_reason': 'zombie-apocalypse'}})
|
||||
|
||||
def test_lock_server_pre_273_fails_with_reason(self):
|
||||
self.cs.api_version = api_versions.APIVersion('2.72')
|
||||
s = self.cs.servers.get(1234)
|
||||
e = self.assertRaises(TypeError,
|
||||
s.lock, reason='blah')
|
||||
self.assertIn("unexpected keyword argument 'reason'", six.text_type(e))
|
||||
|
@ -2092,6 +2092,24 @@ class ShellTest(utils.TestCase):
|
||||
self.run_command('lock sample-server')
|
||||
self.assert_called('POST', '/servers/1234/action', {'lock': None})
|
||||
|
||||
def test_lock_pre_v273(self):
|
||||
exp = self.assertRaises(SystemExit,
|
||||
self.run_command,
|
||||
'lock sample-server --reason zombies',
|
||||
api_version='2.72')
|
||||
self.assertIn('2', six.text_type(exp))
|
||||
|
||||
def test_lock_v273(self):
|
||||
self.run_command('lock sample-server',
|
||||
api_version='2.73')
|
||||
self.assert_called('POST', '/servers/1234/action',
|
||||
{'lock': None})
|
||||
|
||||
self.run_command('lock sample-server --reason zombies',
|
||||
api_version='2.73')
|
||||
self.assert_called('POST', '/servers/1234/action',
|
||||
{'lock': {'locked_reason': 'zombies'}})
|
||||
|
||||
def test_unlock(self):
|
||||
self.run_command('unlock sample-server')
|
||||
self.assert_called('POST', '/servers/1234/action', {'unlock': None})
|
||||
@ -4280,6 +4298,22 @@ class ShellTest(utils.TestCase):
|
||||
self.assert_called('GET', '/servers/9015', pos=2)
|
||||
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=3)
|
||||
|
||||
def test_list_pre_v273(self):
|
||||
exp = self.assertRaises(SystemExit,
|
||||
self.run_command,
|
||||
'list --locked t',
|
||||
api_version='2.72')
|
||||
self.assertEqual(2, exp.code)
|
||||
|
||||
def test_list_v273(self):
|
||||
self.run_command('list --locked t', api_version='2.73')
|
||||
self.assert_called('GET', '/servers/detail?locked=t')
|
||||
|
||||
def test_list_v273_with_sort_key_dir(self):
|
||||
self.run_command('list --sort locked:asc', api_version='2.73')
|
||||
self.assert_called(
|
||||
'GET', '/servers/detail?sort_dir=asc&sort_key=locked')
|
||||
|
||||
|
||||
class PollForStatusTestCase(utils.TestCase):
|
||||
@mock.patch("novaclient.v2.shell.time")
|
||||
|
@ -214,6 +214,7 @@ class Server(base.Resource):
|
||||
"""
|
||||
return self.manager.unpause(self)
|
||||
|
||||
@api_versions.wraps("2.0", "2.72")
|
||||
def lock(self):
|
||||
"""
|
||||
Lock -- Lock the instance from certain operations.
|
||||
@ -222,6 +223,16 @@ class Server(base.Resource):
|
||||
"""
|
||||
return self.manager.lock(self)
|
||||
|
||||
@api_versions.wraps("2.73")
|
||||
def lock(self, reason=None):
|
||||
"""
|
||||
Lock -- Lock the instance from certain operations.
|
||||
|
||||
:param reason: (Optional) The lock reason.
|
||||
:returns: An instance of novaclient.base.TupleWithMeta
|
||||
"""
|
||||
return self.manager.lock(self, reason=reason)
|
||||
|
||||
def unlock(self):
|
||||
"""
|
||||
Unlock -- Remove instance lock.
|
||||
@ -1097,6 +1108,7 @@ class ServerManager(base.BootingManagerWithFind):
|
||||
"""
|
||||
return self._action('unpause', server, None)
|
||||
|
||||
@api_versions.wraps("2.0", "2.72")
|
||||
def lock(self, server):
|
||||
"""
|
||||
Lock the server.
|
||||
@ -1106,6 +1118,22 @@ class ServerManager(base.BootingManagerWithFind):
|
||||
"""
|
||||
return self._action('lock', server, None)
|
||||
|
||||
@api_versions.wraps("2.73")
|
||||
def lock(self, server, reason=None):
|
||||
"""
|
||||
Lock the server.
|
||||
|
||||
:param server: The :class:`Server` (or its ID) to lock
|
||||
:param reason: (Optional) The lock reason.
|
||||
:returns: An instance of novaclient.base.TupleWithMeta
|
||||
"""
|
||||
info = None
|
||||
|
||||
if reason:
|
||||
info = {'locked_reason': reason}
|
||||
|
||||
return self._action('lock', server, info)
|
||||
|
||||
def unlock(self, server):
|
||||
"""
|
||||
Unlock the server.
|
||||
|
@ -1563,6 +1563,13 @@ def _print_flavor(flavor):
|
||||
"case is 'NOT(t1 OR t2)'. Tags must be separated by commas: "
|
||||
"--not-tags-any <tag1,tag2>"),
|
||||
start_version="2.26")
|
||||
@utils.arg(
|
||||
'--locked',
|
||||
dest='locked',
|
||||
metavar='<locked>',
|
||||
default=None,
|
||||
help=_('Display servers based on their locked value'),
|
||||
start_version="2.73")
|
||||
def do_list(cs, args):
|
||||
"""List servers."""
|
||||
imageid = None
|
||||
@ -1639,6 +1646,11 @@ def do_list(cs, args):
|
||||
raise exceptions.CommandError(_('Invalid changes-before value: %s')
|
||||
% search_opts['changes-before'])
|
||||
|
||||
# In microversion 2.73 we added ``locked`` option in server details.
|
||||
have_added_locked = cs.api_version >= api_versions.APIVersion('2.73')
|
||||
if have_added_locked and args.locked:
|
||||
search_opts['locked'] = args.locked
|
||||
|
||||
servers = cs.servers.list(detailed=detailed,
|
||||
search_opts=search_opts,
|
||||
sort_keys=sort_keys,
|
||||
@ -2155,12 +2167,23 @@ def do_start(cs, args):
|
||||
_("Unable to start the specified server(s)."))
|
||||
|
||||
|
||||
# From microversion 2.73, we can specify a reason for locking the server.
|
||||
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
|
||||
@utils.arg(
|
||||
'--reason',
|
||||
metavar='<reason>',
|
||||
help=_('Reason for locking the server.'),
|
||||
start_version='2.73')
|
||||
def do_lock(cs, args):
|
||||
"""Lock a server. A normal (non-admin) user will not be able to execute
|
||||
actions on a locked server.
|
||||
"""
|
||||
_find_server(cs, args.server).lock()
|
||||
update_kwargs = {}
|
||||
if 'reason' in args and args.reason is not None:
|
||||
update_kwargs['reason'] = args.reason
|
||||
|
||||
server = _find_server(cs, args.server)
|
||||
server.lock(**update_kwargs)
|
||||
|
||||
|
||||
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added a ``--reason`` option to ``nova lock`` command that enables users
|
||||
to specify a reason when locking a server and a ``locked``
|
||||
filtering/sorting option to ``nova list`` command which enables users to
|
||||
filter/sort servers based on their ``locked`` value in microversion 2.73.
|
Loading…
Reference in New Issue
Block a user