Merge "NetApp cDOT: Add NetApp Volume Encryption support"
This commit is contained in:
commit
d0057c7d5a
@ -889,6 +889,47 @@ CLONE_SPLIT_STATUS_NO_DATA_RESPONSE = etree.XML("""
|
||||
</results>
|
||||
""")
|
||||
|
||||
VOLUME_GET_ITER_ENCRYPTION_SSC_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<volume-attributes>
|
||||
<encrypt>true</encrypt>
|
||||
<volume-id-attributes>
|
||||
<containing-aggregate-name>%(aggr)s</containing-aggregate-name>
|
||||
<junction-path>/%(volume)s</junction-path>
|
||||
<name>%(volume)s</name>
|
||||
<owning-vserver-name>%(vserver)s</owning-vserver-name>
|
||||
<type>rw</type>
|
||||
</volume-id-attributes>
|
||||
<volume-mirror-attributes>
|
||||
<is-data-protection-mirror>false</is-data-protection-mirror>
|
||||
<is-replica-volume>false</is-replica-volume>
|
||||
</volume-mirror-attributes>
|
||||
<volume-qos-attributes>
|
||||
<policy-group-name>fake_qos_policy_group_name</policy-group-name>
|
||||
</volume-qos-attributes>
|
||||
<volume-space-attributes>
|
||||
<is-space-guarantee-enabled>true</is-space-guarantee-enabled>
|
||||
<space-guarantee>none</space-guarantee>
|
||||
<percentage-snapshot-reserve>5</percentage-snapshot-reserve>
|
||||
<size>12345</size>
|
||||
</volume-space-attributes>
|
||||
<volume-snapshot-attributes>
|
||||
<snapshot-policy>default</snapshot-policy>
|
||||
</volume-snapshot-attributes>
|
||||
<volume-language-attributes>
|
||||
<language-code>en_US</language-code>
|
||||
</volume-language-attributes>
|
||||
</volume-attributes>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>
|
||||
""" % {
|
||||
'aggr': VOLUME_AGGREGATE_NAMES[0],
|
||||
'volume': VOLUME_NAMES[0],
|
||||
'vserver': VOLUME_VSERVER_NAME,
|
||||
})
|
||||
|
||||
STORAGE_DISK_GET_ITER_RESPONSE_PAGE_1 = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
|
@ -1940,6 +1940,72 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_is_flexvol_encrypted(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake_client.VOLUME_GET_ITER_ENCRYPTION_SSC_RESPONSE)
|
||||
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
|
||||
self.mock_object(self.client,
|
||||
'send_iter_request',
|
||||
return_value=api_response)
|
||||
|
||||
result = self.client.is_flexvol_encrypted(
|
||||
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
||||
|
||||
volume_get_iter_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'encrypt': 'true',
|
||||
'volume-id-attributes': {
|
||||
'name': fake_client.VOLUME_NAME,
|
||||
'owning-vserver-name': fake_client.VOLUME_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_unsupported_version(self):
|
||||
|
||||
self.client.features.add_feature('FLEXVOL_ENCRYPTION', supported=False)
|
||||
result = self.client.is_flexvol_encrypted(
|
||||
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_is_flexvol_encrypted_no_records_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake_client.NO_RECORDS_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
return_value=api_response)
|
||||
|
||||
result = self.client.is_flexvol_encrypted(
|
||||
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_is_flexvol_encrypted_api_error(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
side_effect=self._mock_api_error())
|
||||
|
||||
result = self.client.is_flexvol_encrypted(
|
||||
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_get_aggregates(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
|
@ -41,6 +41,7 @@ SSC = {
|
||||
'netapp_raid_type': 'raid_dp',
|
||||
'netapp_disk_type': ['SSD'],
|
||||
'netapp_hybrid_aggregate': 'false',
|
||||
'netapp_flexvol_encryption': 'true',
|
||||
'pool_name': 'volume1',
|
||||
},
|
||||
'volume2': {
|
||||
@ -54,6 +55,7 @@ SSC = {
|
||||
'netapp_raid_type': 'raid_dp',
|
||||
'netapp_disk_type': ['FCAL', 'SSD'],
|
||||
'netapp_hybrid_aggregate': 'true',
|
||||
'netapp_flexvol_encryption': 'false',
|
||||
'pool_name': 'volume2',
|
||||
},
|
||||
}
|
||||
@ -84,6 +86,15 @@ SSC_DEDUPE_INFO = {
|
||||
},
|
||||
}
|
||||
|
||||
SSC_ENCRYPTION_INFO = {
|
||||
'volume1': {
|
||||
'netapp_flexvol_encryption': 'true',
|
||||
},
|
||||
'volume2': {
|
||||
'netapp_flexvol_encryption': 'false',
|
||||
},
|
||||
}
|
||||
|
||||
SSC_MIRROR_INFO = {
|
||||
'volume1': {
|
||||
'netapp_mirrored': 'false',
|
||||
@ -118,6 +129,19 @@ PROVISIONING_OPTS = {
|
||||
'size': 20,
|
||||
}
|
||||
|
||||
ENCRYPTED_PROVISIONING_OPTS = {
|
||||
'aggregate': 'fake_aggregate',
|
||||
'thin_provisioned': True,
|
||||
'snapshot_policy': None,
|
||||
'language': 'en_US',
|
||||
'dedupe_enabled': False,
|
||||
'compression_enabled': False,
|
||||
'snapshot_reserve': '12',
|
||||
'volume_type': 'rw',
|
||||
'size': 20,
|
||||
'encrypt': 'true',
|
||||
}
|
||||
|
||||
|
||||
def get_fake_cmode_config(backend_name):
|
||||
|
||||
|
@ -134,6 +134,10 @@ class CapabilitiesLibraryTestCase(test.TestCase):
|
||||
self.ssc_library, '_get_ssc_aggregate_info',
|
||||
side_effect=[fake.SSC_AGGREGATE_INFO['volume1'],
|
||||
fake.SSC_AGGREGATE_INFO['volume2']])
|
||||
mock_get_ssc_encryption_info = self.mock_object(
|
||||
self.ssc_library, '_get_ssc_encryption_info',
|
||||
side_effect=[fake.SSC_ENCRYPTION_INFO['volume1'],
|
||||
fake.SSC_ENCRYPTION_INFO['volume2']])
|
||||
ordered_ssc = collections.OrderedDict()
|
||||
ordered_ssc['volume1'] = fake.SSC_VOLUME_MAP['volume1']
|
||||
ordered_ssc['volume2'] = fake.SSC_VOLUME_MAP['volume2']
|
||||
@ -150,6 +154,8 @@ class CapabilitiesLibraryTestCase(test.TestCase):
|
||||
mock.call('volume1'), mock.call('volume2')])
|
||||
mock_get_ssc_aggregate_info.assert_has_calls([
|
||||
mock.call('aggr1'), mock.call('aggr2')])
|
||||
mock_get_ssc_encryption_info.assert_has_calls([
|
||||
mock.call('volume1'), mock.call('volume2')])
|
||||
|
||||
def test__update_for_failover(self):
|
||||
self.mock_object(self.ssc_library, 'update_ssc')
|
||||
@ -282,6 +288,22 @@ class CapabilitiesLibraryTestCase(test.TestCase):
|
||||
self.zapi_client.get_flexvol_dedupe_info.assert_called_once_with(
|
||||
fake_client.VOLUME_NAMES[0])
|
||||
|
||||
def test_get_ssc_encryption_info(self):
|
||||
|
||||
self.mock_object(
|
||||
self.ssc_library.zapi_client, 'is_flexvol_encrypted',
|
||||
return_value=True)
|
||||
|
||||
result = self.ssc_library._get_ssc_encryption_info(
|
||||
fake_client.VOLUME_NAMES[0])
|
||||
|
||||
expected = {
|
||||
'netapp_flexvol_encryption': 'true',
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
self.zapi_client.is_flexvol_encrypted.assert_called_once_with(
|
||||
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_get_ssc_mirror_info(self, mirrored):
|
||||
|
||||
|
@ -543,6 +543,9 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase):
|
||||
mock_get_provisioning_opts_call = self.mock_object(
|
||||
self.mock_src_client, 'get_provisioning_options_from_flexvol',
|
||||
return_value=provisioning_opts)
|
||||
mock_is_flexvol_encrypted = self.mock_object(
|
||||
self.mock_src_client, 'is_flexvol_encrypted',
|
||||
return_value=False)
|
||||
self.mock_object(self.dm_mixin, '_get_replication_aggregate_map',
|
||||
return_value=aggr_map)
|
||||
mock_client_call = self.mock_object(
|
||||
@ -560,6 +563,45 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase):
|
||||
mock_client_call.assert_called_once_with(
|
||||
self.dest_flexvol_name, 'aggr01', fakes.PROVISIONING_OPTS['size'],
|
||||
volume_type='dp', **expected_prov_opts)
|
||||
mock_is_flexvol_encrypted.assert_called_once_with(
|
||||
self.src_flexvol_name, self.src_vserver)
|
||||
|
||||
def test_create_encrypted_destination_flexvol(self):
|
||||
aggr_map = {
|
||||
fakes.ENCRYPTED_PROVISIONING_OPTS['aggregate']: 'aggr01',
|
||||
'aggr20': 'aggr02',
|
||||
}
|
||||
provisioning_opts = copy.deepcopy(fakes.ENCRYPTED_PROVISIONING_OPTS)
|
||||
expected_prov_opts = copy.deepcopy(fakes.ENCRYPTED_PROVISIONING_OPTS)
|
||||
expected_prov_opts.pop('volume_type', None)
|
||||
expected_prov_opts.pop('size', None)
|
||||
expected_prov_opts.pop('aggregate', None)
|
||||
mock_get_provisioning_opts_call = self.mock_object(
|
||||
self.mock_src_client, 'get_provisioning_options_from_flexvol',
|
||||
return_value=provisioning_opts)
|
||||
mock_is_flexvol_encrypted = self.mock_object(
|
||||
self.mock_src_client, 'is_flexvol_encrypted',
|
||||
return_value=True)
|
||||
self.mock_object(self.dm_mixin, '_get_replication_aggregate_map',
|
||||
return_value=aggr_map)
|
||||
mock_client_call = self.mock_object(
|
||||
self.mock_dest_client, 'create_flexvol')
|
||||
|
||||
retval = self.dm_mixin.create_destination_flexvol(
|
||||
self.src_backend, self.dest_backend,
|
||||
self.src_flexvol_name, self.dest_flexvol_name)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
mock_get_provisioning_opts_call.assert_called_once_with(
|
||||
self.src_flexvol_name)
|
||||
self.dm_mixin._get_replication_aggregate_map.assert_called_once_with(
|
||||
self.src_backend, self.dest_backend)
|
||||
mock_client_call.assert_called_once_with(
|
||||
self.dest_flexvol_name, 'aggr01',
|
||||
fakes.ENCRYPTED_PROVISIONING_OPTS['size'],
|
||||
volume_type='dp', **expected_prov_opts)
|
||||
mock_is_flexvol_encrypted.assert_called_once_with(
|
||||
self.src_flexvol_name, self.src_vserver)
|
||||
|
||||
def test_ensure_snapmirrors(self):
|
||||
flexvols = ['nvol1', 'nvol2']
|
||||
|
@ -2,6 +2,7 @@
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||
# Copyright (c) 2016 Mike Rooney. All rights reserved.
|
||||
# Copyright (c) 2017 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
|
||||
@ -60,6 +61,7 @@ class Client(client_base.Client):
|
||||
ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
|
||||
ontapi_1_30 = ontapi_version >= (1, 30)
|
||||
ontapi_1_100 = ontapi_version >= (1, 100)
|
||||
ontapi_1_1xx = (1, 100) <= ontapi_version < (1, 200)
|
||||
|
||||
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
|
||||
self.features.add_feature('USER_CAPABILITY_LIST',
|
||||
@ -73,6 +75,7 @@ class Client(client_base.Client):
|
||||
supported=ontapi_1_30)
|
||||
self.features.add_feature('BACKUP_CLONE_PARAM', supported=ontapi_1_100)
|
||||
self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
|
||||
self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_1xx)
|
||||
|
||||
def _invoke_vserver_api(self, na_element, vserver):
|
||||
server = copy.copy(self.connection)
|
||||
@ -1163,6 +1166,41 @@ class Client(client_base.Client):
|
||||
|
||||
return True
|
||||
|
||||
def is_flexvol_encrypted(self, flexvol_name, vserver_name):
|
||||
"""Check if a flexvol is encrypted."""
|
||||
|
||||
if not self.features.FLEXVOL_ENCRYPTION:
|
||||
return False
|
||||
|
||||
api_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'encrypt': 'true',
|
||||
'volume-id-attributes': {
|
||||
'name': flexvol_name,
|
||||
'owning-vserver-name': vserver_name,
|
||||
},
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'encrypt': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
result = self.send_iter_request('volume-get-iter', api_args)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _LE('Failed to get Encryption info for volume %s.')
|
||||
LOG.exception(msg, flexvol_name)
|
||||
return False
|
||||
|
||||
if not self._has_records(result):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def create_flexvol(self, flexvol_name, aggregate_name, size_gb,
|
||||
space_guarantee_type=None, snapshot_policy=None,
|
||||
language=None, dedupe_enabled=False,
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright (c) 2016 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2017 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
|
||||
@ -44,7 +45,9 @@ SSC_API_MAP = {
|
||||
'netapp_dedup',
|
||||
'netapp_compression',
|
||||
],
|
||||
('volume', 'show', 'volume-get-iter'): [],
|
||||
('volume', 'show', 'volume-get-iter'): [
|
||||
'netapp_flexvol_encryption',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@ -129,6 +132,7 @@ class CapabilitiesLibrary(object):
|
||||
ssc_volume.update(self._get_ssc_flexvol_info(flexvol_name))
|
||||
ssc_volume.update(self._get_ssc_dedupe_info(flexvol_name))
|
||||
ssc_volume.update(self._get_ssc_mirror_info(flexvol_name))
|
||||
ssc_volume.update(self._get_ssc_encryption_info(flexvol_name))
|
||||
|
||||
# Get aggregate info
|
||||
aggregate_name = ssc_volume.get('netapp_aggregate')
|
||||
@ -189,6 +193,13 @@ class CapabilitiesLibrary(object):
|
||||
'netapp_compression': six.text_type(compression).lower(),
|
||||
}
|
||||
|
||||
def _get_ssc_encryption_info(self, flexvol_name):
|
||||
"""Gather flexvol encryption info and recast into SSC-style stats."""
|
||||
encrypted = self.zapi_client.is_flexvol_encrypted(
|
||||
flexvol_name, self.vserver_name)
|
||||
|
||||
return {'netapp_flexvol_encryption': six.text_type(encrypted).lower()}
|
||||
|
||||
def _get_ssc_mirror_info(self, flexvol_name):
|
||||
"""Gather SnapMirror info and recast into SSC-style volume stats."""
|
||||
|
||||
|
@ -417,6 +417,12 @@ class DataMotionMixin(object):
|
||||
src_flexvol_name)
|
||||
)
|
||||
|
||||
# If the source is encrypted then the destination needs to be
|
||||
# encrypted too. Using is_flexvol_encrypted because it includes
|
||||
# a simple check to ensure that the NVE feature is supported.
|
||||
if src_client.is_flexvol_encrypted(src_flexvol_name, src_vserver):
|
||||
provisioning_options['encrypt'] = 'true'
|
||||
|
||||
# Remove size and volume_type
|
||||
size = provisioning_options.pop('size', None)
|
||||
if not size:
|
||||
|
Loading…
x
Reference in New Issue
Block a user