Add support of new extra spec 'snapshot_support'

On last midcycle meetup was decided to make snapshots optional feature.
So, add following features:

- Allow to setup 'snapshot_support' extra spec in share type creation
  request. Make it optional key with default 'True' value.
- Display this extra spec with 'type list' command as part of 'optional'
  extra specs.

Change-Id: Id8e11b591a713654f7237cb1b66aeeaebfbf5871
Implements blueprint snapshots-optional
This commit is contained in:
Valeriy Ponomaryov 2015-08-14 18:44:43 +03:00 committed by vponomaryov
parent 80eac1e73c
commit ab49d645be
7 changed files with 164 additions and 22 deletions

View File

@ -144,12 +144,14 @@ class BaseTestCase(base.ClientTestBase):
@classmethod @classmethod
def create_share_type(cls, name=None, driver_handles_share_servers=True, def create_share_type(cls, name=None, driver_handles_share_servers=True,
snapshot_support=True,
is_public=True, client=None, cleanup_in_class=True): is_public=True, client=None, cleanup_in_class=True):
if client is None: if client is None:
client = cls.get_admin_client() client = cls.get_admin_client()
share_type = client.create_share_type( share_type = client.create_share_type(
name=name, name=name,
driver_handles_share_servers=driver_handles_share_servers, driver_handles_share_servers=driver_handles_share_servers,
snapshot_support=snapshot_support,
is_public=is_public) is_public=is_public)
resource = { resource = {
"type": "share_type", "type": "share_type",

View File

@ -129,13 +129,15 @@ class ManilaCLIClient(base.CLIClient):
# Share types # Share types
def create_share_type(self, name=None, driver_handles_share_servers=True, def create_share_type(self, name=None, driver_handles_share_servers=True,
is_public=True): snapshot_support=True, is_public=True):
"""Creates share type. """Creates share type.
:param name: text -- name of share type to use, if not set then :param name: text -- name of share type to use, if not set then
autogenerated will be used autogenerated will be used
:param driver_handles_share_servers: bool/str -- boolean or its :param driver_handles_share_servers: bool/str -- boolean or its
string alias. Default is True. string alias. Default is True.
:param snapshot_support: bool/str -- boolean or its
string alias. Default is True.
:param is_public: bool/str -- boolean or its string alias. Default is :param is_public: bool/str -- boolean or its string alias. Default is
True. True.
""" """
@ -144,10 +146,14 @@ class ManilaCLIClient(base.CLIClient):
dhss = driver_handles_share_servers dhss = driver_handles_share_servers
if not isinstance(dhss, six.string_types): if not isinstance(dhss, six.string_types):
dhss = six.text_type(dhss) dhss = six.text_type(dhss)
if not isinstance(snapshot_support, six.string_types):
snapshot_support = six.text_type(snapshot_support)
if not isinstance(is_public, six.string_types): if not isinstance(is_public, six.string_types):
is_public = six.text_type(is_public) is_public = six.text_type(is_public)
cmd = 'type-create %(name)s %(dhss)s --is-public %(is_public)s' % { cmd = ('type-create %(name)s %(dhss)s --is-public %(is_public)s '
'name': name, 'dhss': dhss, 'is_public': is_public} '--snapshot-support %(snapshot_support)s') % {
'name': name, 'dhss': dhss, 'is_public': is_public,
'snapshot_support': snapshot_support}
share_type_raw = self.manila(cmd) share_type_raw = self.manila(cmd)
# NOTE(vponomaryov): share type creation response is "list"-like with # NOTE(vponomaryov): share type creation response is "list"-like with

View File

@ -69,16 +69,21 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
{'is_public': True, 'dhss': True}, {'is_public': True, 'dhss': True},
{'is_public': False, 'dhss': True}, {'is_public': False, 'dhss': True},
{'is_public': False, 'dhss': False}, {'is_public': False, 'dhss': False},
{'is_public': False, 'dhss': True, 'snapshot_support': False},
{'is_public': False, 'dhss': False, 'snapshot_support': True},
) )
@ddt.unpack @ddt.unpack
def test_create_delete_share_type(self, is_public, dhss): def test_create_delete_share_type(self, is_public, dhss,
snapshot_support=True):
share_type_name = data_utils.rand_name('manilaclient_functional_test') share_type_name = data_utils.rand_name('manilaclient_functional_test')
dhss_expected = 'driver_handles_share_servers : %s' % dhss dhss_expected = 'driver_handles_share_servers : %s' % dhss
snapshot_support_expected = 'snapshot_support : %s' % snapshot_support
# Create share type # Create share type
share_type = self.create_share_type( share_type = self.create_share_type(
name=share_type_name, name=share_type_name,
driver_handles_share_servers=dhss, driver_handles_share_servers=dhss,
snapshot_support=snapshot_support,
is_public=is_public) is_public=is_public)
st_id = share_type['ID'] st_id = share_type['ID']
@ -88,6 +93,8 @@ class ShareTypesReadWriteTest(base.BaseTestCase):
self.assertIn(key, share_type) self.assertIn(key, share_type)
self.assertEqual(share_type_name, share_type['Name']) self.assertEqual(share_type_name, share_type['Name'])
self.assertEqual(dhss_expected, share_type['required_extra_specs']) self.assertEqual(dhss_expected, share_type['required_extra_specs'])
self.assertEqual(
snapshot_support_expected, share_type['optional_extra_specs'])
self.assertEqual('public' if is_public else 'private', self.assertEqual('public' if is_public else 'private',
share_type['Visibility'].lower()) share_type['Visibility'].lower())
self.assertEqual('-', share_type['is_default']) self.assertEqual('-', share_type['is_default'])

