Support share revert to snapshot in Manila client

This commit adds support to Manila client for the
share-revert-to-snapshot feature.

Implements: blueprint manila-share-revert-to-snapshot
Depends-On: Id497e13070e0003db2db951526a52de6c2182cca
Change-Id: Ia9c63ac416db47baae78b02ffcab5a60892cbf5c
This commit is contained in:
Clinton Knight 2016-06-08 13:43:21 -07:00
parent c91deb8498
commit d736acc442
11 changed files with 269 additions and 11 deletions

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__)
MAX_VERSION = '2.26'
MAX_VERSION = '2.27'
MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {}

View File

@ -172,7 +172,8 @@ class BaseTestCase(base.ClientTestBase):
@classmethod
def create_share_type(cls, name=None, driver_handles_share_servers=True,
snapshot_support=None,
create_share_from_snapshot=None, is_public=True,
create_share_from_snapshot=None,
revert_to_snapshot=None, is_public=True,
client=None, cleanup_in_class=True,
microversion=None, extra_specs=None):
if client is None:
@ -185,6 +186,7 @@ class BaseTestCase(base.ClientTestBase):
microversion=microversion,
extra_specs=extra_specs,
create_share_from_snapshot=create_share_from_snapshot,
revert_to_snapshot=revert_to_snapshot
)
resource = {
"type": "share_type",

View File

@ -164,7 +164,8 @@ class ManilaCLIClient(base.CLIClient):
def create_share_type(self, name=None, driver_handles_share_servers=True,
snapshot_support=None,
create_share_from_snapshot=None, is_public=True,
create_share_from_snapshot=None,
revert_to_snapshot=None, is_public=True,
microversion=None, extra_specs=None):
"""Creates share type.
@ -179,6 +180,8 @@ class ManilaCLIClient(base.CLIClient):
:param extra_specs: -- dictionary of extra specs Default is None.
:param create_share_from_snapshot: -- boolean or its string
alias. Default is None.
:param revert_to_snapshot: -- boolean or its string alias. Default is
None.
"""
if name is None:
name = data_utils.rand_name('manilaclient_functional_test')
@ -194,14 +197,20 @@ class ManilaCLIClient(base.CLIClient):
if snapshot_support is not None:
if not isinstance(snapshot_support, six.string_types):
snapshot_support = six.text_type(snapshot_support)
cmd += " --snapshot-support " + snapshot_support
cmd += " --snapshot-support " + snapshot_support
if create_share_from_snapshot is not None:
if not isinstance(create_share_from_snapshot, six.string_types):
create_share_from_snapshot = six.text_type(
create_share_from_snapshot)
cmd += (" --create-share-from-snapshot-support " +
create_share_from_snapshot)
cmd += (" --create-share-from-snapshot-support " +
create_share_from_snapshot)
if revert_to_snapshot is not None:
if not isinstance(revert_to_snapshot, six.string_types):
revert_to_snapshot = six.text_type(
revert_to_snapshot)
cmd += (" --revert-to-snapshot-support " + revert_to_snapshot)
if extra_specs is not None:
extra_spec_str = ''

View File

@ -90,7 +90,7 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
self.skip_if_microversion_not_supported('2.0')
self._test_create_delete_share_type(
'2.0', is_public, dhss, spec_snapshot_support,
None, extra_specs)
None, None, extra_specs)
@ddt.data(*unit_test_types.get_valid_type_create_data_2_24())
@ddt.unpack
@ -101,11 +101,25 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
self.skip_if_microversion_not_supported('2.24')
self._test_create_delete_share_type(
'2.24', is_public, dhss, spec_snapshot_support,
spec_create_share_from_snapshot, extra_specs)
spec_create_share_from_snapshot, None, extra_specs)
@ddt.data(*unit_test_types.get_valid_type_create_data_2_27())
@ddt.unpack
def test_create_delete_share_type_2_27(
self, is_public, dhss, spec_snapshot_support,
spec_create_share_from_snapshot, spec_revert_to_snapshot_support,
extra_specs):
self.skip_if_microversion_not_supported('2.27')
self._test_create_delete_share_type(
'2.27', is_public, dhss, spec_snapshot_support,
spec_create_share_from_snapshot, spec_revert_to_snapshot_support,
extra_specs)
def _test_create_delete_share_type(self, microversion, is_public, dhss,
spec_snapshot_support,
spec_create_share_from_snapshot,
spec_revert_to_snapshot_support,
extra_specs):
share_type_name = data_utils.rand_name('manilaclient_functional_test')
@ -119,6 +133,7 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
driver_handles_share_servers=dhss,
snapshot_support=spec_snapshot_support,
create_share_from_snapshot=spec_create_share_from_snapshot,
revert_to_snapshot=spec_revert_to_snapshot_support,
is_public=is_public,
microversion=microversion,
extra_specs=extra_specs)
@ -163,6 +178,11 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
('{} : {}'.format(
'create_share_from_snapshot_support',
spec_create_share_from_snapshot)).strip())
if spec_revert_to_snapshot_support is not None:
expected_extra_specs.append(
('{} : {}'.format(
'revert_to_snapshot_support',
spec_revert_to_snapshot_support)).strip())
# Verify optional extra specs
optional_extra_specs = share_type['optional_extra_specs']

