Add "--wait" option for force-deleting a share/snapshot/share-instance

Closes-Bug: #1898317
Change-Id: I04265d6bdf84038670a3188a99cd2d639ad62cfe
This commit is contained in:
kafilat-adeleke 2021-01-28 11:32:48 +01:00
parent 4eec42711a
commit b5b8bdbb98
3 changed files with 140 additions and 18 deletions

View File

@ -512,6 +512,38 @@ class ShellTest(test_utils.TestCase):
self.run_command('share-instance-force-delete 1234') self.run_command('share-instance-force-delete 1234')
manager_mock.force_delete.assert_called_once_with(share_instance) manager_mock.force_delete.assert_called_once_with(share_instance)
@ddt.data(('share_instance_xyz', ), ('share_instance_abc',
'share_instance_xyz'))
def test_share_instance_force_delete_wait(self, instances_to_delete):
fake_manager = mock.Mock()
fake_instances = [
share_instances.ShareInstance(fake_manager, {'id': '1234'})
for instance in instances_to_delete
]
instance_not_found_error = ("Delete for instance %s failed: No "
"instance with a name or "
"ID of '%s' exists.")
instances_are_not_found_errors = [
exceptions.CommandError(
instance_not_found_error % (instance, instance))
for instance in instances_to_delete
]
self.mock_object(
shell_v2, '_find_share_instance',
mock.Mock(side_effect=(
fake_instances + instances_are_not_found_errors)))
self.run_command(
'share-instance-force-delete %s --wait' % ' '.join(
instances_to_delete))
shell_v2._find_share_instance.assert_has_calls([
mock.call(self.shell.cs, instance) for instance in
instances_to_delete
])
fake_manager.force_delete.assert_has_calls([
mock.call(instance) for instance in fake_instances])
self.assertEqual(len(instances_to_delete),
fake_manager.force_delete.call_count)
def test_type_show_details(self): def test_type_show_details(self):
self.run_command('type-show 1234') self.run_command('type-show 1234')
self.assert_called_anytime('GET', '/types/1234') self.assert_called_anytime('GET', '/types/1234')
@ -956,6 +988,31 @@ class ShellTest(test_utils.TestCase):
uri = '/shares/%s' % share.id uri = '/shares/%s' % share.id
self.assert_called_anytime('DELETE', uri, clear_callstack=False) self.assert_called_anytime('DELETE', uri, clear_callstack=False)
@ddt.data(('share_xyz', ), ('share_abc', 'share_xyz'))
def test_force_delete_wait(self, shares_to_delete):
fake_manager = mock.Mock()
fake_shares = [
shares.Share(fake_manager, {'id': '1234'})
for share in shares_to_delete
]
share_not_found_error = ("Delete for share %s failed: No share with "
"a name or ID of '%s' exists.")
shares_are_not_found_errors = [
exceptions.CommandError(share_not_found_error % (share, share))
for share in shares_to_delete
]
self.mock_object(
shell_v2, '_find_share',
mock.Mock(side_effect=(fake_shares + shares_are_not_found_errors)))
self.run_command('force-delete %s --wait' % ' '.join(shares_to_delete))
shell_v2._find_share.assert_has_calls([
mock.call(self.shell.cs, share) for share in shares_to_delete
])
fake_manager.force_delete.assert_has_calls([
mock.call(share) for share in fake_shares])
self.assertEqual(len(shares_to_delete),
fake_manager.force_delete.call_count)
def test_list_snapshots(self): def test_list_snapshots(self):
self.run_command('snapshot-list') self.run_command('snapshot-list')
self.assert_called('GET', '/snapshots/detail') self.assert_called('GET', '/snapshots/detail')
@ -3353,26 +3410,36 @@ class ShellTest(test_utils.TestCase):
'DELETE', '/snapshots/%s' % snapshot.id, 'DELETE', '/snapshots/%s' % snapshot.id,
clear_callstack=False) clear_callstack=False)
@ddt.data(('1234', ), ('1234', '5678')) @ddt.data(('snapshot_xyz', ), ('snapshot_abc', 'snapshot_xyz'))
def test_snapshot_force_delete(self, snapshot_ids): def test_snapshot_force_delete_wait(self, snapshots_to_delete):
fake_manager = mock.Mock()
fake_snapshots = [ fake_snapshots = [
share_snapshots.ShareSnapshot('fake', {'id': snapshot_id}, True) share_snapshots.ShareSnapshot(fake_manager, {'id': '1234'})
for snapshot_id in snapshot_ids for snapshot in snapshots_to_delete
]
snapshot_not_found_error = ("Delete for snapshot %s failed: No "
"snapshot with a name or "
"ID of '%s' exists.")
snapshots_are_not_found_errors = [
exceptions.CommandError(
snapshot_not_found_error % (snapshot, snapshot))
for snapshot in snapshots_to_delete
] ]
self.mock_object( self.mock_object(
shell_v2, '_find_share_snapshot', shell_v2, '_find_share_snapshot',
mock.Mock(side_effect=fake_snapshots)) mock.Mock(side_effect=(
fake_snapshots + snapshots_are_not_found_errors)))
self.run_command('snapshot-force-delete %s' % ' '.join(snapshot_ids)) self.run_command(
'snapshot-force-delete %s --wait' % ' '.join(
snapshots_to_delete))
shell_v2._find_share_snapshot.assert_has_calls([ shell_v2._find_share_snapshot.assert_has_calls([
mock.call(self.shell.cs, s_id) for s_id in snapshot_ids mock.call(self.shell.cs, snapshot) for snapshot in
snapshots_to_delete
]) ])
for snapshot in fake_snapshots: fake_manager.force_delete.assert_has_calls([
self.assert_called_anytime( mock.call(snapshot) for snapshot in fake_snapshots])
'POST', '/snapshots/%s/action' % snapshot.id, self.assertEqual(len(snapshots_to_delete),
{'force_delete': None}, fake_manager.force_delete.call_count)
clear_callstack=False)
@ddt.data(('fake_type1', ), ('fake_type1', 'fake_type2')) @ddt.data(('fake_type1', ), ('fake_type1', 'fake_type2'))
def test_share_type_delete(self, type_ids): def test_share_type_delete(self, type_ids):

