support for snapshot management

Add support to manage/unmanage snapshot

cinder snapshot-manage volume identifier
option args is --id-type, --name, --description,
--metadata.

cinder snapshot-unmanage snapshot

DocImpact

Implements: blueprint support-for-snapshot-management
Change-Id: Id4d73fa0ff0c0c05c0c69924968aa2154da64118
This commit is contained in:
wanghao
2015-11-24 17:13:19 +08:00
parent 7f9a81bb1b
commit 948e90010f
6 changed files with 123 additions and 0 deletions

View File

@@ -346,6 +346,8 @@ class FakeHTTPClient(base_client.HTTPClient):
assert 'status' in body['os-reset_status']
elif action == 'os-update_snapshot_status':
assert 'status' in body['os-update_snapshot_status']
elif action == 'os-unmanage':
assert body[action] is None
else:
raise AssertionError('Unexpected action: %s' % action)
return (resp, {}, _body)
@@ -1074,6 +1076,11 @@ class FakeHTTPClient(base_client.HTTPClient):
volume.update(kw['body']['volume'])
return (202, {}, {'volume': volume})
def post_os_snapshot_manage(self, **kw):
snapshot = _stub_snapshot(id='1234', volume_id='volume_id1')
snapshot.update(kw['body']['snapshot'])
return (202, {}, {'snapshot': snapshot})
def post_os_promote_replica_1234(self, **kw):
return (202, {}, {})

View File

@@ -1151,3 +1151,21 @@ class ShellTest(utils.TestCase):
pass
expected = {"os-show_image_metadata": None}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_snapshot_manage(self):
self.run_command('snapshot-manage 1234 some_fake_name '
'--name foo --description bar '
'--metadata k1=v1 k2=v2')
expected = {'snapshot': {'volume_id': 1234,
'ref': {'source-name': 'some_fake_name'},
'name': 'foo',
'description': 'bar',
'metadata': {'k1': 'v1', 'k2': 'v2'}
}}
self.assert_called_anytime('POST', '/os-snapshot-manage',
body=expected)
def test_snapshot_unmanage(self):
self.run_command('snapshot-unmanage 1234')
self.assert_called('POST', '/snapshots/1234/action',
body={'os-unmanage': None})

View File

@@ -42,3 +42,9 @@ class SnapshotActionsTest(utils.FixturedTestCase):
def test_list_snapshots_with_sort(self):
self.cs.volume_snapshots.list(sort="id")
self.assert_called('GET', '/snapshots/detail?sort=id')
def test_snapshot_unmanage(self):
s = self.cs.volume_snapshots.get('1234')
self.cs.volume_snapshots.unmanage(s)
self.assert_called('POST', '/snapshots/1234/action',
{'os-unmanage': None})

View File

@@ -227,6 +227,12 @@ class VolumesTest(utils.TestCase):
cs.volumes.unmanage(v)
cs.assert_called('POST', '/volumes/1234/action', {'os-unmanage': None})
def test_snapshot_manage(self):
cs.volume_snapshots.manage('volume_id1', {'k': 'v'})
expected = {'volume_id': 'volume_id1', 'name': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'}}
cs.assert_called('POST', '/os-snapshot-manage', {'snapshot': expected})
def test_replication_promote(self):
v = cs.volumes.get('1234')
cs.volumes.promote(v)

View File

@@ -2535,3 +2535,63 @@ def do_get_capabilities(cs, args):
prop = infos.pop('properties', None)
utils.print_dict(infos, "Volume stats")
utils.print_dict(prop, "Backend properties")
@utils.arg('volume',
metavar='<volume>',
help='Cinder volume already exists in volume backend')
@utils.arg('identifier',
metavar='<identifier>',
help='Name or other Identifier for existing snapshot')
@utils.arg('--id-type',
metavar='<id-type>',
default='source-name',
help='Type of backend device identifier provided, '
'typically source-name or source-id (Default=source-name)')
@utils.arg('--name',
metavar='<name>',
help='Snapshot name (Default=None)')
@utils.arg('--description',
metavar='<description>',
help='Snapshot description (Default=None)')
@utils.arg('--metadata',
type=str,
nargs='*',
metavar='<key=value>',
help='Metadata key=value pairs (Default=None)')
@utils.service_type('volumev2')
def do_snapshot_manage(cs, args):
"""Manage an existing snapshot."""
snapshot_metadata = None
if args.metadata is not None:
snapshot_metadata = _extract_metadata(args)
# Build a dictionary of key/value pairs to pass to the API.
ref_dict = {args.id_type: args.identifier}
if hasattr(args, 'source_name') and args.source_name is not None:
ref_dict['source-name'] = args.source_name
if hasattr(args, 'source_id') and args.source_id is not None:
ref_dict['source-id'] = args.source_id
volume = utils.find_volume(cs, args.volume)
snapshot = cs.volume_snapshots.manage(volume_id=volume.id,
ref=ref_dict,
name=args.name,
description=args.description,
metadata=snapshot_metadata)
info = {}
snapshot = cs.volume_snapshots.get(snapshot.id)
info.update(snapshot._info)
info.pop('links', None)
utils.print_dict(info)
@utils.arg('snapshot', metavar='<snapshot>',
help='Name or ID of the snapshot to unmanage.')
@utils.service_type('volumev2')
def do_snapshot_unmanage(cs, args):
"""Stop managing a snapshot."""
snapshot = _find_volume_snapshot(cs, args.snapshot)
cs.volume_snapshots.unmanage(snapshot.id)

View File

@@ -56,6 +56,16 @@ class Snapshot(base.Resource):
"""Update_all metadata of this snapshot."""
return self.manager.update_all_metadata(self, metadata)
def manage(self, volume_id, ref, name=None, description=None,
metadata=None):
"""Manage an existing snapshot."""
self.manager.manage(volume_id=volume_id, ref=ref, name=name,
description=description, metadata=metadata)
def unmanage(self, snapshot):
"""Unmanage a snapshot."""
self.manager.unmanage(snapshot)
class SnapshotManager(base.ManagerWithFind):
"""Manage :class:`Snapshot` resources."""
@@ -170,3 +180,19 @@ class SnapshotManager(base.ManagerWithFind):
body = {'metadata': metadata}
return self._update("/snapshots/%s/metadata" % base.getid(snapshot),
body)
def manage(self, volume_id, ref, name=None, description=None,
metadata=None):
"""Manage an existing snapshot."""
body = {'snapshot': {'volume_id': volume_id,
'ref': ref,
'name': name,
'description': description,
'metadata': metadata
}
}
return self._create('/os-snapshot-manage', body, 'snapshot')
def unmanage(self, snapshot):
"""Unmanage a snapshot."""
return self._action('os-unmanage', snapshot, None)