View File

@ -272,7 +272,8 @@ class ShellTest(test_utils.TestCase):
self.assert_called('GET', '/types') self.assert_called('GET', '/types')
cliutils.print_list.assert_called_once_with( cliutils.print_list.assert_called_once_with(
mock.ANY, mock.ANY,
['ID', 'Name', 'Visibility', 'is_default', 'required_extra_specs'], ['ID', 'Name', 'Visibility', 'is_default', 'required_extra_specs',
'optional_extra_specs'],
mock.ANY) mock.ANY)
def test_type_list_default_volume_type(self): def test_type_list_default_volume_type(self):
@ -288,7 +289,10 @@ class ShellTest(test_utils.TestCase):
expected = { expected = {
'share_type': { 'share_type': {
'name': 'test-type-3', 'name': 'test-type-3',
'extra_specs': {'driver_handles_share_servers': False}, 'extra_specs': {
'driver_handles_share_servers': False,
'snapshot_support': True,
},
'os-share-type-access:is_public': public 'os-share-type-access:is_public': public
} }
} }
@ -498,11 +502,11 @@ class ShellTest(test_utils.TestCase):
mock.ANY, ['ID', 'Name', 'all_extra_specs'], mock.ANY) mock.ANY, ['ID', 'Name', 'all_extra_specs'], mock.ANY)
@ddt.data('fake', 'FFFalse', 'trueee') @ddt.data('fake', 'FFFalse', 'trueee')
def test_type_create_invalid_extra_spec(self, extra_spec): def test_type_create_invalid_dhss_value(self, value):
self.assertRaises( self.assertRaises(
exceptions.CommandError, exceptions.CommandError,
self.run_command, self.run_command,
'type-create test ' + extra_spec, 'type-create test ' + value,
) )
@ddt.unpack @ddt.unpack
@ -516,7 +520,8 @@ class ShellTest(test_utils.TestCase):
"name": "test", "name": "test",
"os-share-type-access:is_public": True, "os-share-type-access:is_public": True,
"extra_specs": { "extra_specs": {
"driver_handles_share_servers": expected_bool "driver_handles_share_servers": expected_bool,
"snapshot_support": True,
} }
} }
} }
@ -525,6 +530,37 @@ class ShellTest(test_utils.TestCase):
self.assert_called('POST', '/types', body=expected) self.assert_called('POST', '/types', body=expected)
@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_create_with_snapshot_support(self, expected_bool, text):
expected = {
"share_type": {
"name": "test",
"os-share-type-access:is_public": True,
"extra_specs": {
"driver_handles_share_servers": False,
"snapshot_support": expected_bool,
}
}
}
self.run_command('type-create test false --snapshot-support ' + text)
self.assert_called('POST', '/types', body=expected)
@ddt.data('fake', 'FFFalse', 'trueee')
def test_type_create_invalid_snapshot_support_value(self, value):
self.assertRaises(
exceptions.CommandError,
self.run_command,
'type-create test false --snapshot-support ' + value,
)
@ddt.data('--is-public', '--is_public') @ddt.data('--is-public', '--is_public')
def test_update(self, alias): def test_update(self, alias):
# basic rename with positional agruments # basic rename with positional agruments

View File

