Cinder client reset-state improvements

Now we can use 'cinder reset-state' command for 'volume',
'snapshot', 'backup', 'group' and 'group-snapshot'.
Also change volume's default status from 'available' to
'None' when not any status is specified.

Co-Authored-By: Eric Harney <eharney@redhat.com>

Change-Id: I0aefeaf5ece74f1f2bc4b72d5705c8c088921e20
Partial-implements: blueprint client-reset-state-improvements
This commit is contained in:
TommyLike 2017-03-16 16:32:06 +08:00
parent 48fd320d5c
commit af921bb6b4
8 changed files with 224 additions and 2 deletions

View File

@ -56,6 +56,20 @@ def _stub_group_snapshot(detailed=True, **kwargs):
return group_snapshot
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
class FakeClient(fakes.FakeClient, client.Client):
def __init__(self, api_version=None, *args, **kwargs):
@ -399,6 +413,37 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
def put_group_snapshots_1234(self, **kw):
return (200, {}, {'group_snapshot': {}})
def post_groups_1234_action(self, **kw):
return (202, {}, {})
def get_groups_5678(self, **kw):
return (200, {}, {'group':
_stub_group(id='5678')})
def post_groups_5678_action(self, **kw):
return (202, {}, {})
def post_snapshots_1234_action(self, **kw):
return (202, {}, {})
def get_snapshots_1234(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
def post_snapshots_5678_action(self, **kw):
return (202, {}, {})
def get_snapshots_5678(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='5678')})
def post_group_snapshots_1234_action(self, **kw):
return (202, {}, {})
def post_group_snapshots_5678_action(self, **kw):
return (202, {}, {})
def get_group_snapshots_5678(self, **kw):
return (200, {}, {'group_snapshot': _stub_group_snapshot(id='5678')})
def delete_group_snapshots_1234(self, **kw):
return (202, {}, {})

View File

@ -454,6 +454,80 @@ class ShellTest(utils.TestCase):
self.run_command('--os-volume-api-version 3.3 message-list')
self.assert_called('GET', '/messages')
@ddt.data('volume', 'backup', 'snapshot', None)
def test_reset_state_entity_not_found(self, entity_type):
cmd = 'reset-state 999999'
if entity_type is not None:
cmd += ' --type %s' % entity_type
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@ddt.data({'entity_types': [{'name': 'volume', 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'backup', 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'snapshot', 'version': '3.0',
'command': 'os-reset_status'},
{'name': None, 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'group', 'version': '3.20',
'command': 'reset_status'},
{'name': 'group-snapshot', 'version': '3.19',
'command': 'reset_status'}],
'r_id': ['1234'],
'states': ['available', 'error', None]},
{'entity_types': [{'name': 'volume', 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'backup', 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'snapshot', 'version': '3.0',
'command': 'os-reset_status'},
{'name': None, 'version': '3.0',
'command': 'os-reset_status'},
{'name': 'group', 'version': '3.20',
'command': 'reset_status'},
{'name': 'group-snapshot', 'version': '3.19',
'command': 'reset_status'}],
'r_id': ['1234', '5678'],
'states': ['available', 'error', None]})
@ddt.unpack
def test_reset_state_normal(self, entity_types, r_id, states):
for state in states:
for t in entity_types:
if state is None:
expected = {t['command']: {}}
cmd = ('--os-volume-api-version '
'%s reset-state %s') % (t['version'],
' '.join(r_id))
else:
expected = {t['command']: {'status': state}}
cmd = ('--os-volume-api-version '
'%s reset-state '
'--state %s %s') % (t['version'],
state, ' '.join(r_id))
if t['name'] is not None:
cmd += ' --type %s' % t['name']
self.run_command(cmd)
name = t['name'] if t['name'] else 'volume'
for re in r_id:
self.assert_called_anytime('POST', '/%ss/%s/action'
% (name.replace('-', '_'), re),
body=expected)
@ddt.data({'command': '--attach-status detached',
'expected': {'attach_status': 'detached'}},
{'command': '--state in-use --attach-status attached',
'expected': {'status': 'in-use',
'attach_status': 'attached'}},
{'command': '--reset-migration-status',
'expected': {'migration_status': 'none'}})
@ddt.unpack
def test_reset_state_volume_additional_status(self, command, expected):
self.run_command('reset-state %s 1234' % command)
expected = {'os-reset_status': expected}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_snapshot_list_with_metadata(self):
self.run_command('--os-volume-api-version 3.22 '
'snapshot-list --metadata key1=val1')

View File

@ -99,7 +99,8 @@ class VolumeBackupManager(base.ManagerWithFind):
def reset_state(self, backup, state):
"""Update the specified volume backup with the provided state."""
return self._action('os-reset_status', backup, {'status': state})
return self._action('os-reset_status', backup,
{'status': state} if state else {})
def _action(self, action, backup, info=None, **kwargs):
"""Perform a volume backup action."""

View File

