More Manila cDOT qualified specs

Manila has "share types", analogous to Cinder "volume types", that
may contain qualified extra-specs - extra-specs that have significance
only for the backend driver and which are not reported back to the
scheduler.

This commit adds the following boolean qualified extra-specs:
  * netapp:dedup
  * netapp:compression
alongside preexisting support for the netapp:thin_provisioned boolean
qualified extra spec.

Implements blueprint more-cdot-qualified-specs

Change-Id: Ief1da9019165f60bf191b80c069a1e9bd7c34168
This commit is contained in:
Tom Barron 2015-02-09 19:07:01 -08:00
parent e1fe73d2d9
commit e2d6ca2d99
5 changed files with 132 additions and 34 deletions

View File

@ -760,7 +760,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
@na_utils.trace
def create_volume(self, aggregate_name, volume_name, size_gb,
thin_provisioned=False, snapshot_policy=None,
language=None, max_files=None):
language=None, dedup_enabled=False,
compression_enabled=False, max_files=None):
"""Creates a volume."""
api_args = {
'containing-aggr-name': aggregate_name,
@ -776,9 +778,29 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
api_args['language-code'] = language
self.send_request('volume-create', api_args)
# cDOT compression requires that deduplication be enabled.
if dedup_enabled or compression_enabled:
self.enable_dedup(volume_name)
if compression_enabled:
self.enable_compression(volume_name)
if max_files is not None:
self.set_volume_max_files(volume_name, max_files)
@na_utils.trace
def enable_dedup(self, volume_name):
"""Enable deduplication on volume."""
api_args = {'path': '/vol/%s' % volume_name}
self.send_request('sis-enable', api_args)
@na_utils.trace
def enable_compression(self, volume_name):
"""Enable compression on volume."""
api_args = {
'path': '/vol/%s' % volume_name,
'enable-compression': 'true'
}
self.send_request('sis-set-config', api_args)
@na_utils.trace
def set_volume_max_files(self, volume_name, max_files):
"""Set flexvol file limit."""

View File

@ -51,7 +51,9 @@ class NetAppCmodeFileStorageLibrary(object):
# client library argument keywords. When we expose more backend
# capabilities here, we will add them to this map.
BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP = {
'netapp:thin_provisioned': 'thin_provisioned'
'netapp:thin_provisioned': 'thin_provisioned',
'netapp:dedup': 'dedup_enabled',
'netapp:compression': 'compression_enabled',
}
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
'netapp:snapshot_policy': 'snapshot_policy',
@ -341,9 +343,6 @@ class NetAppCmodeFileStorageLibrary(object):
'provisioning options %(options)s',
{'share': share_name, 'pool': pool_name,
'options': provisioning_options})
LOG.debug('Creating share %(share)s on pool %(pool)s',
{'share': share_name, 'pool': pool_name})
vserver_client.create_volume(pool_name, share_name,
share['size'],
**provisioning_options)
@ -375,6 +374,21 @@ class NetAppCmodeFileStorageLibrary(object):
@na_utils.trace
def _check_boolean_extra_specs_validity(self, share, specs,
keys_of_interest):
# cDOT compression requires deduplication.
dedup = specs.get('netapp:dedup', None)
compression = specs.get('netapp:compression', None)
if dedup is not None and compression is not None:
if dedup.lower() == 'false' and compression.lower() == 'true':
spec = {'netapp:dedup': dedup,
'netapp:compression': compression}
type_id = share['share_type_id']
share_id = share['id']
args = {'type_id': type_id, 'share_id': share_id, 'spec': spec}
msg = _('Invalid combination of extra_specs in share_type '
'%(type_id)s for share %(share_id)s: %(spec)s: '
'deduplication must be enabled in order for '
'compression to be enabled.')
raise exception.Invalid(msg % args)
"""Check if the boolean_extra_specs have valid values."""
# Extra spec values must be (ignoring case) 'true' or 'false'.
for key in keys_of_interest:

View File

@ -1383,12 +1383,15 @@ class NetAppClientCmodeTestCase(test.TestCase):
def test_create_volume_with_extra_specs(self):
self.mock_object(self.client, 'set_volume_max_files')
self.mock_object(self.client, 'enable_dedup')
self.mock_object(self.client, 'enable_compression')
self.mock_object(self.client, 'send_request')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100,
thin_provisioned=True, language='en-US',
snapshot_policy='default', max_files=5000)
snapshot_policy='default', dedup_enabled=True,
compression_enabled=True, max_files=5000)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
@ -1404,6 +1407,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
volume_create_args)
self.client.set_volume_max_files.assert_called_once_with(
fake.SHARE_NAME, fake.MAX_FILES)
self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME)
self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME)
def test_set_volume_max_files(self):
@ -1431,6 +1436,31 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with(
'volume-modify-iter', volume_modify_iter_api_args)
def test_enable_dedup(self):
self.mock_object(self.client, 'send_request')
self.client.enable_dedup(fake.SHARE_NAME)
sis_enable_args = {'path': '/vol/%s' % fake.SHARE_NAME}
self.client.send_request.assert_called_once_with('sis-enable',
sis_enable_args)
def test_enable_compression(self):
self.mock_object(self.client, 'send_request')
self.client.enable_compression(fake.SHARE_NAME)
sis_set_config_args = {
'path': '/vol/%s' % fake.SHARE_NAME,
'enable-compression': 'true'
}
self.client.send_request.assert_called_once_with('sis-set-config',
sis_set_config_args)
def test_volume_exists(self):
api_response = netapp_api.NaElement(fake.VOLUME_GET_NAME_RESPONSE)

View File