@ -23,33 +23,82 @@ cs = fakes.FakeClient()
@ddt.ddt @ddt.ddt
class TypesTest(utils.TestCase): class TypesTest(utils.TestCase):
@ddt.data(
{'snapshot_support': 'False'},
{'snapshot_support': 'False', 'foo': 'bar'},
)
def test_init(self, extra_specs):
info = {'extra_specs': {'snapshot_support': 'False'}}
share_type = share_types.ShareType(share_types.ShareTypeManager, info)
self.assertTrue(hasattr(share_type, '_required_extra_specs'))
self.assertTrue(hasattr(share_type, '_optional_extra_specs'))
self.assertTrue(isinstance(share_type._required_extra_specs, dict))
self.assertTrue(isinstance(share_type._optional_extra_specs, dict))
self.assertEqual(
{'snapshot_support': 'False'}, share_type.get_optional_keys())
def test_list_types(self): def test_list_types(self):
tl = cs.share_types.list() tl = cs.share_types.list()
cs.assert_called('GET', '/types?is_public=all') cs.assert_called('GET', '/types?is_public=all')
for t in tl: for t in tl:
self.assertIsInstance(t, share_types.ShareType) self.assertIsInstance(t, share_types.ShareType)
self.assertTrue(callable(getattr(t, 'get_required_keys', ''))) self.assertTrue(callable(getattr(t, 'get_required_keys', '')))
self.assertTrue(callable(getattr(t, 'get_optional_keys', '')))
self.assertEqual({'test': 'test'}, t.get_required_keys()) self.assertEqual({'test': 'test'}, t.get_required_keys())
self.assertEqual(
{'snapshot_support': 'unknown'}, t.get_optional_keys())
def test_list_types_only_public(self): def test_list_types_only_public(self):
cs.share_types.list(show_all=False) cs.share_types.list(show_all=False)
cs.assert_called('GET', '/types') cs.assert_called('GET', '/types')
@ddt.data(True, False) @ddt.data(
def test_create(self, is_public): (True, True, True),
(True, True, False),
(True, False, True),
(False, True, True),
(True, False, False),
(False, False, True),
(False, True, False),
(False, False, False),
)
@ddt.unpack
def test_create(self, is_public, dhss, snapshot_support):
expected_body = { expected_body = {
"share_type": { "share_type": {
"name": 'test-type-3', "name": 'test-type-3',
'os-share-type-access:is_public': is_public, 'os-share-type-access:is_public': is_public,
"extra_specs": { "extra_specs": {
"driver_handles_share_servers": True "driver_handles_share_servers": dhss,
"snapshot_support": snapshot_support,
} }
} }
} }
t = cs.share_types.create('test-type-3', True, is_public=is_public) result = cs.share_types.create(
'test-type-3', dhss, snapshot_support, is_public=is_public)
cs.assert_called('POST', '/types', expected_body) cs.assert_called('POST', '/types', expected_body)
self.assertIsInstance(t, share_types.ShareType) self.assertIsInstance(result, share_types.ShareType)
@ddt.data(True, False)
def test_create_with_default_values(self, dhss):
expected_body = {
"share_type": {
"name": 'test-type-3',
'os-share-type-access:is_public': True,
"extra_specs": {
"driver_handles_share_servers": dhss,
"snapshot_support": True,
}
}
}
result = cs.share_types.create('test-type-3', dhss)
cs.assert_called('POST', '/types', expected_body)
self.assertIsInstance(result, share_types.ShareType)
def test_set_key(self): def test_set_key(self):
t = cs.share_types.get(1) t = cs.share_types.get(1)

View File

@ -28,6 +28,10 @@ class ShareType(common_base.Resource):
def __init__(self, manager, info, loaded=False): def __init__(self, manager, info, loaded=False):
super(ShareType, self).__init__(manager, info, loaded) super(ShareType, self).__init__(manager, info, loaded)
self._required_extra_specs = info.get('required_extra_specs', {}) self._required_extra_specs = info.get('required_extra_specs', {})
self._optional_extra_specs = {
'snapshot_support': info.get('extra_specs', {}).get(
'snapshot_support', 'unknown'),
}
def __repr__(self): def __repr__(self):
return "<ShareType: %s>" % self.name return "<ShareType: %s>" % self.name
@ -59,6 +63,9 @@ class ShareType(common_base.Resource):
def get_required_keys(self): def get_required_keys(self):
return self._required_extra_specs return self._required_extra_specs
def get_optional_keys(self):
return self._optional_extra_specs
def set_keys(self, metadata): def set_keys(self, metadata):
"""Set extra specs on a share type. """Set extra specs on a share type.
@ -123,7 +130,8 @@ class ShareTypeManager(base.ManagerWithFind):
""" """
self._delete("/types/%s" % common_base.getid(share_type)) self._delete("/types/%s" % common_base.getid(share_type))
def create(self, name, spec_driver_handles_share_servers, is_public=True): def create(self, name, spec_driver_handles_share_servers,
spec_snapshot_support=True, is_public=True):
"""Create a share type. """Create a share type.
:param name: Descriptive name of the share type :param name: Descriptive name of the share type
@ -136,7 +144,8 @@ class ShareTypeManager(base.ManagerWithFind):
"os-share-type-access:is_public": is_public, "os-share-type-access:is_public": is_public,
"extra_specs": { "extra_specs": {
"driver_handles_share_servers": "driver_handles_share_servers":
spec_driver_handles_share_servers spec_driver_handles_share_servers,
"snapshot_support": spec_snapshot_support,
}, },
} }
} }

