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
This commit is contained in:
Pony Chou 2017-06-02 15:49:21 +08:00 committed by Chris Yang
parent 92d9f19f99
commit 08dcf03541
3 changed files with 256 additions and 135 deletions

View File

@ -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(

View File

@ -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 '<is> True' , '<is> 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'^<is>\s*(?P<value>True|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',

View File

@ -0,0 +1,5 @@
---
features:
- |
Add enhanced support to the QNAP Cinder driver, including
'CHAP', 'Thin Provision', 'SSD Cache', 'Dedup' and 'Compression'.