View File

@ -402,6 +402,9 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
assert body[action]['new_size'] is not None
elif action in ('unmanage', ):
assert body[action] is None
elif action in ('revert', ):
assert body[action] is not None
assert body[action]['snapshot_id'] is not None
elif action in (
'migration_cancel', 'migration_complete',
'migration_get_progress'):

View File

@ -19,6 +19,7 @@ import ddt
import mock
from manilaclient import api_versions
from manilaclient.common.apiclient import exceptions as client_exceptions
from manilaclient import exceptions
from manilaclient import extension
from manilaclient.tests.unit import utils
@ -239,6 +240,35 @@ class SharesTest(utils.TestCase):
manager._action.assert_called_once_with("unmanage", share)
self.assertEqual("fake", result)
def test_revert_to_snapshot(self):
share = 'fake_share'
snapshot = 'fake_snapshot'
version = api_versions.APIVersion("2.27")
mock_microversion = mock.Mock(api_version=version)
manager = shares.ShareManager(api=mock_microversion)
mock_action = self.mock_object(
manager, '_action', mock.Mock(return_value='fake'))
result = manager.revert_to_snapshot(share, snapshot)
self.assertEqual('fake', result)
mock_action.assert_called_once_with(
'revert', 'fake_share', info={'snapshot_id': 'fake_snapshot'})
def test_revert_to_snapshot_not_supported(self):
share = 'fake_share'
snapshot = 'fake_snapshot'
version = api_versions.APIVersion("2.26")
mock_microversion = mock.Mock(api_version=version)
manager = shares.ShareManager(api=mock_microversion)
self.assertRaises(client_exceptions.UnsupportedVersion,
manager.revert_to_snapshot,
share,
snapshot)
@ddt.data(
("2.6", "os-force_delete"),
("2.7", "force_delete"),

View File

@ -455,6 +455,7 @@ class ShellTest(test_utils.TestCase):
'driver_handles_share_servers': False,
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': False,
},
'share_type_access:is_public': public
}
@ -618,6 +619,19 @@ class ShellTest(test_utils.TestCase):
self.assert_called('POST', '/snapshots/1234/action',
body={'unmanage': None})
def test_revert_to_snapshot(self):
fake_share_snapshot = type(
'FakeShareSnapshot', (object,), {'id': '5678', 'share_id': '1234'})
self.mock_object(
shell_v2, '_find_share_snapshot',
mock.Mock(return_value=fake_share_snapshot))
self.run_command('revert-to-snapshot 5678')
self.assert_called('POST', '/shares/1234/action',
body={'revert': {'snapshot_id': '5678'}})
def test_delete(self):
self.run_command('delete 1234')
self.assert_called('DELETE', '/shares/1234')
@ -823,6 +837,7 @@ class ShellTest(test_utils.TestCase):
"driver_handles_share_servers": expected_bool,
"snapshot_support": True,
"create_share_from_snapshot_support": True,
"revert_to_snapshot_support": False,
}
}
}
@ -870,6 +885,7 @@ class ShellTest(test_utils.TestCase):
"driver_handles_share_servers": False,
"snapshot_support": expected_bool,
"create_share_from_snapshot_support": True,
"revert_to_snapshot_support": False,
"replication_type": replication_type,
}
}
@ -898,6 +914,7 @@ class ShellTest(test_utils.TestCase):
"driver_handles_share_servers": False,
"snapshot_support": True,
"create_share_from_snapshot_support": expected_bool,
"revert_to_snapshot_support": False,
}
}
}
@ -923,6 +940,41 @@ class ShellTest(test_utils.TestCase):
'type-create test false --extra-specs %s=fake' % value,
)
@ddt.unpack
@ddt.data(
*([{'expected_bool': True, 'text': v}
for v in ('true', 'True', '1', 'TRUE', 'tRuE')] +
[{'expected_bool': False, 'text': v}
for v in ('false', 'False', '0', 'FALSE', 'fAlSe')])
)
def test_type_create_with_revert_to_snapshot_support(
self, expected_bool, text):
expected = {
"share_type": {
"name": "test",
"share_type_access:is_public": True,
"extra_specs": {
"driver_handles_share_servers": False,
"snapshot_support": True,
"create_share_from_snapshot_support": True,
"revert_to_snapshot_support": expected_bool,
}
}
}
self.run_command('type-create test false --snapshot-support true '
'--revert-to-snapshot-support ' + text)
self.assert_called('POST', '/types', body=expected)
@ddt.data('fake', 'FFFalse', 'trueee')
def test_type_create_invalid_revert_to_snapshot_support_value(self, value):
self.assertRaises(
exceptions.CommandError,
self.run_command,
'type-create test false --revert-to-snapshot-support ' + value,
)
@ddt.data('--is-public', '--is_public')
def test_update(self, alias):
# basic rename with positional arguments