View File

@ -55,6 +55,7 @@ def _wait_for_resource_status(cs,
'share_replica': _find_share_replica, 'share_replica': _find_share_replica,
'share_group': _find_share_group, 'share_group': _find_share_group,
'share_group_snapshot': _find_share_group_snapshot, 'share_group_snapshot': _find_share_group_snapshot,
'share_instance': _find_share_instance,
} }
print_resource = { print_resource = {
@ -63,6 +64,7 @@ def _wait_for_resource_status(cs,
'share_replica': _print_share_replica, 'share_replica': _print_share_replica,
'share_group': _print_share_group, 'share_group': _print_share_group,
'share_group_snapshot': _print_share_group_snapshot, 'share_group_snapshot': _print_share_group_snapshot,
'share_instance': _print_share_instance,
} }
expected_status = expected_status or ('available', ) expected_status = expected_status or ('available', )
@ -80,7 +82,7 @@ def _wait_for_resource_status(cs,
'resource_type': resource_type.capitalize(), 'resource_type': resource_type.capitalize(),
'resource': resource.id, 'resource': resource.id,
} }
not_found_regex = "no %s .* exists" % resource_type not_found_regex = "no .* exists"
while True: while True:
if time_elapsed > poll_timeout: if time_elapsed > poll_timeout:
print_resource[resource_type](cs, resource) print_resource[resource_type](cs, resource)
@ -1686,12 +1688,20 @@ def do_delete(cs, args):
metavar='<share>', metavar='<share>',
nargs='+', nargs='+',
help='Name or ID of the share(s) to force delete.') help='Name or ID of the share(s) to force delete.')
@cliutils.arg(
'--wait',
action='store_true',
help='Wait for share to delete')
@cliutils.service_type('sharev2')
def do_force_delete(cs, args): def do_force_delete(cs, args):
"""Attempt force-delete of share, regardless of state (Admin only).""" """Attempt force-delete of share, regardless of state (Admin only)."""
failure_count = 0 failure_count = 0
shares_to_delete = []
for share in args.share: for share in args.share:
try: try:
_find_share(cs, share).force_delete() share_ref = _find_share(cs, share)
shares_to_delete.append(share_ref)
share_ref.force_delete()
except Exception as e: except Exception as e:
failure_count += 1 failure_count += 1
print("Delete for share %s failed: %s" % (share, e), print("Delete for share %s failed: %s" % (share, e),
@ -1699,6 +1709,12 @@ def do_force_delete(cs, args):
if failure_count == len(args.share): if failure_count == len(args.share):
raise exceptions.CommandError("Unable to force delete any of " raise exceptions.CommandError("Unable to force delete any of "
"specified shares.") "specified shares.")
if args.wait:
for share in shares_to_delete:
try:
_wait_for_share_status(cs, share, expected_status='deleted')
except exceptions.CommandError as e:
print(e, file=sys.stderr)
@api_versions.wraps("1.0", "2.8") @api_versions.wraps("1.0", "2.8")
@ -2358,12 +2374,20 @@ def do_share_instance_show(cs, args): # noqa
nargs='+', nargs='+',
help='Name or ID of the instance(s) to force delete.') help='Name or ID of the instance(s) to force delete.')
@api_versions.wraps("2.3") @api_versions.wraps("2.3")
@cliutils.arg(
'--wait',
action='store_true',
help='Wait for share instance deletion')
@cliutils.service_type('sharev2')
def do_share_instance_force_delete(cs, args): def do_share_instance_force_delete(cs, args):
"""Force-delete the share instance, regardless of state (Admin only).""" """Force-delete the share instance, regardless of state (Admin only)."""
failure_count = 0 failure_count = 0
instances_to_delete = []
for instance in args.instance: for instance in args.instance:
try: try:
_find_share_instance(cs, instance).force_delete() instance_ref = _find_share_instance(cs, instance)
instances_to_delete.append(instance_ref)
instance_ref.force_delete()
except Exception as e: except Exception as e:
failure_count += 1 failure_count += 1
print("Delete for share instance %s failed: %s" % (instance, e), print("Delete for share instance %s failed: %s" % (instance, e),
@ -2371,6 +2395,14 @@ def do_share_instance_force_delete(cs, args):
if failure_count == len(args.instance): if failure_count == len(args.instance):
raise exceptions.CommandError("Unable to force delete any of " raise exceptions.CommandError("Unable to force delete any of "
"specified share instances.") "specified share instances.")
if args.wait:
for instance in instances_to_delete:
try:
_wait_for_resource_status(
cs, instance, resource_type='share_instance',
expected_status='deleted')
except exceptions.CommandError as e:
print(e, file=sys.stderr)
@cliutils.arg( @cliutils.arg(
@ -2814,18 +2846,25 @@ def do_snapshot_delete(cs, args):
metavar='<snapshot>', metavar='<snapshot>',
nargs='+', nargs='+',
help='Name or ID of the snapshot(s) to force delete.') help='Name or ID of the snapshot(s) to force delete.')
@cliutils.arg(
'--wait',
action='store_true',
help='Wait for snapshot to delete')
@cliutils.service_type('sharev2')
def do_snapshot_force_delete(cs, args): def do_snapshot_force_delete(cs, args):
"""Attempt force-deletion of one or more snapshots. """Attempt force-deletion of one or more snapshots.
Regardless of the state (Admin only). Regardless of the state (Admin only).
""" """
failure_count = 0 failure_count = 0
snapshots_to_delete = []
for snapshot in args.snapshot: for snapshot in args.snapshot:
try: try:
snapshot_ref = _find_share_snapshot( snapshot_ref = _find_share_snapshot(
cs, snapshot) cs, snapshot)
cs.share_snapshots.force_delete(snapshot_ref) snapshots_to_delete.append(snapshot_ref)
snapshot_ref.force_delete()
except Exception as e: except Exception as e:
failure_count += 1 failure_count += 1
print("Delete for snapshot %s failed: %s" % ( print("Delete for snapshot %s failed: %s" % (
@ -2834,6 +2873,14 @@ def do_snapshot_force_delete(cs, args):
if failure_count == len(args.snapshot): if failure_count == len(args.snapshot):
raise exceptions.CommandError("Unable to force delete any of the " raise exceptions.CommandError("Unable to force delete any of the "
"specified snapshots.") "specified snapshots.")
if args.wait:
for snapshot in snapshots_to_delete:
try:
_wait_for_resource_status(
cs, snapshot, resource_type='snapshot',
expected_status='deleted')
except exceptions.CommandError as e:
print(e, file=sys.stderr)
@cliutils.arg( @cliutils.arg(

View File

@ -0,0 +1,8 @@
---
features:
- |
The commands "manila force-delete", "manila snapshot-force-delete"
and "manila share-instance-force-delete" now accept an optional
"--wait" that allows administrator users to let the client poll for the
completion of the operation.