NetApp cDOT: Add NVE support in Manila

NVE is a software-based technology for encrypting
data at rest one volume at a time. An encryption
key, accessible only to the storage system,
ensures that volume data cannot be read if the
underlying device is repurposed, returned,
misplaced or stolen.

Signed-off-by: Erlon R. Cruz <erlon@netapp.com>
Signed-off-by: Tiago Pasqualini <tiagod@netapp.com>

Change-Id: Ib622c3d64cbec5a7254f6074f07d6f56f6492ca4
Implements: blueprint netapp-encrypted-shares
This commit is contained in:
Jose Porrua 2017-03-02 16:12:22 -05:00 committed by tpsilva
parent c4b59336c2
commit 48e4e65b02
11 changed files with 679 additions and 39 deletions

View File

@ -158,12 +158,23 @@ class NaServer(object):
raise ValueError('Major and minor versions must be integers')
self._refresh_conn = True
def set_system_version(self, system_version):
"""Set the ONTAP system version."""
self._system_version = system_version
self._refresh_conn = True
def get_api_version(self):
"""Gets the API version tuple."""
if hasattr(self, '_api_version'):
return (self._api_major_version, self._api_minor_version)
return None
def get_system_version(self):
"""Gets the ONTAP system version."""
if hasattr(self, '_system_version'):
return self._system_version
return None
def set_port(self, port):
"""Set the server communication port."""
try:

View File

@ -49,9 +49,12 @@ class NetAppBaseClient(object):
return major, minor
@na_utils.trace
def get_system_version(self):
def get_system_version(self, cached=True):
"""Gets the current Data ONTAP version."""
if cached:
return self.connection.get_system_version()
result = self.send_request('system-get-version')
version_tuple = result.get_child_by_name(
@ -62,9 +65,9 @@ class NetAppBaseClient(object):
version = {}
version['version'] = result.get_child_content('version')
version['version-tuple'] = (
system_version_tuple.get_child_content('generation'),
system_version_tuple.get_child_content('major'),
system_version_tuple.get_child_content('minor'))
int(system_version_tuple.get_child_content('generation')),
int(system_version_tuple.get_child_content('major')),
int(system_version_tuple.get_child_content('minor')))
return version

View File