View File

@ -72,6 +72,47 @@ def get_valid_type_create_data_2_24():
return snapshot_none_combos + snapshot_true_combos + snapshot_false_combos
def get_valid_type_create_data_2_27():
public = [True, False]
dhss = [True, False]
snapshot = [None]
create_from_snapshot = [None]
revert_to_snapshot = [None]
extra_specs = [None, {'replication_type': 'writable', 'foo': 'bar'}]
snapshot_none_combos = list(itertools.product(public, dhss, snapshot,
create_from_snapshot,
revert_to_snapshot,
extra_specs))
public = [True, False]
dhss = [True, False]
snapshot = [True]
create_from_snapshot = [True, False, None]
revert_to_snapshot = [True, False, None]
extra_specs = [None, {'replication_type': 'readable', 'foo': 'bar'}]
snapshot_true_combos = list(itertools.product(public, dhss, snapshot,
create_from_snapshot,
revert_to_snapshot,
extra_specs))
public = [True, False]
dhss = [True, False]
snapshot = [False]
create_from_snapshot = [False, None]
revert_to_snapshot = [False, None]
extra_specs = [None, {'replication_type': 'dr', 'foo': 'bar'}]
snapshot_false_combos = list(itertools.product(public, dhss, snapshot,
create_from_snapshot,
revert_to_snapshot,
extra_specs))
return snapshot_none_combos + snapshot_true_combos + snapshot_false_combos
@ddt.ddt
class TypesTest(utils.TestCase):
@ -146,9 +187,12 @@ class TypesTest(utils.TestCase):
self.assertEqual("fake", result)
def _add_standard_extra_specs_to_dict(self, extra_specs,
create_from_snapshot=None):
create_from_snapshot=None,
revert_to_snapshot=None):
if all(spec is None for spec in [create_from_snapshot]):
# Short-circuit checks to allow for extra specs to be (and remain) None
if all(spec is None for spec in [
create_from_snapshot, revert_to_snapshot]):
return extra_specs
extra_specs = extra_specs or {}
@ -156,6 +200,9 @@ class TypesTest(utils.TestCase):
if create_from_snapshot is not None:
extra_specs['create_share_from_snapshot_support'] = (
create_from_snapshot)
if revert_to_snapshot is not None:
extra_specs['revert_to_snapshot_support'] = (
revert_to_snapshot)
return extra_specs
@ -203,6 +250,57 @@ class TypesTest(utils.TestCase):
"/types", expected_body, "share_type")
self.assertEqual("fake", result)
@ddt.data(*get_valid_type_create_data_2_27())
@ddt.unpack
def test_create_2_27(self, is_public, dhss, snapshot, create_from_snapshot,
revert_to_snapshot, extra_specs):
extra_specs = copy.copy(extra_specs)
extra_specs = self._add_standard_extra_specs_to_dict(
extra_specs, create_from_snapshot=create_from_snapshot,
revert_to_snapshot=revert_to_snapshot)
manager = self._get_share_types_manager("2.27")
self.mock_object(manager, '_create', mock.Mock(return_value="fake"))
result = manager.create(
'test-type-3', spec_driver_handles_share_servers=dhss,
spec_snapshot_support=snapshot,
extra_specs=extra_specs, is_public=is_public)
expected_extra_specs = dict(extra_specs or {})
expected_extra_specs["driver_handles_share_servers"] = dhss
if snapshot is None:
expected_extra_specs.pop("snapshot_support", None)
else:
expected_extra_specs["snapshot_support"] = snapshot
if create_from_snapshot is None:
expected_extra_specs.pop("create_share_from_snapshot_support",
None)
else:
expected_extra_specs["create_share_from_snapshot_support"] = (
create_from_snapshot)
if revert_to_snapshot is None:
expected_extra_specs.pop("revert_to_snapshot_support", None)
else:
expected_extra_specs["revert_to_snapshot_support"] = (
revert_to_snapshot)
expected_body = {
"share_type": {
"name": 'test-type-3',
'share_type_access:is_public': is_public,
"extra_specs": expected_extra_specs,
}
}
manager._create.assert_called_once_with(
"/types", expected_body, "share_type")
self.assertEqual("fake", result)
@ddt.data(
(False, False, True, {'snapshot_support': True,
'replication_type': 'fake_repl_type'}),

View File

@ -109,6 +109,10 @@ class Share(common_base.Resource):
"""List instances of the specified share."""
self.manager.list_instances(self)
def revert_to_snapshot(self, snapshot):
"""Reverts a share (in place) to a snapshot."""
self.manager.revert_to_snapshot(self, snapshot)
class ShareManager(base.ManagerWithFind):
"""Manage :class:`Share` resources."""
@ -276,6 +280,19 @@ class ShareManager(base.ManagerWithFind):
"""
return self._action("unmanage", share)
@api_versions.wraps("2.27")
def revert_to_snapshot(self, share, snapshot):
"""Reverts a share (in place) to a snapshot.
The snapshot must be the most recent one known to manila.
:param share: either share object or text with its ID.
:param snapshot: either snapshot object or text with its ID.
"""
snapshot_id = common_base.getid(snapshot)
info = {'snapshot_id': snapshot_id}
return self._action('revert', share, info=info)
def get(self, share):
"""Get a share.

View File

@ -1034,6 +1034,19 @@ def do_snapshot_unmanage(cs, args):
"specified snapshots.")
@api_versions.wraps("2.27")
@cliutils.arg(
'snapshot',
metavar='<snapshot>',
help='Name or ID of the snapshot to restore. The snapshot must be the '
'most recent one known to manila.')
def do_revert_to_snapshot(cs, args):
"""Revert a share to the specified snapshot."""
snapshot = _find_share_snapshot(cs, args.snapshot)
share = _find_share(cs, snapshot.share_id)
share.revert_to_snapshot(snapshot)
@cliutils.arg(
'share',
metavar='<share>',
@ -3117,6 +3130,13 @@ def do_extra_specs_list(cs, args):
action='single_alias',
help="Boolean extra spec used for filtering of back ends by their "
"capability to create shares from snapshots.")
@cliutils.arg(
'--revert_to_snapshot_support',
'--revert-to-snapshot-support',
metavar='<revert_to_snapshot_support>',
action='single_alias',
help="Boolean extra spec used for filtering of back ends by their "
"capability to revert shares to snapshots. (Default is False).")
@cliutils.arg(
'--extra-specs',
'--extra_specs', # alias
@ -3157,7 +3177,11 @@ def do_type_create(cs, args):
"set via positional argument.")
raise exceptions.CommandError(msg)
boolean_keys = ('snapshot_support', 'create_share_from_snapshot_support')
boolean_keys = (
'snapshot_support',
'create_share_from_snapshot_support',
'revert_to_snapshot_support',
)
for key in boolean_keys:
value = getattr(args, key)

View File

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