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:
parent
c4b59336c2
commit
48e4e65b02
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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\"."
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user