@ -17,6 +17,7 @@
from cinderclient.apiclient import base as common_base
from cinderclient import api_versions
from cinderclient import base
from cinderclient import utils
@ -34,6 +35,10 @@ class GroupSnapshot(base.Resource):
"""Update the name or description for this group snapshot."""
return self.manager.update(self, **kwargs)
def reset_state(self, state):
"""Reset the group snapshot's state with specified one."""
return self.manager.reset_state(self, state)
class GroupSnapshotManager(base.ManagerWithFind):
"""Manage :class:`GroupSnapshot` resources."""
@ -74,6 +79,16 @@ class GroupSnapshotManager(base.ManagerWithFind):
return self._get("/group_snapshots/%s" % group_snapshot_id,
"group_snapshot")
@api_versions.wraps('3.19')
def reset_state(self, group_snapshot, state):
"""Update the provided group snapshot with the provided state.
:param group_snapshot: The :class:`GroupSnapshot` to set the state.
:param state: The state of the group snapshot to be set.
"""
body = {'status': state} if state else {}
return self._action('reset_status', group_snapshot, body)
def list(self, detailed=True, search_opts=None):
"""Lists all group snapshots.

View File

@ -15,6 +15,7 @@
"""Group interface (v3 extension)."""
from cinderclient import api_versions
from cinderclient import base
from cinderclient.apiclient import base as common_base
from cinderclient import utils
@ -33,6 +34,10 @@ class Group(base.Resource):
"""Update the name or description for this group."""
return self.manager.update(self, **kwargs)
def reset_state(self, state):
"""Reset the group's state with specified one"""
return self.manager.reset_state(self, state)
class GroupManager(base.ManagerWithFind):
"""Manage :class:`Group` resources."""
@ -64,6 +69,16 @@ class GroupManager(base.ManagerWithFind):
return self._create('/groups', body, 'group')
@api_versions.wraps('3.20')
def reset_state(self, group, state):
"""Update the provided group with the provided state.
:param group: The :class:`Group` to set the state.
:param state: The state of the group to be set.
"""
body = {'status': state} if state else {}
return self._action('reset_status', group, body)
def create_from_src(self, group_snapshot_id, source_group_id,
name=None, description=None, user_id=None,
project_id=None):

View File

@ -33,6 +33,13 @@ from cinderclient import utils
from cinderclient.v2.shell import * # flake8: noqa
RESET_STATE_RESOURCES = {'volume': utils.find_volume,
'backup': shell_utils.find_backup,
'snapshot': shell_utils.find_volume_snapshot,
'group': shell_utils.find_group,
'group-snapshot': shell_utils.find_group_snapshot}
@utils.arg('--group_id',
metavar='<group_id>',
default=None,
@ -194,6 +201,63 @@ def do_list(cs, args):
sortby_index=sortby_index)
@utils.arg('entity', metavar='<entity>', nargs='+',
help='Name or ID of entity to update.')
@utils.arg('--type', metavar='<type>', default='volume',
choices=RESET_STATE_RESOURCES.keys(),
help="Type of entity to update. Available resources "
"are: 'volume', 'snapshot', 'backup', "
"'group' (since 3.20) and "
"'group-snapshot' (since 3.19), Default=volume.")
@utils.arg('--state', metavar='<state>', default=None,
help=("The state to assign to the entity. "
"NOTE: This command simply changes the state of the "
"entity in the database with no regard to actual status, "
"exercise caution when using. Default=None, that means the "
"state is unchanged."))
@utils.arg('--attach-status', metavar='<attach-status>', default=None,
help=('This only used in volume entity. The attach status to '
'assign to the volume in the DataBase, with no regard to '
'the actual status. Valid values are "attached" and '
'"detached". Default=None, that means the status '
'is unchanged.'))
@utils.arg('--reset-migration-status',
action='store_true',
help=('This only used in volume entity. Clears the migration '
'status of the volume in the DataBase that indicates the '
'volume is source or destination of volume migration, '
'with no regard to the actual status.'))
def do_reset_state(cs, args):
"""Explicitly updates the entity state in the Cinder database.
Being a database change only, this has no impact on the true state of the
entity and may not match the actual state. This can render a entity
unusable in the case of changing to the 'available' state.
"""
failure_count = 0
single = (len(args.entity) == 1)
migration_status = 'none' if args.reset_migration_status else None
collector = RESET_STATE_RESOURCES[args.type]
argument = (args.state,)
if args.type == 'volume':
argument += (args.attach_status, migration_status)
for entity in args.entity:
try:
collector(cs, entity).reset_state(*argument)
except Exception as e:
print(e)
failure_count += 1
msg = "Reset state for entity %s failed: %s" % (entity, e)
if not single:
print(msg)
if failure_count == len(args.entity):
msg = "Unable to reset the state for the specified entity(s)."
raise exceptions.CommandError(msg)
@utils.arg('size',
metavar='<size>',
nargs='?',

View File

@ -150,7 +150,8 @@ class SnapshotManager(base.ManagerWithFind):
def reset_state(self, snapshot, state):
"""Update the specified snapshot with the provided state."""
return self._action('os-reset_status', snapshot, {'status': state})
return self._action('os-reset_status', snapshot,
{'status': state} if state else {})
def _action(self, action, snapshot, info=None, **kwargs):
"""Perform a snapshot action."""

View File

@ -0,0 +1,7 @@
---
features:
- Use 'cinder reset-state' as generic resource reset
state command for resource 'volume', 'snapshot', 'backup'
'group' and 'group-snapshot'. Also change volume's
default status from 'available' to none when no
status is specified.