Require confirmation to reset server state.
This change updates the server set state command to require confirmation before it is applied. The same pattern as project clean is used and a new --auto-approve flag is added to allow skipping the prompt. Operators often use reset state in cases that are incorrect this change updates the warning to be more explicit about when and when not to use it. Change-Id: Iab14739cf6043ad45ad49edff0580e81d75af2fd
This commit is contained in:

committed by
Stephen Finucane

parent
7ecdb69f08
commit
25cd1178b3
@@ -4462,15 +4462,27 @@ class SetServer(command.Command):
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--auto-approve',
|
||||
action='store_true',
|
||||
help=_(
|
||||
"Allow server state override without asking for confirmation"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--state',
|
||||
metavar='<state>',
|
||||
choices=['active', 'error'],
|
||||
help=_(
|
||||
'New server state '
|
||||
'**WARNING** This can result in instances that are no longer '
|
||||
'usable and should be used with caution '
|
||||
'(admin only)'
|
||||
'New server state.'
|
||||
'**WARNING** Resetting the state is intended to work around '
|
||||
'servers stuck in an intermediate state, such as deleting. '
|
||||
'If the server is in an error state then this is almost '
|
||||
'never the correct command to run and you should prefer hard '
|
||||
'reboot where possible. In particular, if the server is in '
|
||||
'an error state due to a move operation, setting the state '
|
||||
'can result in instances that are no longer usable. Proceed '
|
||||
'with caution. (admin only)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -4505,6 +4517,20 @@ class SetServer(command.Command):
|
||||
)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def ask_user_yesno(msg):
|
||||
"""Ask user Y/N question
|
||||
|
||||
:param str msg: question text
|
||||
:return bool: User choice
|
||||
"""
|
||||
while True:
|
||||
answer = getpass.getpass('{} [{}]: '.format(msg, 'y/n'))
|
||||
if answer in ('y', 'Y', 'yes'):
|
||||
return True
|
||||
elif answer in ('n', 'N', 'no'):
|
||||
return False
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
server = compute_client.find_server(
|
||||
@@ -4555,6 +4581,17 @@ class SetServer(command.Command):
|
||||
)
|
||||
|
||||
if parsed_args.state:
|
||||
if not parsed_args.auto_approve:
|
||||
if not self.ask_user_yesno(
|
||||
_(
|
||||
"Resetting the server state can make it much harder "
|
||||
"to recover a server from an error state. If the "
|
||||
"server is in error status due to a failed move "
|
||||
"operation then this is likely not the correct "
|
||||
"approach to fix the problem. Do you wish to continue?"
|
||||
)
|
||||
):
|
||||
return
|
||||
compute_client.reset_server_state(server, state=parsed_args.state)
|
||||
|
||||
if parsed_args.root_password:
|
||||
|
@@ -7935,6 +7935,7 @@ class TestServerSet(TestServer):
|
||||
arglist = [
|
||||
'--state',
|
||||
'active',
|
||||
'--auto-approve',
|
||||
self.server.id,
|
||||
]
|
||||
verifylist = [
|
||||
@@ -7955,6 +7956,54 @@ class TestServerSet(TestServer):
|
||||
self.compute_client.add_tag_to_server.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_server_set_with_state_prompt_y(self):
|
||||
arglist = [
|
||||
'--state',
|
||||
'active',
|
||||
self.server.id,
|
||||
]
|
||||
verifylist = [
|
||||
('state', 'active'),
|
||||
('server', self.server.id),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
with mock.patch('getpass.getpass', return_value='y'):
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_client.reset_server_state.assert_called_once_with(
|
||||
self.server, state='active'
|
||||
)
|
||||
self.compute_client.update_server.assert_not_called()
|
||||
self.compute_client.set_server_metadata.assert_not_called()
|
||||
self.compute_client.change_server_password.assert_not_called()
|
||||
self.compute_client.clear_server_password.assert_not_called()
|
||||
self.compute_client.add_tag_to_server.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_server_set_with_state_prompt_n(self):
|
||||
arglist = [
|
||||
'--state',
|
||||
'active',
|
||||
self.server.id,
|
||||
]
|
||||
verifylist = [
|
||||
('state', 'active'),
|
||||
('server', self.server.id),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
with mock.patch('getpass.getpass', return_value='n'):
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_client.reset_server_state.assert_not_called()
|
||||
self.compute_client.update_server.assert_not_called()
|
||||
self.compute_client.set_server_metadata.assert_not_called()
|
||||
self.compute_client.change_server_password.assert_not_called()
|
||||
self.compute_client.clear_server_password.assert_not_called()
|
||||
self.compute_client.add_tag_to_server.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_server_set_with_invalid_state(self):
|
||||
arglist = [
|
||||
'--state',
|
||||
|
@@ -0,0 +1,6 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The ``openstack server set`` command has been extended with a new
|
||||
parameter ``--auto-approve`` and the existing ``--state`` parameter
|
||||
has been modified to require confirmation before resetting the state.
|
Reference in New Issue
Block a user