@ -1,6 +1,7 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2015 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2018 Jose Porrua. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -55,6 +56,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
self.connection.set_api_version(1, 15)
(major, minor) = self.get_ontapi_version(cached=False)
self.connection.set_api_version(major, minor)
system_version = self.get_system_version(cached=False)
self.connection.set_system_version(system_version)
self._init_features()
@ -66,6 +69,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
ontapi_1_20 = ontapi_version >= (1, 20)
ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
ontapi_1_30 = ontapi_version >= (1, 30)
ontapi_1_110 = ontapi_version >= (1, 110)
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
@ -77,6 +81,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
self.features.add_feature('ADVANCED_DISK_PARTITIONING',
supported=ontapi_1_30)
self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_110)
def _invoke_vserver_api(self, na_element, vserver):
server = copy.copy(self.connection)
@ -372,6 +377,31 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
else:
vserver_client.send_request('cifs-server-delete')
@na_utils.trace
def is_nve_supported(self):
"""Determine whether NVE is supported on this platform and version."""
nodes = self.list_cluster_nodes()
system_version = self.get_system_version()
version = system_version.get('version')
version_tuple = system_version.get('version-tuple')
# NVE requires an ONTAP version >= 9.1. Also, not all platforms
# support this feature. NVE is not supported if the version
# includes the substring '<1no-DARE>' (no Data At Rest Encryption).
if version_tuple >= (9, 1, 0) and "<1no-DARE>" not in version:
if nodes is not None:
return self.get_security_key_manager_nve_support(nodes[0])
else:
LOG.debug('Cluster credentials are required in order to '
'determine whether NetApp Volume Encryption is '
'supported or not on this platform.')
return False
else:
LOG.debug('NetApp Volume Encryption is not supported on this '
'ONTAP version: %(version)s, %(version_tuple)s. ',
{'version': version, 'version_tuple': version_tuple})
return False
@na_utils.trace
def list_cluster_nodes(self):
"""Get all available cluster nodes."""
@ -388,6 +418,25 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
return [node_info.get_child_content('node') for node_info
in nodes_info_list.get_children()]
@na_utils.trace
def get_security_key_manager_nve_support(self, node):
"""Determine whether the cluster platform supports Volume Encryption"""
api_args = {'node': node}
try:
result = self.send_request(
'security-key-manager-volume-encryption-supported', api_args)
vol_encryption_supported = result.get_child_content(
'vol-encryption-supported') or 'false'
except netapp_api.NaApiError as e:
if (e.code == netapp_api.EAPIERROR and
"key manager is not enabled" in e.message):
LOG.debug("%s", e.message)
return False
else:
raise
return strutils.bool_from_string(vol_encryption_supported)
@na_utils.trace
def list_node_data_ports(self, node):
ports = self.get_node_data_ports(node)
@ -1430,8 +1479,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
language=None, dedup_enabled=False,
compression_enabled=False, max_files=None,
snapshot_reserve=None, volume_type='rw',
qos_policy_group=None, **options):
qos_policy_group=None,
encrypt=False, **options):
"""Creates a volume."""
api_args = {
'containing-aggr-name': aggregate_name,
@ -1452,6 +1501,14 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
snapshot_reserve)
if qos_policy_group is not None:
api_args['qos-policy-group-name'] = qos_policy_group
if encrypt is True:
if not self.features.FLEXVOL_ENCRYPTION:
msg = 'Flexvol encryption is not supported on this backend.'
raise exception.NetAppException(msg)
else:
api_args['encrypt'] = 'true'
self.send_request('volume-create', api_args)
# cDOT compression requires that deduplication be enabled.
@ -1705,6 +1762,41 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
result = self.send_iter_request('volume-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def is_flexvol_encrypted(self, volume_name, vserver_name):
"""Checks whether the volume is encrypted or not."""
if not self.features.FLEXVOL_ENCRYPTION:
return False
api_args = {
'query': {
'volume-attributes': {
'encrypt': 'true',
'volume-id-attributes': {
'name': volume_name,
'owning-vserver-name': vserver_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'encrypt': None,
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
if self._has_records(result):
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'volume-attributes') or netapp_api.NaElement('none')
encrypt = volume_attributes.get_child_content('encrypt')
if encrypt:
return True
return False
@na_utils.trace
def get_aggregate_for_volume(self, volume_name):
"""Get the name of the aggregate containing a volume."""
@ -3419,29 +3511,37 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
@na_utils.trace
def start_volume_move(self, volume_name, vserver, destination_aggregate,
cutover_action='wait'):
cutover_action='wait', encrypt_destination=None):
"""Moves a FlexVol across Vserver aggregates.
Requires cluster-scoped credentials.
"""
self._send_volume_move_request(
volume_name, vserver, destination_aggregate,
cutover_action=cutover_action)
volume_name, vserver,
destination_aggregate,
cutover_action=cutover_action,
encrypt_destination=encrypt_destination)
@na_utils.trace
def check_volume_move(self, volume_name, vserver, destination_aggregate):
def check_volume_move(self, volume_name, vserver, destination_aggregate,
encrypt_destination=None):
"""Moves a FlexVol across Vserver aggregates.
Requires cluster-scoped credentials.
"""
self._send_volume_move_request(
volume_name, vserver, destination_aggregate, validation_only=True)
volume_name,
vserver,
destination_aggregate,
validation_only=True,
encrypt_destination=encrypt_destination)
@na_utils.trace
def _send_volume_move_request(self, volume_name, vserver,
destination_aggregate,
cutover_action='wait',
validation_only=False):
validation_only=False,
encrypt_destination=None):
"""Send request to check if vol move is possible, or start it.
:param volume_name: Name of the FlexVol to be moved.
@ -3454,6 +3554,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
intervention in case of errors.
:param validation_only: If set to True, only validates if the volume
move is possible, does not trigger data copy.
:param encrypt_destination: If set to True, it encrypts the Flexvol
after the volume move is complete.
"""
api_args = {
'source-volume': volume_name,
@ -3461,6 +3563,15 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'dest-aggr': destination_aggregate,
'cutover-action': CUTOVER_ACTION_MAP[cutover_action],
}
if self.features.FLEXVOL_ENCRYPTION and encrypt_destination:
api_args['encrypt-destination'] = 'true'
elif encrypt_destination:
msg = 'Flexvol encryption is not supported on this backend.'
raise exception.NetAppException(msg)
else:
api_args['encrypt-destination'] = 'false'
if validation_only:
api_args['perform-validation-only'] = 'true'
self.send_request('volume-move-start', api_args)

View File

