Merge "Add --wait to server delete"
This commit is contained in:
commit
4d57e9f62a
@ -157,7 +157,11 @@ Delete server(s)
|
||||
.. code:: bash
|
||||
|
||||
os server delete
|
||||
<server> [<server> ...]
|
||||
<server> [<server> ...] [--wait]
|
||||
|
||||
.. option:: --wait
|
||||
|
||||
Wait for delete to complete
|
||||
|
||||
.. describe:: <server>
|
||||
|
||||
|
@ -283,6 +283,52 @@ def wait_for_status(status_f,
|
||||
return retval
|
||||
|
||||
|
||||
def wait_for_delete(manager,
|
||||
res_id,
|
||||
status_field='status',
|
||||
sleep_time=5,
|
||||
timeout=300,
|
||||
callback=None):
|
||||
"""Wait for resource deletion
|
||||
|
||||
:param res_id: the resource id to watch
|
||||
:param status_field: the status attribute in the returned resource object,
|
||||
this is used to check for error states while the resource is being
|
||||
deleted
|
||||
:param sleep_time: wait this long between checks (seconds)
|
||||
:param timeout: check until this long (seconds)
|
||||
:param callback: called per sleep cycle, useful to display progress; this
|
||||
function is passed a progress value during each iteration of the wait
|
||||
loop
|
||||
:rtype: True on success, False if the resource has gone to error state or
|
||||
the timeout has been reached
|
||||
"""
|
||||
total_time = 0
|
||||
while total_time < timeout:
|
||||
try:
|
||||
# might not be a bad idea to re-use find_resource here if it was
|
||||
# a bit more friendly in the exceptions it raised so we could just
|
||||
# handle a NotFound exception here without parsing the message
|
||||
res = manager.get(res_id)
|
||||
except Exception as ex:
|
||||
if type(ex).__name__ == 'NotFound':
|
||||
return True
|
||||
raise
|
||||
|
||||
status = getattr(res, status_field, '').lower()
|
||||
if status == 'error':
|
||||
return False
|
||||
|
||||
if callback:
|
||||
progress = getattr(res, 'progress', None) or 0
|
||||
callback(progress)
|
||||
time.sleep(sleep_time)
|
||||
total_time += sleep_time
|
||||
|
||||
# if we got this far we've timed out
|
||||
return False
|
||||
|
||||
|
||||
def get_effective_log_level():
|
||||
"""Returns the lowest logging level considered by logging handlers
|
||||
|
||||
|
@ -572,6 +572,11 @@ class DeleteServer(command.Command):
|
||||
nargs="+",
|
||||
help=_('Server(s) to delete (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--wait',
|
||||
action='store_true',
|
||||
help=_('Wait for delete to complete'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
@ -581,6 +586,18 @@ class DeleteServer(command.Command):
|
||||
server_obj = utils.find_resource(
|
||||
compute_client.servers, server)
|
||||
compute_client.servers.delete(server_obj.id)
|
||||
if parsed_args.wait:
|
||||
if utils.wait_for_delete(
|
||||
compute_client.servers,
|
||||
server_obj.id,
|
||||
callback=_show_progress,
|
||||
):
|
||||
sys.stdout.write('\n')
|
||||
else:
|
||||
self.log.error(_('Error deleting server: %s'),
|
||||
server_obj.id)
|
||||
sys.stdout.write(_('\nError deleting server'))
|
||||
raise SystemExit
|
||||
return
|
||||
|
||||
|
||||
|
@ -13,6 +13,9 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
|
||||
from openstackclient.common import exceptions
|
||||
@ -120,6 +123,42 @@ class TestUtils(test_utils.TestCase):
|
||||
utils.sort_items,
|
||||
items, sort_str)
|
||||
|
||||
@mock.patch.object(time, 'sleep')
|
||||
def test_wait_for_delete_ok(self, mock_sleep):
|
||||
# Tests the normal flow that the resource is deleted with a 404 coming
|
||||
# back on the 2nd iteration of the wait loop.
|
||||
resource = mock.MagicMock(status='ACTIVE', progress=None)
|
||||
mock_get = mock.Mock(side_effect=[resource,
|
||||
exceptions.NotFound(404)])
|
||||
manager = mock.MagicMock(get=mock_get)
|
||||
res_id = str(uuid.uuid4())
|
||||
callback = mock.Mock()
|
||||
self.assertTrue(utils.wait_for_delete(manager, res_id,
|
||||
callback=callback))
|
||||
mock_sleep.assert_called_once_with(5)
|
||||
callback.assert_called_once_with(0)
|
||||
|
||||
@mock.patch.object(time, 'sleep')
|
||||
def test_wait_for_delete_timeout(self, mock_sleep):
|
||||
# Tests that we fail if the resource is not deleted before the timeout.
|
||||
resource = mock.MagicMock(status='ACTIVE')
|
||||
mock_get = mock.Mock(return_value=resource)
|
||||
manager = mock.MagicMock(get=mock_get)
|
||||
res_id = str(uuid.uuid4())
|
||||
self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1,
|
||||
timeout=1))
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
@mock.patch.object(time, 'sleep')
|
||||
def test_wait_for_delete_error(self, mock_sleep):
|
||||
# Tests that we fail if the resource goes to error state while waiting.
|
||||
resource = mock.MagicMock(status='ERROR')
|
||||
mock_get = mock.Mock(return_value=resource)
|
||||
manager = mock.MagicMock(get=mock_get)
|
||||
res_id = str(uuid.uuid4())
|
||||
self.assertFalse(utils.wait_for_delete(manager, res_id))
|
||||
self.assertFalse(mock_sleep.called)
|
||||
|
||||
|
||||
class NoUniqueMatch(Exception):
|
||||
pass
|
||||
|
@ -16,6 +16,7 @@
|
||||
import copy
|
||||
import mock
|
||||
|
||||
from openstackclient.common import utils as common_utils
|
||||
from openstackclient.compute.v2 import server
|
||||
from openstackclient.tests.compute.v2 import fakes as compute_fakes
|
||||
from openstackclient.tests import fakes
|
||||
@ -319,6 +320,52 @@ class TestServerDelete(TestServer):
|
||||
compute_fakes.server_id,
|
||||
)
|
||||
|
||||
@mock.patch.object(common_utils, 'wait_for_delete', return_value=True)
|
||||
def test_server_delete_wait_ok(self, mock_wait_for_delete):
|
||||
arglist = [
|
||||
compute_fakes.server_id, '--wait'
|
||||
]
|
||||
verifylist = [
|
||||
('servers', [compute_fakes.server_id]),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.servers_mock.delete.assert_called_with(
|
||||
compute_fakes.server_id,
|
||||
)
|
||||
|
||||
mock_wait_for_delete.assert_called_once_with(
|
||||
self.servers_mock,
|
||||
compute_fakes.server_id,
|
||||
callback=server._show_progress
|
||||
)
|
||||
|
||||
@mock.patch.object(common_utils, 'wait_for_delete', return_value=False)
|
||||
def test_server_delete_wait_fails(self, mock_wait_for_delete):
|
||||
arglist = [
|
||||
compute_fakes.server_id, '--wait'
|
||||
]
|
||||
verifylist = [
|
||||
('servers', [compute_fakes.server_id]),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
|
||||
|
||||
self.servers_mock.delete.assert_called_with(
|
||||
compute_fakes.server_id,
|
||||
)
|
||||
|
||||
mock_wait_for_delete.assert_called_once_with(
|
||||
self.servers_mock,
|
||||
compute_fakes.server_id,
|
||||
callback=server._show_progress
|
||||
)
|
||||
|
||||
|
||||
class TestServerImageCreate(TestServer):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user