Support revert to snapshot in client

This patch added revert to snapshot support in
cinder client, also fix two pylint errors.

Change-Id: I20d8df8d7bcf763f6651f44901a98f6d853b27ce
Partial-Implements: blueprint revert-volume-to-snapshot
Depends-On: cca9e1ac54d123da8859ff918b2355606bfa474e
This commit is contained in:
TommyLike 2017-05-16 09:52:08 +08:00 committed by xing-yang
parent b910f5bea3
commit 7dfbb239de
7 changed files with 73 additions and 1 deletions

@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
# key is a deprecated version and value is an alternative version. # key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {"1": "2"} DEPRECATED_VERSIONS = {"1": "2"}
DEPRECATED_VERSION = "2.0" DEPRECATED_VERSION = "2.0"
MAX_VERSION = "3.33" MAX_VERSION = "3.40"
MIN_VERSION = "3.0" MIN_VERSION = "3.0"
_SUBSTITUTIONS = {} _SUBSTITUTIONS = {}

@ -535,6 +535,8 @@ class FakeHTTPClient(base_client.HTTPClient):
elif action == 'os-volume_upload_image': elif action == 'os-volume_upload_image':
assert 'image_name' in body[action] assert 'image_name' in body[action]
_body = body _body = body
elif action == 'revert':
assert 'snapshot_id' in body[action]
else: else:
raise AssertionError("Unexpected action: %s" % action) raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, _body) return (resp, {}, _body)

@ -23,6 +23,7 @@ from cinderclient import client
from cinderclient import exceptions from cinderclient import exceptions
from cinderclient import shell from cinderclient import shell
from cinderclient.v3 import volumes from cinderclient.v3 import volumes
from cinderclient.v3 import volume_snapshots
from cinderclient.tests.unit import utils from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes from cinderclient.tests.unit.v3 import fakes
from cinderclient.tests.unit.fixture_data import keystone_client from cinderclient.tests.unit.fixture_data import keystone_client
@ -278,6 +279,18 @@ class ShellTest(utils.TestCase):
self.run_command(command) self.run_command(command)
self.assert_called('GET', '/attachments%s' % expected) self.assert_called('GET', '/attachments%s' % expected)
@mock.patch('cinderclient.shell_utils.find_volume_snapshot')
def test_revert_to_snapshot(self, mock_snapshot):
mock_snapshot.return_value = volume_snapshots.Snapshot(
self, {'id': '5678', 'volume_id': '1234'})
self.run_command(
'--os-volume-api-version 3.40 revert-to-snapshot 5678')
self.assert_called('POST', '/volumes/1234/action',
body={'revert': {'snapshot_id': '5678'}})
def test_attachment_show(self): def test_attachment_show(self):
self.run_command('--os-volume-api-version 3.27 attachment-show 1234') self.run_command('--os-volume-api-version 3.27 attachment-show 1234')
self.assert_called('GET', '/attachments/1234') self.assert_called('GET', '/attachments/1234')