@ -71,11 +71,13 @@ class NetAppCmodeFileStorageLibrary(object):
'netapp:compression': 'compression_enabled',
'netapp:split_clone_on_create': 'split',
}
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
'netapp:snapshot_policy': 'snapshot_policy',
'netapp:language': 'language',
'netapp:max_files': 'max_files',
}
# Maps standard extra spec keys to legacy NetApp keys
STANDARD_BOOLEAN_EXTRA_SPECS_MAP = {
'thin_provisioning': 'netapp:thin_provisioned',
@ -114,6 +116,7 @@ class NetAppCmodeFileStorageLibrary(object):
self._clients = {}
self._ssc_stats = {}
self._have_cluster_creds = None
self._cluster_info = {}
self._app_version = kwargs.get('app_version', 'unknown')
@ -126,10 +129,18 @@ class NetAppCmodeFileStorageLibrary(object):
def do_setup(self, context):
self._client = self._get_api_client()
self._have_cluster_creds = self._client.check_for_cluster_credentials()
if self._have_cluster_creds is True:
self._set_cluster_info()
# Performance monitoring library
self._perf_library = performance.PerformanceLibrary(self._client)
@na_utils.trace
def _set_cluster_info(self):
self._cluster_info['nve_support'] = (
self._client.is_nve_supported()
and self._client.features.FLEXVOL_ENCRYPTION)
@na_utils.trace
def check_for_setup_error(self):
self._licenses = self._get_licenses()
@ -140,6 +151,7 @@ class NetAppCmodeFileStorageLibrary(object):
@na_utils.trace
def _get_api_client(self, vserver=None):
# Use cached value to prevent calls to system-get-ontapi-version.
client = self._clients.get(vserver)
@ -300,6 +312,9 @@ class NetAppCmodeFileStorageLibrary(object):
else:
qos_support = False
netapp_flexvol_encryption = self._cluster_info.get(
'nve_support', False)
for aggr_name in sorted(aggregates):
reserved_percentage = self.configuration.reserved_share_percentage
@ -325,6 +340,7 @@ class NetAppCmodeFileStorageLibrary(object):
'reserved_percentage': reserved_percentage,
'dedupe': [True, False],
'compression': [True, False],
'netapp_flexvol_encryption': netapp_flexvol_encryption,
'thin_provisioning': [True, False],
'snapshot_support': True,
'create_share_from_snapshot_support': True,
@ -685,8 +701,19 @@ class NetAppCmodeFileStorageLibrary(object):
specs, self.STRING_QUALIFIED_EXTRA_SPECS_MAP)
result = boolean_args.copy()
result.update(string_args)
result['encrypt'] = self._get_nve_option(specs)
return result
def _get_nve_option(self, specs):
if 'netapp_flexvol_encryption' in specs:
nve = specs['netapp_flexvol_encryption'].lower() == 'true'
else:
nve = False
return nve
@na_utils.trace
def _check_aggregate_extra_specs_validity(self, aggregate_name, specs):
@ -981,9 +1008,6 @@ class NetAppCmodeFileStorageLibrary(object):
# When calculating the size, round up to the next GB.
volume_size = int(math.ceil(float(volume['size']) / units.Gi))
# Ensure volume is manageable
self._validate_volume_for_manage(volume, vserver_client)
# Validate extra specs
extra_specs = share_types.get_extra_specs_from_share(share)
try:
@ -993,6 +1017,10 @@ class NetAppCmodeFileStorageLibrary(object):
except exception.ManilaException as ex:
raise exception.ManageExistingShareTypeMismatch(
reason=six.text_type(ex))
# Ensure volume is manageable
self._validate_volume_for_manage(volume, vserver_client)
provisioning_options = self._get_provisioning_options(extra_specs)
debug_args = {
@ -1923,8 +1951,11 @@ class NetAppCmodeFileStorageLibrary(object):
self._check_destination_vserver_for_vol_move(
source_share, source_vserver, destination_share_server)
encrypt_dest = self._get_dest_flexvol_encryption_value(
destination_share)
self._client.check_volume_move(
share_volume, source_vserver, destination_aggregate)
share_volume, source_vserver, destination_aggregate,
encrypt_destination=encrypt_dest)
except Exception:
msg = ("Cannot migrate share %(shr)s efficiently between "
@ -1963,8 +1994,18 @@ class NetAppCmodeFileStorageLibrary(object):
destination_aggregate = share_utils.extract_host(
destination_share['host'], level='pool')
# If the destination's share type extra-spec for Flexvol encryption
# is different than the source's, then specify the volume-move
# operation to set the correct 'encrypt' attribute on the destination
# volume.
encrypt_dest = self._get_dest_flexvol_encryption_value(
destination_share)
self._client.start_volume_move(
share_volume, vserver, destination_aggregate)
share_volume,
vserver,
destination_aggregate,
encrypt_destination=encrypt_dest)
msg = ("Began volume move operation of share %(shr)s from %(src)s "
"to %(dest)s.")
@ -1981,6 +2022,15 @@ class NetAppCmodeFileStorageLibrary(object):
status = self._client.get_volume_move_status(share_volume, vserver)
return status
def _get_dest_flexvol_encryption_value(self, destination_share):
dest_share_type_encrypted_val = share_types.get_share_type_extra_specs(
destination_share['share_type_id'],
'netapp_flexvol_encryption')
encrypt_destination = share_types.parse_boolean_extra_spec(
'netapp_flexvol_encryption', dest_share_type_encrypted_val)
return encrypt_destination
def migration_continue(self, context, source_share, destination_share,
source_snapshots, snapshot_mappings,
share_server=None, destination_share_server=None):

View File

