From 08dcf03541995cc9f8a22232bf738967e4b6570b Mon Sep 17 00:00:00 2001 From: Pony Chou Date: Fri, 2 Jun 2017 15:49:21 +0800 Subject: [PATCH] Add support for enhanced features to the QNAP Cinder driver This adds enhanced supports to the QNAP Cinder driver: - CHAP - Thin Provision - SSD Cache - Dedupe - Compression DocImpact Implements: blueprint qnap-enhance-support Change-Id: I2a7440789753bb0e42ac0e8d0190b21652a87e2f --- cinder/tests/unit/volume/drivers/test_qnap.py | 134 ++++++---- cinder/volume/drivers/qnap.py | 252 +++++++++++------- ...qnap-enhance-support-4ab5cbb110b3303b.yaml | 5 + 3 files changed, 256 insertions(+), 135 deletions(-) create mode 100644 releasenotes/notes/qnap-enhance-support-4ab5cbb110b3303b.yaml diff --git a/cinder/tests/unit/volume/drivers/test_qnap.py b/cinder/tests/unit/volume/drivers/test_qnap.py index 430cfafe083..950e82cbe30 100644 --- a/cinder/tests/unit/volume/drivers/test_qnap.py +++ b/cinder/tests/unit/volume/drivers/test_qnap.py @@ -22,6 +22,7 @@ except ImportError: from ddt import data from ddt import ddt from ddt import unpack +import eventlet import mock from oslo_config import cfg from oslo_utils import units @@ -594,19 +595,26 @@ def create_configuration( management_url, san_iscsi_ip, poolname, - thin_provision=True): + thin_provision=True, + compression=True, + deduplication=False, + ssd_cache=False): """Create configuration.""" configuration = mock.Mock() configuration.san_login = username configuration.san_password = password configuration.qnap_management_url = management_url configuration.san_thin_provision = thin_provision + configuration.qnap_compression = compression + configuration.qnap_deduplication = deduplication + configuration.qnap_ssd_cache = ssd_cache configuration.san_iscsi_ip = san_iscsi_ip configuration.qnap_poolname = poolname configuration.safe_get.return_value = 'QNAP' configuration.iscsi_ip_address = '1.2.3.4' configuration.qnap_storage_protocol = 'iscsi' configuration.reserved_percentage = 0 + configuration.use_chap_auth = False return configuration @@ -679,6 +687,14 @@ class VolumeClass(object): 'name': 'fakeTargetIqn', 'tgt_lun': '1' } + self.volume_type = { + 'extra_specs': { + 'qnap_thin_provision': 'True', + 'qnap_compression': 'True', + 'qnap_deduplication': 'False', + 'qnap_ssd_cache': 'False' + } + } def __getitem__(self, arg): """Getitem.""" @@ -689,7 +705,8 @@ class VolumeClass(object): 'name': self.name, 'volume_metadata': self.volume_metadata, 'metadata': self.metadata, - 'provider_location': self.provider_location + 'provider_location': self.provider_location, + 'volume_type': self.volume_type }[arg] def __contains__(self, arg): @@ -701,7 +718,8 @@ class VolumeClass(object): 'name': self.name, 'volume_metadata': self.volume_metadata, 'metadata': self.metadata, - 'provider_location': self.provider_location + 'provider_location': self.provider_location, + 'volume_type': self.volume_type }[arg] def __setitem__(self, key, value): @@ -1288,6 +1306,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') self.driver.create_volume(fake_volume) @@ -1295,7 +1314,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): fake_volume, self.driver.configuration.qnap_poolname, 'fakeLun', - True) + True, False, True, False) expected_call_list = [ mock.call(LUNName='fakeLun'), @@ -1442,6 +1461,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): 'http://1.2.3.4:8080', 'Pool1', True)) + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') self.driver.create_cloned_volume(fake_volume, fake_src_vref) @@ -1506,19 +1526,18 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') self.driver.create_cloned_volume(fake_volume, fake_src_vref) mock_extend_lun.assert_called_once_with(fake_volume, 'fakeLunNaa') - @mock.patch('eventlet.greenthread.sleep', return_value=None) @mock.patch.object(qnap.QnapISCSIDriver, '_create_snapshot_name') @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor') def test_create_snapshot_positive( self, mock_api_executor, - mock_create_snapshot_name, - mock_greenthread_sleep): + mock_create_snapshot_name): """Test create snapshot.""" fake_volume = VolumeClass( 'fakeDisplayName', 'fakeId', 100, 'fakeLunName') @@ -1542,6 +1561,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') self.driver.create_snapshot(snapshot) @@ -1647,6 +1667,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') self.driver.create_volume_from_snapshot(fake_volume, fake_snapshot) @@ -1743,7 +1764,11 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): free_capacity_gb=928732941681 / units.Gi, provisioned_capacity_gb=1480470528 / units.Gi, reserved_percentage=self.driver.configuration.reserved_percentage, - QoS_support=False) + QoS_support=False, + qnap_thin_provision=['True', 'False'], + qnap_compression=['True', 'False'], + qnap_deduplication=['True', 'False'], + qnap_ssd_cache=['True', 'False']) expected_res['pools'] = [single_pool] self.assertEqual( @@ -1819,7 +1844,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): 'LUNStatus': '1'} mock_api_return.edit_lun.assert_called_once_with(expect_lun) - @mock.patch('eventlet.greenthread.sleep', return_value=None) @mock.patch.object(qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata') @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name') @@ -1828,8 +1852,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self, mock_api_executor, mock_gen_random_name, - mock_get_lun_naa_from_volume_metadata, - mock_greenthread_sleep): + mock_get_lun_naa_from_volume_metadata): """Test create export.""" fake_volume = VolumeClass( 'fakeDisplayName', 'fakeId', 100, 'fakeLunName') @@ -1847,8 +1870,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): mock_api_return.create_target.return_value = 'fakeTargetIndex' mock_api_return.get_target_info.return_value = ( self.get_target_info_return_value()) - mock_api_return.get_all_iscsi_portal_setting.return_value = ( - FAKE_RES_DETAIL_GET_ALL_ISCSI_PORTAL_SETTING) mock_api_return.map_lun.return_value = None mock_api_return.get_ethernet_ip.return_value = ['1.2.3.4'], None @@ -1860,7 +1881,11 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.driver.configuration.use_chap_auth = False + self.driver.configuration.chap_username = '' + self.driver.configuration.chap_password = '' self.driver.iscsi_port = 'fakeServicePort' + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % { @@ -1874,7 +1899,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self.assertEqual(expected_return, self.driver.create_export( 'context', fake_volume, fake_connector)) - @mock.patch('eventlet.greenthread.sleep', return_value=None) @mock.patch.object(qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata') @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name') @@ -1883,8 +1907,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self, mock_api_executor, mock_gen_random_name, - mock_get_lun_naa_from_volume_metadata, - mock_greenthread_sleep): + mock_get_lun_naa_from_volume_metadata): """Test create export.""" fake_volume = VolumeClass( 'fakeDisplayName', 'fakeId', 100, 'fakeLunName') @@ -1902,8 +1925,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): mock_api_return.create_target.return_value = 'fakeTargetIndex' mock_api_return.get_target_info.return_value = ( self.get_target_info_return_value()) - mock_api_return.get_all_iscsi_portal_setting.return_value = ( - FAKE_RES_DETAIL_GET_ALL_ISCSI_PORTAL_SETTING) mock_api_return.map_lun.return_value = None mock_api_return.get_ethernet_ip.return_value = ['1.2.3.4'], None @@ -1915,7 +1936,11 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.driver.configuration.use_chap_auth = False + self.driver.configuration.chap_username = '' + self.driver.configuration.chap_password = '' self.driver.iscsi_port = 'fakeServicePort' + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % { @@ -1929,7 +1954,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self.assertEqual(expected_return, self.driver.create_export( 'context', fake_volume, fake_connector)) - @mock.patch('eventlet.greenthread.sleep', return_value=None) @mock.patch.object(qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata') @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name') @@ -1938,8 +1962,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self, mock_api_executor, mock_gen_random_name, - mock_get_lun_naa_from_volume_metadata, - mock_greenthread_sleep): + mock_get_lun_naa_from_volume_metadata): """Test create export.""" fake_volume = VolumeClass( 'fakeDisplayName', 'fakeId', 100, 'fakeLunName') @@ -1954,8 +1977,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): FAKE_RES_DETAIL_ISCSI_PORTAL_INFO) mock_gen_random_name.return_value = 'fakeTargetName' mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa' - mock_api_return.get_target_info_by_initiator.return_value = ( - 'fakeTargetIndex', 'fakeTargetIqn') mock_api_return.create_target.return_value = 'fakeTargetIndex' mock_api_return.get_target_info.return_value = ( self.get_target_info_return_value()) @@ -1971,7 +1992,11 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.driver.configuration.use_chap_auth = False + self.driver.configuration.chap_username = '' + self.driver.configuration.chap_password = '' self.driver.iscsi_port = 'fakeServicePort' + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % { @@ -1985,7 +2010,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): self.assertEqual(expected_return, self.driver.create_export( 'context', fake_volume, fake_connector)) - @mock.patch('eventlet.greenthread.sleep', return_value=None) @mock.patch.object(qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata') @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name') @@ -1996,8 +2020,7 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): mock_api_executor, mock_api_executor_ts, mock_gen_random_name, - mock_get_lun_naa_from_volume_metadata, - mock_greenthread_sleep): + mock_get_lun_naa_from_volume_metadata): """Test create export.""" fake_volume = VolumeClass( 'fakeDisplayName', 'fakeId', 100, 'fakeLunName') @@ -2016,8 +2039,6 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): mock_api_return.create_target.return_value = 'fakeTargetIndex' mock_api_return.get_target_info.return_value = ( self.get_target_info_return_value()) - mock_api_return.get_all_iscsi_portal_setting.return_value = ( - FAKE_RES_DETAIL_GET_ALL_ISCSI_PORTAL_SETTING) mock_api_return.map_lun.return_value = None mock_api_return.get_ethernet_ip.return_value = ['1.2.3.4'], None @@ -2029,7 +2050,11 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Storage Pool 1', True)) + self.driver.configuration.use_chap_auth = False + self.driver.configuration.chap_username = '' + self.driver.configuration.chap_password = '' self.driver.iscsi_port = 'fakeServicePort' + self.mock_object(eventlet, 'sleep') self.driver.do_setup('context') expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % { @@ -2109,6 +2134,9 @@ class QnapDriverVolumeTestCase(QnapDriverBaseTestCase): '1.2.3.4', 'Pool1', True)) + self.driver.configuration.use_chap_auth = False + self.driver.configuration.chap_username = '' + self.driver.configuration.chap_password = '' self.driver.iscsi_port = 'fakeServicePort' self.driver.do_setup('context') @@ -2332,7 +2360,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', True)) + fake_volume, 'fakepool', 'fakeLun', True, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -2342,6 +2370,8 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): fake_params['LUNPath'] = 'fakeLun' fake_params['poolID'] = 'fakepool' fake_params['lv_ifssd'] = 'no' + fake_params['compression'] = '1' + fake_params['dedup'] = 'off' fake_params['LUNCapacity'] = 100 fake_params['lv_threshold'] = '80' fake_params['sid'] = 'fakeSid' @@ -2392,7 +2422,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', False)) + fake_volume, 'fakepool', 'fakeLun', False, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -2402,6 +2432,8 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): fake_params['LUNPath'] = 'fakeLun' fake_params['poolID'] = 'fakepool' fake_params['lv_ifssd'] = 'no' + fake_params['compression'] = '1' + fake_params['dedup'] = 'off' fake_params['LUNCapacity'] = 100 fake_params['lv_threshold'] = '80' fake_params['sid'] = 'fakeSid' @@ -2453,7 +2485,8 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_create_lun_negative_with_wrong_result( @@ -2481,7 +2514,8 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_delete_lun( @@ -2845,7 +2879,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): True)) self.driver.do_setup('context') self.driver.api_executor.add_target_init( - 'fakeTargetIqn', 'fakeInitiatorIqn') + 'fakeTargetIqn', 'fakeInitiatorIqn', False, '', '') fake_params = {} fake_params['func'] = 'add_init' @@ -2905,7 +2939,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.driver.do_setup('context') self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.add_target_init, - 'fakeTargetIqn', 'fakeInitiatorIqn') + 'fakeTargetIqn', 'fakeInitiatorIqn', False, '', '') @mock.patch('six.moves.http_client.HTTPConnection') def test_add_target_init_negative_with_wrong_result( @@ -2929,7 +2963,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.driver.do_setup('context') self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.add_target_init, - 'fakeTargetIqn', 'fakeInitiatorIqn') + 'fakeTargetIqn', 'fakeInitiatorIqn', False, '', '') @mock.patch('six.moves.http_client.HTTPConnection') def test_remove_target_init( @@ -4531,7 +4565,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): True)) self.driver.do_setup('context') self.driver.api_executor.get_target_info_by_initiator( - 'fakeInitiatorIQN', 'fakeLunSlotId') + 'fakeInitiatorIQN') fake_params = {} fake_params['func'] = 'extra_get' @@ -4582,7 +4616,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor. get_target_info_by_initiator, - 'fakeInitiatorIQN', 'fakeLunSlotId') + 'fakeInitiatorIQN') @mock.patch('six.moves.http_client.HTTPConnection') def test_get_target_info_by_initiator_with_wrong_result( @@ -4605,7 +4639,7 @@ class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase): True)) self.driver.do_setup('context') self.driver.api_executor.get_target_info_by_initiator( - 'fakeInitiatorIQN', 'fakeLunSlotId') + 'fakeInitiatorIQN') fake_params = {} fake_params['func'] = 'extra_get' @@ -4662,7 +4696,7 @@ class QnapAPIExecutorTsTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', True)) + fake_volume, 'fakepool', 'fakeLun', True, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -4722,7 +4756,7 @@ class QnapAPIExecutorTsTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', False)) + fake_volume, 'fakepool', 'fakeLun', False, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -4783,7 +4817,8 @@ class QnapAPIExecutorTsTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_create_lun_negative_with_wrong_result( @@ -4811,7 +4846,8 @@ class QnapAPIExecutorTsTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_delete_lun( @@ -5742,7 +5778,7 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', True)) + fake_volume, 'fakepool', 'fakeLun', True, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -5752,6 +5788,8 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): fake_params['LUNPath'] = 'fakeLun' fake_params['poolID'] = 'fakepool' fake_params['lv_ifssd'] = 'no' + fake_params['compression'] = '1' + fake_params['dedup'] = 'off' fake_params['sync'] = 'disabled' fake_params['LUNCapacity'] = 100 fake_params['lv_threshold'] = '80' @@ -5803,7 +5841,7 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): self.assertEqual( 'fakeLunIndex', self.driver.api_executor.create_lun( - fake_volume, 'fakepool', 'fakeLun', False)) + fake_volume, 'fakepool', 'fakeLun', False, False, True, False)) fake_params = {} fake_params['func'] = 'add_lun' @@ -5813,6 +5851,8 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): fake_params['LUNPath'] = 'fakeLun' fake_params['poolID'] = 'fakepool' fake_params['lv_ifssd'] = 'no' + fake_params['compression'] = '1' + fake_params['dedup'] = 'off' fake_params['sync'] = 'disabled' fake_params['LUNCapacity'] = 100 fake_params['lv_threshold'] = '80' @@ -5865,7 +5905,8 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_create_lun_negative_with_wrong_result( @@ -5893,7 +5934,8 @@ class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.api_executor.create_lun, - fake_volume, 'fakepool', 'fakeLun', 'False') + fake_volume, 'fakepool', 'fakeLun', 'False', + 'False', 'True', 'False') @mock.patch('six.moves.http_client.HTTPConnection') def test_get_ethernet_ip_with_type( diff --git a/cinder/volume/drivers/qnap.py b/cinder/volume/drivers/qnap.py index abd33f7c1a3..5e50fd2a114 100644 --- a/cinder/volume/drivers/qnap.py +++ b/cinder/volume/drivers/qnap.py @@ -31,6 +31,7 @@ except ImportError: from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log as logging +from oslo_utils import strutils from oslo_utils import timeutils from oslo_utils import units import six @@ -62,16 +63,23 @@ CONF.register_opts(qnap_opts, group=configuration.SHARED_CONF_GROUP) @interface.volumedriver class QnapISCSIDriver(san.SanISCSIDriver): - """OpenStack driver to enable QNAP Storage. + """QNAP iSCSI based cinder driver + + .. code-block:: default + + Version History: + 1.0.0: + Initial driver (Only iSCSI). + 1.2.001: + Add supports for Thin Provisioning, SSD Cache, Deduplication + , Compression and CHAP. - Version history: - 1.0.0 - Initial driver (Only iSCSI) """ # ThirdPartySystems wiki page CI_WIKI_NAME = "QNAP_CI" - VERSION = '1.1.021' + VERSION = '1.2.001' TIME_INTERVAL = 3 @@ -103,6 +111,22 @@ class QnapISCSIDriver(san.SanISCSIDriver): raise exception.InvalidInput( reason=_('%s is not set.') % attr) + if not self.configuration.use_chap_auth: + self.configuration.chap_username = '' + self.configuration.chap_password = '' + else: + if not str.isalnum(self.configuration.chap_username): + # invalid chap_username + LOG.error('Username must be single-byte alphabet or number.') + raise exception.InvalidInput( + reason=_('Username must be single-byte ' + 'alphabet or number.')) + if not 12 <= len(self.configuration.chap_password) <= 16: + # invalid chap_password + LOG.error('Password must contain 12-16 characters.') + raise exception.InvalidInput( + reason=_('Password must contain 12-16 characters.')) + def do_setup(self, context): """Setup the QNAP Cinder volume driver.""" self._check_config() @@ -111,7 +135,7 @@ class QnapISCSIDriver(san.SanISCSIDriver): # Setup API Executor try: - self.api_executor = self.creat_api_executor() + self.api_executor = self.create_api_executor() except Exception: LOG.error('Failed to create HTTP client. ' 'Check ip, port, username, password' @@ -123,7 +147,7 @@ class QnapISCSIDriver(san.SanISCSIDriver): """Check the status of setup.""" pass - def creat_api_executor(self): + def create_api_executor(self): """Create api executor by nas model.""" self.api_executor = QnapAPIExecutor( username=self.configuration.san_login, @@ -229,12 +253,60 @@ class QnapISCSIDriver(san.SanISCSIDriver): break return create_lun_name + def _parse_boolean_extra_spec(self, extra_spec_value): + """Parse boolean value from extra spec. + + Parse extra spec values of the form ' True' , ' False', + 'True' and 'False'. + """ + + if not isinstance(extra_spec_value, six.string_types): + extra_spec_value = six.text_type(extra_spec_value) + + match = re.match(r'^\s*(?PTrue|False)$', + extra_spec_value.strip(), + re.IGNORECASE) + if match: + extra_spec_value = match.group('value') + return strutils.bool_from_string(extra_spec_value, strict=True) + def create_volume(self, volume): """Create a new volume.""" start_time = time.time() LOG.debug('in create_volume') LOG.debug('volume: %s', volume.__dict__) - reserve = self.configuration.san_thin_provision + try: + extra_specs = volume["volume_type"]["extra_specs"] + LOG.debug('extra_spec: %s', extra_specs) + qnap_thin_provision = self._parse_boolean_extra_spec( + extra_specs.get('qnap_thin_provision', 'true')) + qnap_compression = self._parse_boolean_extra_spec( + extra_specs.get('qnap_compression', 'true')) + qnap_deduplication = self._parse_boolean_extra_spec( + extra_specs.get('qnap_deduplication', 'false')) + qnap_ssd_cache = self._parse_boolean_extra_spec( + extra_specs.get('qnap_ssd_cache', 'false')) + except TypeError: + LOG.debug('Unable to retrieve extra specs info. ' + 'Use default extra spec.') + qnap_thin_provision = True + qnap_compression = True + qnap_deduplication = False + qnap_ssd_cache = False + + LOG.debug('qnap_thin_provision: %(qnap_thin_provision)s ' + 'qnap_compression: %(qnap_compression)s ' + 'qnap_deduplication: %(qnap_deduplication)s ' + 'qnap_ssd_cache: %(qnap_ssd_cache)s', + {'qnap_thin_provision': qnap_thin_provision, + 'qnap_compression': qnap_compression, + 'qnap_deduplication': qnap_deduplication, + 'qnap_ssd_cache': qnap_ssd_cache}) + + if (qnap_deduplication and not qnap_thin_provision): + LOG.debug('Dedupe cannot be enabled without thin_provisioning.') + raise exception.VolumeBackendAPIException( + data=_('Dedupe cannot be enabled without thin_provisioning.')) # User could create two volume with the same name on horizon. # Therefore, We should not use display name to create lun on nas. @@ -244,7 +316,10 @@ class QnapISCSIDriver(san.SanISCSIDriver): volume, self.configuration.qnap_poolname, create_lun_name, - reserve) + qnap_thin_provision, + qnap_ssd_cache, + qnap_compression, + qnap_deduplication) max_wait_sec = 600 try_times = 0 @@ -590,7 +665,11 @@ class QnapISCSIDriver(san.SanISCSIDriver): free_capacity_gb=freesize_bytes / units.Gi, provisioned_capacity_gb=provisioned_bytes / units.Gi, reserved_percentage=self.configuration.reserved_percentage, - QoS_support=False) + QoS_support=False, + qnap_thin_provision=["True", "False"], + qnap_compression=["True", "False"], + qnap_deduplication=["True", "False"], + qnap_ssd_cache=["True", "False"]) self.group_stats['pools'] = [single_pool] return self.group_stats @@ -627,51 +706,6 @@ class QnapISCSIDriver(san.SanISCSIDriver): target_index = '' target_iqn = '' - if 'ES' in internal_model_name.upper() and fw_version == "1.1.4": - LOG.debug('in ES FW after 1.1.4: get_target_info_by_initiator') - target_index, target_iqn = (self.api_executor - .get_target_info_by_initiator - (connector['initiator'], lun_slot_id)) - LOG.debug('get_target_info_by_initiator target_index: %s', - target_index) - LOG.debug('get_target_info_by_initiator target_iqn: %s', - target_iqn) - else: - ret = self.api_executor.get_all_iscsi_portal_setting() - root = ET.fromstring(ret['data']) - # find the targets have acl with connector['initiator'] - target_with_initiator_list = [] - target_acl_tree = root.find('targetACL') - target_acl_list = target_acl_tree.findall('row') - tmp_target_iqn = '' - for targetACL in target_acl_list: - tmp_target_iqn = targetACL.find('targetIQN').text - # If lun and the targetiqn in different controller, - # skip the targetiqn, in case lun in sca map to target of scb - LOG.debug('lun_slot_id: %s', lun_slot_id) - LOG.debug('tmp_target_iqn[-1]: %s', tmp_target_iqn[-1]) - if (lun_slot_id != ''): - if (lun_slot_id != tmp_target_iqn[-1]): - LOG.debug('skip the targetiqn') - continue - - target_init_info_list = targetACL.findall('targetInitInfo') - for targetInitInfo in target_init_info_list: - if(targetInitInfo.find('initiatorIQN').text == - connector['initiator']): - target_with_initiator_list.append( - targetACL.find('targetIndex').text) - - # find the target in target_with_initiator_list with ready status - target_tree = root.find('iSCSITargetList') - target_list = target_tree.findall('targetInfo') - for target_with_initiator in target_with_initiator_list: - for target in target_list: - if(target_with_initiator == - target.find('targetIndex').text): - if int(target.find('targetStatus').text) >= 0: - target_index = target_with_initiator - target_iqn = target.find('targetIQN').text # create a new target if no target has ACL connector['initiator'] LOG.debug('exist target_index: %s', target_index) @@ -703,7 +737,10 @@ class QnapISCSIDriver(san.SanISCSIDriver): self.api_executor.remove_target_init(target_iqn, default_acl) # add ACL self.api_executor.add_target_init( - target_iqn, connector['initiator']) + target_iqn, connector['initiator'], + self.configuration.use_chap_auth, + self.configuration.chap_username, + self.configuration.chap_password) # Get information for multipath target_iqns = [] @@ -799,11 +836,7 @@ class QnapISCSIDriver(san.SanISCSIDriver): LOG.debug('connector: %s', connector['initiator']) iscsi_port, target_index, target_iqn, target_iqns, target_portals = ( - Util.retriveFormCache(connector['initiator'] + - self.configuration.qnap_management_url, - lambda: self._get_portal_info( - volume, connector, lun_slot_id, lun_owner), - 30)) + self._get_portal_info(volume, connector, lun_slot_id, lun_owner)) self.api_executor.map_lun(lun_index, target_index) @@ -882,6 +915,12 @@ class QnapISCSIDriver(san.SanISCSIDriver): 'tgt_lun': target_lun_id, } + if self.configuration.use_chap_auth: + provider_auth = 'CHAP %s %s' % (self.configuration.chap_username, + self.configuration.chap_password) + else: + provider_auth = None + elapsed_time = time.time() - start_time LOG.debug('create_export elapsed_time: %s', elapsed_time) @@ -890,7 +929,7 @@ class QnapISCSIDriver(san.SanISCSIDriver): return ( {'provider_location': provider_location, - 'provider_auth': None}) + 'provider_auth': provider_auth}) def initialize_connection(self, volume, connector): start_time = time.time() @@ -921,6 +960,11 @@ class QnapISCSIDriver(san.SanISCSIDriver): properties['target_lun'] = target_lun_id properties['volume_id'] = volume['id'] # used by xen currently + if self.configuration.use_chap_auth: + properties['auth_method'] = 'CHAP' + properties['auth_username'] = self.configuration.chap_username + properties['auth_password'] = self.configuration.chap_password + elapsed_time = time.time() - start_time LOG.debug('initialize_connection elapsed_time: %s', elapsed_time) @@ -1007,6 +1051,7 @@ class QnapISCSIDriver(san.SanISCSIDriver): elapsed_time = time.time() - start_time LOG.debug('terminate_connection elapsed_time : %s', elapsed_time) + self.api_executor.delete_target(target_index) def update_migrated_volume( self, context, volume, new_volume, original_volume_status): @@ -1217,7 +1262,8 @@ class QnapAPIExecutor(object): return res_details @_connection_checker - def create_lun(self, volume, pool_name, create_lun_name, reserve): + def create_lun(self, volume, pool_name, create_lun_name, reserve, + ssd_cache, compress, dedup): """Create lun.""" self.es_create_lun_lock.acquire() @@ -1236,7 +1282,9 @@ class QnapAPIExecutor(object): LUNName=create_lun_name, LUNPath=create_lun_name, poolID=pool_name, - lv_ifssd='no', + lv_ifssd='yes' if ssd_cache else 'no', + compression='1' if compress else '0', + dedup='sha256' if dedup else 'off', LUNCapacity=volume['size'], lv_threshold='80', sid=self.sid) @@ -1337,7 +1385,24 @@ class QnapAPIExecutor(object): return targetIndex @_connection_checker - def add_target_init(self, target_iqn, init_iqn): + def delete_target(self, target_index): + """Delete target on nas.""" + res_details = self._get_res_details( + '/cgi-bin/disk/iscsi_target_setting.cgi?', + func='remove_target', + targetIndex=target_index, + sid=self.sid) + root = ET.fromstring(res_details['data']) + if root.find('authPassed').text == '0': + raise exception.VolumeBackendAPIException( + data=_('Session id expired')) + if root.find('result').text != '0': + raise exception.VolumeBackendAPIException( + data=_('Delete target failed')) + + @_connection_checker + def add_target_init(self, target_iqn, init_iqn, use_chap_auth, + chap_username, chap_password): """Add target acl.""" res_details = self._get_res_details( '/cgi-bin/disk/iscsi_target_setting.cgi?', @@ -1345,9 +1410,9 @@ class QnapAPIExecutor(object): targetIQN=target_iqn, initiatorIQN=init_iqn, initiatorAlias=init_iqn, - bCHAPEnable='0', - CHAPUserName='', - CHAPPasswd='', + bCHAPEnable='1' if use_chap_auth else '0', + CHAPUserName=chap_username, + CHAPPasswd=chap_password, bMutualCHAPEnable='0', mutualCHAPUserName='', mutualCHAPPasswd='', @@ -1694,12 +1759,12 @@ class QnapAPIExecutor(object): return target @_connection_checker - def get_target_info_by_initiator(self, initiator_iqn, lun_slot_id): + def get_target_info_by_initiator(self, initiatorIQN): """Get target info by initiatorIQN.""" res_details = self._get_res_details( '/cgi-bin/disk/iscsi_portal_setting.cgi?', func='extra_get', - initiatorIQN=initiator_iqn, + initiatorIQN=initiatorIQN, sid=self.sid) root = ET.fromstring(res_details['data']) @@ -1709,22 +1774,11 @@ class QnapAPIExecutor(object): if root.find('result').text < '0': return "", "" - target_acl_tree = root.find('targetACL') - target_acl_list = target_acl_tree.findall('row') - for target_acl in target_acl_list: - target_iqn = target_acl.find('targetIQN').text - # If lun and the targetiqn in different controller, - # skip the targetiqn, in case lun in sca map to target of scb - LOG.debug('lun_slot_id: %s', lun_slot_id) - LOG.debug('target_iqn[-1]: %s', target_iqn[-1]) - if (lun_slot_id != ''): - if (lun_slot_id != target_iqn[-1]): - LOG.debug('skip the targetiqn') - continue - target_index = target_acl.find('targetIndex').text - if int(target_acl.find('targetStatus').text) >= 0: - return target_index, target_iqn - return "", "" + target = root.find('targetACL').find('row') + targetIndex = target.find('targetIndex').text + targetIQN = target.find('targetIQN').text + + return targetIndex, targetIQN class QnapAPIExecutorTS(QnapAPIExecutor): @@ -1734,7 +1788,8 @@ class QnapAPIExecutorTS(QnapAPIExecutor): lun_locks = {} @_connection_checker - def create_lun(self, volume, pool_name, create_lun_name, reserve): + def create_lun(self, volume, pool_name, create_lun_name, reserve, + ssd_cache, compress, dedup): """Create lun.""" self.create_lun_lock.acquire() @@ -1753,7 +1808,7 @@ class QnapAPIExecutorTS(QnapAPIExecutor): LUNName=create_lun_name, LUNPath=create_lun_name, poolID=pool_name, - lv_ifssd='no', + lv_ifssd='yes' if ssd_cache else 'no', LUNCapacity=volume['size'], lv_threshold='80', sid=self.sid) @@ -2006,13 +2061,30 @@ class QnapAPIExecutorTS(QnapAPIExecutor): targetIndex = root.find('result').text return targetIndex + @_connection_checker + def delete_target(self, target_index): + """Delete target on nas.""" + res_details = self._get_res_details( + '/cgi-bin/disk/iscsi_target_setting.cgi?', + func='remove_target', + targetIndex=target_index, + sid=self.sid) + root = ET.fromstring(res_details['data']) + if root.find('authPassed').text == '0': + raise exception.VolumeBackendAPIException( + data=_('Session id expired')) + if root.find('result').text != target_index: + raise exception.VolumeBackendAPIException( + data=_('Delete target failed')) + class QnapAPIExecutorTES(QnapAPIExecutor): """Makes QNAP API calls for TES NAS.""" tes_create_lun_lock = threading.Lock() @_connection_checker - def create_lun(self, volume, pool_name, create_lun_name, reserve): + def create_lun(self, volume, pool_name, create_lun_name, reserve, + ssd_cache, compress, dedup): """Create lun.""" self.tes_create_lun_lock.acquire() @@ -2031,7 +2103,9 @@ class QnapAPIExecutorTES(QnapAPIExecutor): LUNName=create_lun_name, LUNPath=create_lun_name, poolID=pool_name, - lv_ifssd='no', + lv_ifssd='yes' if ssd_cache else 'no', + compression='1' if compress else '0', + dedup='sha256' if dedup else 'off', sync='disabled', LUNCapacity=volume['size'], lv_threshold='80', diff --git a/releasenotes/notes/qnap-enhance-support-4ab5cbb110b3303b.yaml b/releasenotes/notes/qnap-enhance-support-4ab5cbb110b3303b.yaml new file mode 100644 index 00000000000..7a6e22ced0a --- /dev/null +++ b/releasenotes/notes/qnap-enhance-support-4ab5cbb110b3303b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add enhanced support to the QNAP Cinder driver, including + 'CHAP', 'Thin Provision', 'SSD Cache', 'Dedup' and 'Compression'.