@ -18,9 +18,11 @@
import ddt import ddt
from cinderclient import api_versions from cinderclient import api_versions
from cinderclient import exceptions
from cinderclient.tests.unit import utils from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes from cinderclient.tests.unit.v3 import fakes
from cinderclient.v3 import volumes from cinderclient.v3 import volumes
from cinderclient.v3 import volume_snapshots
from six.moves.urllib import parse from six.moves.urllib import parse
@ -48,6 +50,28 @@ class VolumesTest(utils.TestCase):
visibility='public', protected=True) visibility='public', protected=True)
cs.assert_called_anytime('POST', '/volumes/1234/action', body=expected) cs.assert_called_anytime('POST', '/volumes/1234/action', body=expected)
@ddt.data('3.39', '3.40')
def test_revert_to_snapshot(self, version):
api_version = api_versions.APIVersion(version)
cs = fakes.FakeClient(api_version)
manager = volumes.VolumeManager(cs)
fake_snapshot = volume_snapshots.Snapshot(
manager, {'id': 12345, 'name': 'fake-snapshot'}, loaded=True)
fake_volume = volumes.Volume(manager,
{'id': 1234, 'name': 'sample-volume'},
loaded=True)
expected = {'revert': {'snapshot_id': 12345}}
if version == '3.40':
fake_volume.revert_to_snapshot(fake_snapshot)
cs.assert_called_anytime('POST', '/volumes/1234/action',
body=expected)
else:
self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
fake_volume.revert_to_snapshot, fake_snapshot)
def test_create_volume(self): def test_create_volume(self):
vol = cs.volumes.create(1, group_id='1234', volume_type='5678') vol = cs.volumes.create(1, group_id='1234', volume_type='5678')
expected = {'volume': {'status': 'creating', expected = {'volume': {'status': 'creating',

@ -1371,6 +1371,19 @@ def do_api_version(cs, args):
utils.print_list(response, columns) utils.print_list(response, columns)
@api_versions.wraps("3.40")
@utils.arg(
'snapshot',
metavar='<snapshot>',
help='Name or ID of the snapshot to restore. The snapshot must be the '
'most recent one known to cinder.')
def do_revert_to_snapshot(cs, args):
"""Revert a volume to the specified snapshot."""
snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot)
volume = utils.find_volume(cs, snapshot.volume_id)
volume.revert_to_snapshot(snapshot)
@api_versions.wraps("3.3") @api_versions.wraps("3.3")
@utils.arg('--marker', @utils.arg('--marker',
metavar='<marker>', metavar='<marker>',

@ -45,6 +45,10 @@ class Volume(volumes.Volume):
return self.manager.upload_to_image(self, force, image_name, return self.manager.upload_to_image(self, force, image_name,
container_format, disk_format) container_format, disk_format)
def revert_to_snapshot(self, snapshot):
"""Revert a volume to a snapshot."""
self.manager.revert_to_snapshot(self, snapshot)
class VolumeManager(volumes.VolumeManager): class VolumeManager(volumes.VolumeManager):
resource_class = Volume resource_class = Volume
@ -109,6 +113,17 @@ class VolumeManager(volumes.VolumeManager):
return self._create('/volumes', body, 'volume') return self._create('/volumes', body, 'volume')
@api_versions.wraps('3.40')
def revert_to_snapshot(self, volume, snapshot):
"""Revert a volume to a snapshot.
The snapshot must be the most recent one known to cinder.
:param volume: volume object.
:param snapshot: snapshot object.
"""
return self._action('revert', volume,
info={'snapshot_id': base.getid(snapshot.id)})
@api_versions.wraps("3.0") @api_versions.wraps("3.0")
def delete_metadata(self, volume, keys): def delete_metadata(self, volume, keys):
"""Delete specified keys from volumes metadata. """Delete specified keys from volumes metadata.
@ -131,6 +146,7 @@ class VolumeManager(volumes.VolumeManager):
:param volume: The :class:`Volume`. :param volume: The :class:`Volume`.
:param keys: A list of keys to be removed. :param keys: A list of keys to be removed.
""" """
# pylint: disable=function-redefined
data = self._get("/volumes/%s/metadata" % base.getid(volume)) data = self._get("/volumes/%s/metadata" % base.getid(volume))
metadata = data._info.get("metadata", {}) metadata = data._info.get("metadata", {})
if set(keys).issubset(metadata.keys()): if set(keys).issubset(metadata.keys()):
@ -160,6 +176,7 @@ class VolumeManager(volumes.VolumeManager):
"""Upload volume to image service as image. """Upload volume to image service as image.
:param volume: The :class:`Volume` to upload. :param volume: The :class:`Volume` to upload.
""" """
# pylint: disable=function-redefined
return self._action('os-volume_upload_image', return self._action('os-volume_upload_image',
volume, volume,
{'force': force, {'force': force,

@ -0,0 +1,3 @@
---
features:
- Added support for the revert-to-snapshot feature.