@ -33,6 +33,8 @@ REMOTE_CLUSTER_NAME = 'fake_cluster_2'
CLUSTER_ADDRESS_1 = 'fake_cluster_address'
CLUSTER_ADDRESS_2 = 'fake_cluster_address_2'
VERSION = 'NetApp Release 8.2.1 Cluster-Mode: Fri Mar 21 14:25:07 PDT 2014'
VERSION_NO_DARE = 'NetApp Release 9.1.0: Tue May 10 19:30:23 2016 <1no-DARE>'
VERSION_TUPLE = (9, 1, 0)
NODE_NAME = 'fake_node1'
NODE_NAMES = ('fake_node1', 'fake_node2')
VSERVER_NAME = 'fake_vserver'
@ -454,6 +456,18 @@ SYSTEM_NODE_GET_ITER_RESPONSE = etree.XML("""
</results>
""" % NODE_NAME)
SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_TRUE = etree.XML("""
<results status="passed">
<vol-encryption-supported>true</vol-encryption-supported>
</results>
""")
SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_FALSE = etree.XML("""
<results status="passed">
<vol-encryption-supported>false</vol-encryption-supported>
</results>
""")
NET_PORT_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
@ -1884,6 +1898,35 @@ GET_AGGREGATE_FOR_VOLUME_RESPONSE = etree.XML("""
'share': SHARE_NAME
})
GET_VOLUME_FOR_ENCRYPTED_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<volume-attributes>
<encrypt>true</encrypt>
<volume-id-attributes>
<name>%(volume)s</name>
<owning-vserver-name>manila_svm</owning-vserver-name>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {'volume': SHARE_NAME})
GET_VOLUME_FOR_ENCRYPTED_OLD_SYS_VERSION_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<name>%(volume)s</name>
<owning-vserver-name>manila_svm</owning-vserver-name>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {'volume': SHARE_NAME})
EXPORT_RULE_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
@ -2476,3 +2519,6 @@ FAKE_MANAGE_VOLUME = {
'style': 'fake_style',
'size': SHARE_SIZE,
}
FAKE_KEY_MANAGER_ERROR = "The onboard key manager is not enabled. To enable \
it, run \"security key-manager setup\"."

View File

@ -31,6 +31,16 @@ from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
class NetAppApiElementTransTests(test.TestCase):
"""Test case for NetApp API element translations."""
def test_get_set_system_version(self):
napi = api.NaServer('localhost')
# Testing calls before version is set
version = napi.get_system_version()
self.assertIsNone(version)
napi.set_system_version(fake.VERSION_TUPLE)
version = napi.get_system_version()
self.assertEqual(fake.VERSION_TUPLE, version)
def test_translate_struct_dict_unique_key(self):
"""Tests if dict gets properly converted to NaElements."""
root = api.NaElement('root')

View File

@ -66,10 +66,22 @@ class NetAppBaseClientTestCase(test.TestCase):
fake.SYSTEM_GET_VERSION_RESPONSE)
self.connection.invoke_successfully.return_value = version_response
result = self.client.get_system_version(cached=False)
self.assertEqual(fake.VERSION, result['version'])
self.assertEqual((8, 2, 1), result['version-tuple'])
def test_get_system_version_cached(self):
self.connection.get_system_version.return_value = {
'version': fake.VERSION,
'version-tuple': (8, 2, 1)
}
result = self.client.get_system_version()
self.assertEqual(fake.VERSION, result['version'])
self.assertEqual(('8', '2', '1'), result['version-tuple'])
self.assertEqual((8, 2, 1), result['version-tuple'])
def test_init_features(self):

View File

@ -53,6 +53,13 @@ class NetAppClientCmodeTestCase(test.TestCase):
'get_ontapi_version',
mock.Mock(return_value=(1, 20)))
self.mock_object(client_base.NetAppBaseClient,
'get_system_version',
mock.Mock(return_value={
'version-tuple': (8, 3, 0),
'version': fake.VERSION,
}))
self.client = client_cmode.NetAppCmodeClient(**fake.CONNECTION_INFO)
self.client.connection = mock.MagicMock()
@ -76,6 +83,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertFalse(self.client.features.BROADCAST_DOMAINS)
self.assertFalse(self.client.features.IPSPACES)
self.assertFalse(self.client.features.SUBNETS)
self.assertFalse(self.client.features.FLEXVOL_ENCRYPTION)
@ddt.data((1, 30), (1, 40), (2, 0))
def test_init_features_ontapi_1_30(self, ontapi_version):
@ -90,6 +98,145 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertTrue(self.client.features.IPSPACES)
self.assertTrue(self.client.features.SUBNETS)
@ddt.data((1, 110), (2, 0))
def test_init_features_ontap_1_110(self, ontapi_version):
self.mock_object(client_base.NetAppBaseClient,
'get_ontapi_version',
mock.Mock(return_value=ontapi_version))
self.client._init_features()
self.assertTrue(self.client.features.BROADCAST_DOMAINS)
self.assertTrue(self.client.features.IPSPACES)
self.assertTrue(self.client.features.SUBNETS)
self.assertTrue(self.client.features.FLEXVOL_ENCRYPTION)
@ddt.data(((9, 1, 0), fake.VERSION_NO_DARE), ((8, 3, 2), fake.VERSION))
@ddt.unpack
def test_is_nve_supported_unsupported_release_or_platform(self, gen, ver):
system_version = {'version-tuple': gen, 'version': ver}
self.mock_object(client_base.NetAppBaseClient,
'get_system_version',
mock.Mock(return_value=system_version))
self.mock_object(self.client,
'get_security_key_manager_nve_support',
mock.Mock(return_value=True))
self.mock_object(self.client,
'list_cluster_nodes',
mock.Mock(return_value=fake.NODE_NAMES))
result = self.client.is_nve_supported()
self.assertFalse(result)
def test_is_nve_supported_valid_platform_and_supported_release(self):
system_version = {
'version-tuple': (9, 1, 0),
'version': fake.VERSION,
}
self.mock_object(client_base.NetAppBaseClient,
'get_system_version',
mock.Mock(return_value=system_version))
self.mock_object(self.client,
'get_security_key_manager_nve_support',
mock.Mock(return_value=True))
self.mock_object(self.client,
'list_cluster_nodes',
mock.Mock(return_value=fake.NODE_NAMES))
result = self.client.is_nve_supported()
self.assertTrue(result)
def test_is_nve_supported_key_manager_not_enabled(self):
system_version = {
'version-tuple': (9, 1, 0),
'version': fake.VERSION,
}
self.mock_object(client_base.NetAppBaseClient,
'get_system_version',
mock.Mock(return_value=system_version))
self.mock_object(self.client,
'get_security_key_manager_nve_support',
mock.Mock(return_value=False))
self.mock_object(self.client,
'list_cluster_nodes',
mock.Mock(return_value=fake.NODE_NAMES))
result = self.client.is_nve_supported()
self.assertFalse(result)
def test_get_security_key_manager_nve_support_enabled(self):
api_response = netapp_api.NaElement(
fake.SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_TRUE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_security_key_manager_nve_support(
fake.NODE_NAME)
self.assertTrue(result)
api_args = {'node': fake.NODE_NAME}
self.client.send_request.assert_has_calls([
mock.call('security-key-manager-volume-encryption-supported',
api_args)])
def test_get_security_key_manager_nve_support_disabled(self):
api_response = netapp_api.NaElement(
fake.SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_FALSE)
self.mock_object(self.client, 'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_security_key_manager_nve_support(
fake.NODE_NAME)
self.assertFalse(result)
api_args = {'node': fake.NODE_NAME}
self.client.send_request.assert_has_calls([
mock.call('security-key-manager-volume-encryption-supported',
api_args)])
self.mock_object(self.client, 'send_request', self._mock_api_error())
self.assertRaises(netapp_api.NaApiError,
self.client.get_security_key_manager_nve_support,
fake.NODE_NAME)
self.mock_object(self.client, 'send_request', self._mock_api_error(
code=netapp_api.EAPIERROR, message=fake.FAKE_KEY_MANAGER_ERROR))
result = self.client.get_security_key_manager_nve_support(
fake.NODE_NAME)
self.assertFalse(result)
@ddt.data((True, True, True), (False, None, False))
@ddt.unpack
def test_send_volume_move_request_success(self, validation_only,
encrypt_dst, fv_encryption):
self.mock_object(self.client, 'features',
mock.Mock(FLEXVOL_ENCRYPTION=fv_encryption))
self.client._send_volume_move_request(fake.ROOT_VOLUME_NAME,
fake.NODE_VSERVER_NAME,
fake.SHARE_AGGREGATE_NAME,
validation_only=validation_only,
encrypt_destination=encrypt_dst)
@ddt.data((True, True, False))
@ddt.unpack
def test_send_volume_move_request_failure(self, validation_only,
encrypt_dst, fv_encrypt):
self.mock_object(self.client, 'features',
mock.Mock(FLEXVOL_ENCRYPTION=fv_encrypt))
self.assertRaises(exception.NetAppException,
self.client._send_volume_move_request,
fake.ROOT_VOLUME_NAME,
fake.NODE_VSERVER_NAME,
fake.SHARE_AGGREGATE_NAME,
validation_only=validation_only,
encrypt_destination=encrypt_dst)
def test_invoke_vserver_api(self):
self.client._invoke_vserver_api('fake-api', 'fake_vserver')
@ -2536,6 +2683,143 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME)
self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME)
def test_create_encrypted_volume(self):
self.mock_object(self.client, 'send_request')
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100, encrypt=True)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
'encrypt': 'true',
}
self.client.send_request.assert_called_once_with('volume-create',
volume_create_args)
def test_create_non_encrypted_volume(self):
self.mock_object(self.client, 'send_request')
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100, encrypt=False)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
}
self.client.send_request.assert_called_once_with('volume-create',
volume_create_args)
def test_create_encrypted_volume_not_supported(self):
self.assertRaises(exception.NetAppException,
self.client.create_volume,
fake.SHARE_AGGREGATE_NAME,
fake.SHARE_NAME,
100,
encrypt=True)
def test_is_flexvol_encrypted_unsupported(self):
self.client.features.add_feature('FLEXVOL_ENCRYPTION', supported=False)
result = self.client.is_flexvol_encrypted(fake.SHARE_NAME,
fake.VSERVER_NAME)
self.assertFalse(result)
def test_is_flexvol_encrypted_no_records_found(self):
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.is_flexvol_encrypted(fake.SHARE_NAME,
fake.VSERVER_NAME)
self.assertFalse(result)
def test_is_flexvol_encrypted(self):
self.client.features.add_feature('FLEXVOL_ENCRYPTION', supported=True)
api_response = netapp_api.NaElement(
fake.GET_VOLUME_FOR_ENCRYPTED_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.is_flexvol_encrypted(fake.SHARE_NAME,
fake.VSERVER_NAME)
volume_get_iter_args = {
'query': {
'volume-attributes': {
'encrypt': 'true',
'volume-id-attributes': {
'name': fake.SHARE_NAME,
'owning-vserver-name': fake.VSERVER_NAME,
}
}
},
'desired-attributes': {
'volume-attributes': {
'encrypt': None,
}
}
}
self.client.send_iter_request.assert_called_once_with(
'volume-get-iter', volume_get_iter_args)
self.assertTrue(result)
def test_is_flexvol_encrypted_8_x_system_version_response(self):
self.client.features.add_feature('FLEXVOL_ENCRYPTION', supported=True)
api_response = netapp_api.NaElement(
fake.GET_VOLUME_FOR_ENCRYPTED_OLD_SYS_VERSION_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.is_flexvol_encrypted(fake.SHARE_NAME,
fake.VSERVER_NAME)
volume_get_iter_args = {
'query': {
'volume-attributes': {
'encrypt': 'true',
'volume-id-attributes': {
'name': fake.SHARE_NAME,
'owning-vserver-name': fake.VSERVER_NAME,
}
}
},
'desired-attributes': {
'volume-attributes': {
'encrypt': None,
}
}
}
self.client.send_iter_request.assert_called_once_with(
'volume-get-iter', volume_get_iter_args)
self.assertFalse(result)
def test_enable_dedup(self):
self.mock_object(self.client, 'send_request')
@ -5812,6 +6096,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
'vserver': fake.VSERVER_NAME,
'dest-aggr': fake.SHARE_AGGREGATE_NAME,
'cutover-action': 'wait',
'encrypt-destination': 'false'
}
if method_name.startswith('check'):
expected_api_args['perform-validation-only'] = 'true'

View File

@ -111,13 +111,27 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(
performance, 'PerformanceLibrary',
mock.Mock(return_value='fake_perf_library'))
self.mock_object(
self.library._client, 'check_for_cluster_credentials',
mock.Mock(return_value=True))
self.library.do_setup(self.context)
mock_get_api_client.assert_called_once_with()
(self.library._client.check_for_cluster_credentials.
assert_called_once_with())
self.assertEqual('fake_perf_library', self.library._perf_library)
self.mock_object(self.library._client,
'check_for_cluster_credentials',
mock.Mock(return_value=True))
mock_set_cluster_info = self.mock_object(
self.library, '_set_cluster_info')
self.library.do_setup(self.context)
mock_set_cluster_info.assert_called_once()
def test_set_cluster_info(self):
self.library._set_cluster_info()
self.assertTrue(self.library._cluster_info['nve_support'],
fake.CLUSTER_NODES)
def test_check_for_setup_error(self):
@ -415,6 +429,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library, '_get_aggregate_space',
mock.Mock(return_value=fake.AGGREGATE_CAPACITIES))
self.library._have_cluster_creds = True
self.library._cluster_info = fake.CLUSTER_INFO
self.library._ssc_stats = fake.SSC_INFO
self.library._perf_library.get_node_utilization_for_pool = (
mock.Mock(side_effect=[30.0, 42.0]))
@ -430,6 +445,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library, '_get_aggregate_space',
mock.Mock(return_value=fake.AGGREGATE_CAPACITIES_VSERVER_CREDS))
self.library._have_cluster_creds = False
self.library._cluster_info = fake.CLUSTER_INFO
self.library._ssc_stats = fake.SSC_INFO_VSERVER_CREDS
self.library._perf_library.get_node_utilization_for_pool = (
mock.Mock(side_effect=[50.0, 50.0]))
@ -672,7 +688,7 @@ 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', dedup_enabled=True, split=True,
language='en-US', dedup_enabled=True, split=True, encrypt=False,
compression_enabled=False, max_files=5000, snapshot_reserve=8)
def test_remap_standard_boolean_extra_specs(self):
@ -703,7 +719,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
thin_provisioned=True, snapshot_policy='default',
language='en-US', dedup_enabled=True, split=True,
compression_enabled=False, max_files=5000,
compression_enabled=False, max_files=5000, encrypt=False,
snapshot_reserve=8, volume_type='dp')
def test_allocate_container_no_pool_name(self):
@ -834,18 +850,6 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_get_provisioning_options.assert_called_once_with(extra_specs)
mock_get_normalized_qos_specs.assert_called_once_with(extra_specs)
def test_get_provisioning_options(self):
result = self.library._get_provisioning_options(fake.EXTRA_SPEC)
self.assertEqual(fake.PROVISIONING_OPTIONS, result)
def test_get_provisioning_options_missing_spec(self):
result = self.library._get_provisioning_options(
fake.SHORT_BOOLEAN_EXTRA_SPEC)
self.assertEqual(
fake.PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE, result)
def test_get_provisioning_options_implicit_false(self):
result = self.library._get_provisioning_options(
fake.EMPTY_EXTRA_SPEC)
@ -858,6 +862,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'compression_enabled': False,
'dedup_enabled': False,
'split': False,
'encrypt': False,
}
self.assertEqual(expected, result)
@ -1048,7 +1053,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client.create_volume_clone.assert_called_once_with(
share_name, parent_share_name, parent_snapshot_name,
thin_provisioned=True, snapshot_policy='default',
language='en-US', dedup_enabled=True, split=True,
language='en-US', dedup_enabled=True, split=True, encrypt=False,
compression_enabled=False, max_files=5000)
if size > original_snapshot_size:
vserver_client.set_volume_size.assert_called_once_with(
@ -1587,8 +1592,6 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_get_volume_to_manage.assert_called_once_with(
fake.POOL_NAME, fake.FLEXVOL_NAME)
mock_validate_volume_for_manage.assert_called_once_with(
fake.FLEXVOL_TO_MANAGE, vserver_client)
mock_check_extra_specs_validity.assert_called_once_with(
share_to_manage, extra_specs)
mock_check_aggregate_extra_specs_validity.assert_called_once_with(
@ -1603,6 +1606,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.POOL_NAME, fake.SHARE_NAME, **provisioning_opts)
mock_modify_or_create_qos_policy.assert_called_once_with(
share_to_manage, extra_specs, fake.VSERVER1, vserver_client)
mock_validate_volume_for_manage.assert_called()
original_data = {
'original_name': fake.FLEXVOL_TO_MANAGE['name'],
@ -4209,6 +4213,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library, '_check_aggregate_extra_specs_validity')
mock_vserver_compatibility_check = self.mock_object(
self.library, '_check_destination_vserver_for_vol_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=False))
migration_compatibility = self.library.migration_check_compatibility(
self.context, fake_share.fake_share_instance(),
@ -4326,6 +4332,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_move_check = self.mock_object(
self.client, 'check_volume_move',
mock.Mock(side_effect=netapp_api.NaApiError))
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=False))
migration_compatibility = self.library.migration_check_compatibility(
self.context, fake_share.fake_share_instance(),
@ -4344,7 +4352,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
data_motion.get_backend_configuration.assert_called_once_with(
'destination_backend')
mock_move_check.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool')
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=False)
self.library._get_vserver.assert_has_calls(
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
@ -4362,6 +4371,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(share_utils, 'extract_host', mock.Mock(
side_effect=['destination_backend', 'destination_pool']))
mock_move_check = self.mock_object(self.client, 'check_volume_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=False))
migration_compatibility = self.library.migration_check_compatibility(
self.context, fake_share.fake_share_instance(),
@ -4379,7 +4390,51 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
data_motion.get_backend_configuration.assert_called_once_with(
'destination_backend')
mock_move_check.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool')
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=False)
self.library._get_vserver.assert_has_calls(
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
def test_migration_check_compatibility_destination_type_is_encrypted(self):
self.library._have_cluster_creds = True
self.mock_object(self.library, '_get_backend_share_name',
mock.Mock(return_value=fake.SHARE_NAME))
self.mock_object(data_motion, 'get_backend_configuration')
self.mock_object(self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1, mock.Mock())))
self.mock_object(share_utils, 'extract_host', mock.Mock(
side_effect=['destination_backend', 'destination_pool']))
mock_move_check = self.mock_object(self.client, 'check_volume_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=True))
self.mock_object(share_types, 'get_extra_specs_from_share',
mock.Mock(return_value={'spec1': 'spec-data'}))
self.mock_object(self.library,
'_check_extra_specs_validity')
self.mock_object(self.library,
'_check_aggregate_extra_specs_validity')
migration_compatibility = self.library.migration_check_compatibility(
self.context, fake_share.fake_share_instance(),
fake_share.fake_share_instance(), share_server=fake.SHARE_SERVER,
destination_share_server='dst_srv')
expected_compatibility = {
'compatible': True,
'writable': True,
'nondisruptive': True,
'preserve_metadata': True,
'preserve_snapshots': True,
}
self.assertDictMatch(expected_compatibility, migration_compatibility)
data_motion.get_backend_configuration.assert_called_once_with(
'destination_backend')
mock_move_check.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=True)
self.library._get_vserver.assert_has_calls(
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
@ -4395,6 +4450,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(share_utils, 'extract_host',
mock.Mock(return_value='destination_pool'))
mock_move = self.mock_object(self.client, 'start_volume_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=False))
retval = self.library.migration_start(
self.context, fake_share.fake_share_instance(),
@ -4405,7 +4462,34 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertIsNone(retval)
self.assertTrue(mock_info_log.called)
mock_move.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool')
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=False)
def test_migration_start_encrypted_destination(self):
mock_info_log = self.mock_object(lib_base.LOG, 'info')
source_snapshots = mock.Mock()
snapshot_mappings = mock.Mock()
self.mock_object(self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1, mock.Mock())))
self.mock_object(self.library, '_get_backend_share_name',
mock.Mock(return_value=fake.SHARE_NAME))
self.mock_object(share_utils, 'extract_host',
mock.Mock(return_value='destination_pool'))
mock_move = self.mock_object(self.client, 'start_volume_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=True))
retval = self.library.migration_start(
self.context, fake_share.fake_share_instance(),
fake_share.fake_share_instance(),
source_snapshots, snapshot_mappings,
share_server=fake.SHARE_SERVER, destination_share_server='dst_srv')
self.assertIsNone(retval)
self.assertTrue(mock_info_log.called)
mock_move.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=True)
def test_migration_continue_volume_move_failed(self):
source_snapshots = mock.Mock()

View File

@ -103,6 +103,7 @@ SHARE = {
'replica_state': constants.REPLICA_STATE_ACTIVE,
'status': constants.STATUS_AVAILABLE,
'share_server': None,
'encrypt': False,
}
FLEXVOL_TO_MANAGE = {
@ -126,6 +127,16 @@ QOS_POLICY_GROUP = {
'num-workloads': 1,
}
FLEXVOL = {
'aggregate': POOL_NAME,
'junction-path': '/%s' % FLEXVOL_NAME,
'name': FLEXVOL_NAME,
'type': 'rw',
'style': 'flex',
'size': '1610612736', # rounds down to 1 GB,
'owning-vserver-name': VSERVER1,
}
EXTRA_SPEC = {
'netapp:thin_provisioned': 'true',
'netapp:snapshot_policy': 'default',
@ -136,6 +147,7 @@ EXTRA_SPEC = {
'netapp:split_clone_on_create': 'true',
'netapp_disk_type': 'FCAL',
'netapp_raid_type': 'raid4',
'netapp_flexvol_encryption': 'true',
}
EXTRA_SPEC_WITH_QOS = copy.deepcopy(EXTRA_SPEC)
@ -158,6 +170,7 @@ PROVISIONING_OPTIONS = {
'compression_enabled': False,
'max_files': 5000,
'split': True,
'encrypt': False,
}
PROVISIONING_OPTIONS_WITH_QOS = copy.deepcopy(PROVISIONING_OPTIONS)
@ -179,6 +192,7 @@ PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {
'compression_enabled': False,
'max_files': None,
'split': False,
'encrypt': False,
}
PROVISIONING_OPTIONS_STRING = {
@ -626,6 +640,7 @@ POOLS = [
'dedupe': [True, False],
'compression': [True, False],
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'netapp_raid_type': 'raid4',
'netapp_disk_type': 'FCAL',
'netapp_hybrid_aggregate': 'false',
@ -648,6 +663,7 @@ POOLS = [
'dedupe': [True, False],
'compression': [True, False],
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'netapp_raid_type': 'raid_dp',
'netapp_disk_type': ['SATA', 'SSD'],
'netapp_hybrid_aggregate': 'true',
@ -673,6 +689,7 @@ POOLS_VSERVER_CREDS = [
'dedupe': [True, False],
'compression': [True, False],
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'utilization': 50.0,
'filter_function': None,
'goodness_function': None,
@ -692,6 +709,7 @@ POOLS_VSERVER_CREDS = [
'dedupe': [True, False],
'compression': [True, False],
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'utilization': 50.0,
'filter_function': None,
'goodness_function': None,
@ -715,6 +733,11 @@ SSC_AGGREGATES = [
},
]
CLUSTER_INFO = {
'nodes': CLUSTER_NODES,
'nve_support': True,
}
SSC_DISK_TYPES = ['FCAL', ['SATA', 'SSD']]
NODE = 'cluster1-01'

View File

@ -0,0 +1,5 @@
---
features:
- Now Manila NetApp ONTAP driver supports NetApp Volume Encryption
(NVE) which allows the creation of volumes that will be encrypted
at rest.