@ -538,6 +538,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
return_value=fake.POOL_NAME))
self.mock_object(share_types, 'get_extra_specs_from_share',
mock.Mock(return_value=fake.EXTRA_SPEC))
self.mock_object(self.library, '_check_boolean_extra_specs_validity')
self.mock_object(self.library, '_get_boolean_provisioning_options',
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
@ -549,7 +550,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client.create_volume.assert_called_once_with(
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
thin_provisioned=True, snapshot_policy='default',
language='en-US', max_files=5000)
language='en-US', dedup_enabled=True,
compression_enabled=False, max_files=5000)
def test_allocate_container_no_pool_name(self):
self.mock_object(self.library, '_get_valid_share_name', mock.Mock(
@ -573,42 +575,50 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual(0, self.library._get_provisioning_options.call_count)
def test_check_extra_specs_validity(self):
boolean_extra_spec_keys = list(
self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP)
mock_bool_check = self.mock_object(
self.library, '_check_boolean_extra_specs_validity')
mock_string_check = self.mock_object(
self.library, '_check_string_extra_specs_validity')
self.library._check_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC)
mock_bool_check.assert_called_once_with(
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC, boolean_extra_spec_keys)
mock_string_check.assert_called_once_with(
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC)
def test_check_extra_specs_validity_empty_spec(self):
self.library._check_extra_specs_validity(
result = self.library._check_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EMPTY_EXTRA_SPEC)
self.assertIsNone(result)
def test_check_extra_specs_validity_invalid_value(self):
self.assertRaises(
exception.Invalid, self.library._check_extra_specs_validity,
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC)
def test_check_string_extra_specs_validity(self):
self.library._check_string_extra_specs_validity(
result = self.library._check_string_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC)
self.assertIsNone(result)
def test_check_string_extra_specs_validity_empty_spec(self):
self.library._check_string_extra_specs_validity(
result = self.library._check_string_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EMPTY_EXTRA_SPEC)
self.assertIsNone(result)
def test_check_string_extra_specs_validity_invalid_value(self):
self.assertRaises(
exception.NetAppException,
self.library._check_string_extra_specs_validity,
fake.EXTRA_SPEC_SHARE, fake.INVALID_MAX_FILE_EXTRA_SPEC)
def test_check_boolean_extra_specs_validity(self):
self.library._check_boolean_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC,
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
def test_check_boolean_extra_specs_validity_empty_spec(self):
self.library._check_boolean_extra_specs_validity(
fake.EXTRA_SPEC_SHARE, fake.EMPTY_EXTRA_SPEC,
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
def test_check_boolean_extra_specs_validity_invalid_value(self):
self.assertRaises(
exception.Invalid,
@ -616,6 +626,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC,
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
def test_check_extra_specs_validity_invalid_combination(self):
self.assertRaises(
exception.Invalid,
self.library._check_boolean_extra_specs_validity,
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC_COMBO,
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
def test_get_provisioning_options(self):
result = self.library._get_provisioning_options(fake.EXTRA_SPEC)
@ -632,15 +649,20 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
result = self.library._get_provisioning_options(
fake.EMPTY_EXTRA_SPEC)
expected = {'language': None, 'max_files': None,
expected = {
'language': None,
'max_files': None,
'snapshot_policy': None,
'thin_provisioned': False}
'thin_provisioned': False,
'compression_enabled': False,
'dedup_enabled': False,
}
self.assertEqual(expected, result)
def test_get_boolean_provisioning_options(self):
result = self.library._get_boolean_provisioning_options(
fake.BOOLEAN_EXTRA_SPEC,
fake.SHORT_BOOLEAN_EXTRA_SPEC,
self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP)
self.assertEqual(fake.PROVISIONING_OPTIONS_BOOLEAN, result)
@ -653,11 +675,17 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual(fake.PROVISIONING_OPTIONS_BOOLEAN, result)
def test_get_boolean_provisioning_options_implicit_false(self):
expected = {
'thin_provisioned': False,
'dedup_enabled': False,
'compression_enabled': False,
}
result = self.library._get_boolean_provisioning_options(
fake.EMPTY_EXTRA_SPEC,
self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP)
self.assertEqual({'thin_provisioned': False}, result)
self.assertEqual(expected, result)
def test_get_string_provisioning_options(self):
result = self.library._get_string_provisioning_options(

View File

@ -80,6 +80,8 @@ EXTRA_SPEC = {
'netapp:thin_provisioned': 'true',
'netapp:snapshot_policy': 'default',
'netapp:language': 'en-US',
'netapp:dedup': 'True',
'netapp:compression': 'false',
'netapp:max_files': 5000,
}
@ -87,17 +89,23 @@ PROVISIONING_OPTIONS = {
'thin_provisioned': True,
'snapshot_policy': 'default',
'language': 'en-US',
'dedup_enabled': True,
'compression_enabled': False,
'max_files': 5000,
}
PROVISIONING_OPTIONS_BOOLEAN = {
'thin_provisioned': True,
'dedup_enabled': False,
'compression_enabled': False,
}
PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {
'thin_provisioned': True,
'snapshot_policy': None,
'language': None,
'dedup_enabled': False,
'compression_enabled': False,
'max_files': None,
}
@ -140,19 +148,15 @@ INVALID_EXTRA_SPEC = {
'netapp:language': 'abc',
}
INVALID_EXTRA_SPEC_COMBO = {
'netapp:dedup': 'false',
'netapp:compression': 'true'
}
INVALID_MAX_FILE_EXTRA_SPEC = {
'netapp:max_files': -1,
}
BOOLEAN_EXTRA_SPEC = {
'netapp:thin_provisioned': 'true',
}
BOOLEAN_SHORT_EXTRA_SPEC = {
'netapp:thin_provisioned': 'true',
}
EMPTY_EXTRA_SPEC = {}
SHARE_TYPE = {