View File

@ -1769,6 +1769,13 @@ def _print_type_required_extra_specs(share_type):
return "N/A" return "N/A"
def _print_type_optional_extra_specs(share_type):
try:
return _print_dict(share_type.get_optional_keys())
except exceptions.NotFound:
return "N/A"
def _print_share_type_list(stypes, default_share_type=None): def _print_share_type_list(stypes, default_share_type=None):
def _is_default(share_type): def _is_default(share_type):
@ -1784,13 +1791,21 @@ def _print_share_type_list(stypes, default_share_type=None):
'Visibility': is_public, 'Visibility': is_public,
'is_default': _is_default, 'is_default': _is_default,
'required_extra_specs': _print_type_required_extra_specs, 'required_extra_specs': _print_type_required_extra_specs,
'optional_extra_specs': _print_type_optional_extra_specs,
} }
for stype in stypes: for stype in stypes:
stype = stype.to_dict() stype = stype.to_dict()
stype['Visibility'] = stype.pop('is_public', 'unknown') stype['Visibility'] = stype.pop('is_public', 'unknown')
fields = ['ID', 'Name', 'Visibility', 'is_default', 'required_extra_specs'] fields = [
'ID',
'Name',
'Visibility',
'is_default',
'required_extra_specs',
'optional_extra_specs',
]
cliutils.print_list(stypes, fields, formatters) cliutils.print_list(stypes, fields, formatters)
@ -1842,6 +1857,13 @@ def do_extra_specs_list(cs, args):
type=str, type=str,
help="Required extra specification. " help="Required extra specification. "
"Valid values are 'true'/'1' and 'false'/'0'") "Valid values are 'true'/'1' and 'false'/'0'")
@cliutils.arg(
'--snapshot_support',
'--snapshot-support',
metavar='<snapshot_support>',
action='single_alias',
help="Boolean extra spec that used for filtering of back ends by their "
"capability to create share snapshots. (Default is True).")
@cliutils.arg( @cliutils.arg(
'--is_public', '--is_public',
'--is-public', '--is-public',
@ -1851,17 +1873,28 @@ def do_extra_specs_list(cs, args):
@cliutils.service_type('share') @cliutils.service_type('share')
def do_type_create(cs, args): def do_type_create(cs, args):
"""Create a new share type.""" """Create a new share type."""
kwargs = {
"name": args.name,
"is_public": strutils.bool_from_string(args.is_public, default=True),
}
try: try:
extra_spec = strutils.bool_from_string( kwargs['spec_driver_handles_share_servers'] = (
args.spec_driver_handles_share_servers, strict=True) strutils.bool_from_string(
args.spec_driver_handles_share_servers, strict=True))
except ValueError as e: except ValueError as e:
msg = ("Argument spec_driver_handles_share_servers " msg = ("Argument spec_driver_handles_share_servers "
"argument is not valid: %s" % six.text_type(e)) "argument is not valid: %s" % six.text_type(e))
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
try:
if args.snapshot_support:
kwargs['spec_snapshot_support'] = strutils.bool_from_string(
args.snapshot_support, strict=True)
except ValueError as e:
msg = ("Argument 'snapshot_support' is of boolean type and has "
"invalid value: %s" % six.text_type(e))
raise exceptions.CommandError(msg)
is_public = strutils.bool_from_string(args.is_public, default=True) stype = cs.share_types.create(**kwargs)
stype = cs.share_types.create(args.name, extra_spec, is_public=is_public)
_print_share_type_list([stype]) _print_share_type_list([stype])