From 48e4e65b0293546d8beeb38c17bef13afaa74288 Mon Sep 17 00:00:00 2001 From: Jose Porrua Date: Thu, 2 Mar 2017 16:12:22 -0500 Subject: [PATCH] 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 Signed-off-by: Tiago Pasqualini Change-Id: Ib622c3d64cbec5a7254f6074f07d6f56f6492ca4 Implements: blueprint netapp-encrypted-shares --- .../drivers/netapp/dataontap/client/api.py | 11 + .../netapp/dataontap/client/client_base.py | 11 +- .../netapp/dataontap/client/client_cmode.py | 127 +++++++- .../netapp/dataontap/cluster_mode/lib_base.py | 60 +++- .../drivers/netapp/dataontap/client/fakes.py | 46 +++ .../netapp/dataontap/client/test_api.py | 10 + .../dataontap/client/test_client_base.py | 14 +- .../dataontap/client/test_client_cmode.py | 285 ++++++++++++++++++ .../dataontap/cluster_mode/test_lib_base.py | 126 ++++++-- .../share/drivers/netapp/dataontap/fakes.py | 23 ++ ...ge-based-cryptograpy-bb7e28896e2a2539.yaml | 5 + 11 files changed, 679 insertions(+), 39 deletions(-) create mode 100644 releasenotes/notes/bp-netapp-ontap-storage-based-cryptograpy-bb7e28896e2a2539.yaml diff --git a/manila/share/drivers/netapp/dataontap/client/api.py b/manila/share/drivers/netapp/dataontap/client/api.py index c446c797b2..6c09973b0b 100644 --- a/manila/share/drivers/netapp/dataontap/client/api.py +++ b/manila/share/drivers/netapp/dataontap/client/api.py @@ -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: diff --git a/manila/share/drivers/netapp/dataontap/client/client_base.py b/manila/share/drivers/netapp/dataontap/client/client_base.py index 2f5511aa5c..7e03f32264 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_base.py +++ b/manila/share/drivers/netapp/dataontap/client/client_base.py @@ -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 diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 9c15fa1ba5..31610a7017 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -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) diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py index d20b3449ce..3b21f2113d 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py @@ -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): diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index 1eb5d7274c..1afe6888f4 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -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(""" """ % NODE_NAME) +SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_TRUE = etree.XML(""" + + true + +""") + +SECUTITY_KEY_MANAGER_NVE_SUPPORT_RESPONSE_FALSE = etree.XML(""" + + false + +""") + NET_PORT_GET_ITER_RESPONSE = etree.XML(""" @@ -1884,6 +1898,35 @@ GET_AGGREGATE_FOR_VOLUME_RESPONSE = etree.XML(""" 'share': SHARE_NAME }) +GET_VOLUME_FOR_ENCRYPTED_RESPONSE = etree.XML(""" + + + + true + + %(volume)s + manila_svm + + + + 1 + +""" % {'volume': SHARE_NAME}) + +GET_VOLUME_FOR_ENCRYPTED_OLD_SYS_VERSION_RESPONSE = etree.XML(""" + + + + + %(volume)s + manila_svm + + + + 1 + +""" % {'volume': SHARE_NAME}) + EXPORT_RULE_GET_ITER_RESPONSE = etree.XML(""" @@ -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\"." diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_api.py b/manila/tests/share/drivers/netapp/dataontap/client/test_api.py index 590f16159e..d3d7f527cf 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_api.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_api.py @@ -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') diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_base.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_base.py index 30940fa53c..382ae22011 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_base.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_base.py @@ -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): diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py index 42fe82e123..87c87bd629 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py @@ -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' diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py index e3898c4151..323bdbf568 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py @@ -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() diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py index 5bbdbe9f18..b395a32224 100644 --- a/manila/tests/share/drivers/netapp/dataontap/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py @@ -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' diff --git a/releasenotes/notes/bp-netapp-ontap-storage-based-cryptograpy-bb7e28896e2a2539.yaml b/releasenotes/notes/bp-netapp-ontap-storage-based-cryptograpy-bb7e28896e2a2539.yaml new file mode 100644 index 0000000000..d681710cc0 --- /dev/null +++ b/releasenotes/notes/bp-netapp-ontap-storage-based-cryptograpy-bb7e28896e2a2539.yaml @@ -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.