diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py index a8aed380a3d..47f2065c751 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py @@ -889,6 +889,47 @@ CLONE_SPLIT_STATUS_NO_DATA_RESPONSE = etree.XML(""" """) +VOLUME_GET_ITER_ENCRYPTION_SSC_RESPONSE = etree.XML(""" + + + + true + + %(aggr)s + /%(volume)s + %(volume)s + %(vserver)s + rw + + + false + false + + + fake_qos_policy_group_name + + + true + none + 5 + 12345 + + + default + + + en_US + + + + 1 + +""" % { + 'aggr': VOLUME_AGGREGATE_NAMES[0], + 'volume': VOLUME_NAMES[0], + 'vserver': VOLUME_VSERVER_NAME, +}) + STORAGE_DISK_GET_ITER_RESPONSE_PAGE_1 = etree.XML(""" diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py index 15dd93b65fd..dc47d6add44 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py @@ -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( diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py index adb4bc25052..47ffccaa2c4 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py @@ -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): diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py index 5c11f366e76..ec8f7cbb7fc 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py @@ -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): diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py index f6e57e5738b..4e352126eb6 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py @@ -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'] diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py index a13097efedf..3cb8b8d8d95 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py @@ -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, diff --git a/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py b/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py index 3df17f331c7..14353232d14 100644 --- a/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py +++ b/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py @@ -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.""" diff --git a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py index 231a8091144..3ca99142f6d 100644 --- a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py +++ b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py @@ -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: