diff --git a/cinder/opts.py b/cinder/opts.py
index 20990687b09..31d9ed085cb 100644
--- a/cinder/opts.py
+++ b/cinder/opts.py
@@ -141,6 +141,7 @@ from cinder.volume.drivers import nimble as cinder_volume_drivers_nimble
from cinder.volume.drivers.prophetstor import options as \
cinder_volume_drivers_prophetstor_options
from cinder.volume.drivers import pure as cinder_volume_drivers_pure
+from cinder.volume.drivers import qnap as cinder_volume_drivers_qnap
from cinder.volume.drivers import quobyte as cinder_volume_drivers_quobyte
from cinder.volume.drivers import rbd as cinder_volume_drivers_rbd
from cinder.volume.drivers import remotefs as cinder_volume_drivers_remotefs
@@ -327,6 +328,7 @@ def list_opts():
cinder_volume_drivers_nimble.nimble_opts,
cinder_volume_drivers_prophetstor_options.DPL_OPTS,
cinder_volume_drivers_pure.PURE_OPTS,
+ cinder_volume_drivers_qnap.qnap_opts,
cinder_volume_drivers_quobyte.volume_opts,
cinder_volume_drivers_rbd.RBD_OPTS,
cinder_volume_drivers_remotefs.nas_opts,
diff --git a/cinder/tests/unit/volume/drivers/test_qnap.py b/cinder/tests/unit/volume/drivers/test_qnap.py
new file mode 100644
index 00000000000..430cfafe083
--- /dev/null
+++ b/cinder/tests/unit/volume/drivers/test_qnap.py
@@ -0,0 +1,5957 @@
+# Copyright (c) 2016 QNAP Systems, Inc.
+# 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
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import base64
+try:
+ import xml.etree.cElementTree as ET
+except ImportError:
+ import xml.etree.ElementTree as ET
+
+from ddt import data
+from ddt import ddt
+from ddt import unpack
+import mock
+from oslo_config import cfg
+from oslo_utils import units
+import six
+from six.moves import urllib
+
+from cinder import exception
+from cinder import test
+from cinder.volume.drivers import qnap
+CONF = cfg.CONF
+
+FAKE_LUNNAA = {'LUNNAA': 'fakeLunNaa'}
+FAKE_SNAPSHOT = {'snapshot_id': 'fakeSnapshotId'}
+
+FAKE_PASSWORD = 'qnapadmin'
+FAKE_PARMS = {}
+FAKE_PARMS['pwd'] = base64.b64encode(FAKE_PASSWORD.encode("utf-8"))
+FAKE_PARMS['serviceKey'] = 1
+FAKE_PARMS['user'] = 'admin'
+sanitized_params = {}
+
+for key in FAKE_PARMS:
+ value = FAKE_PARMS[key]
+ if value is not None:
+ sanitized_params[key] = six.text_type(value)
+global_sanitized_params = urllib.parse.urlencode(sanitized_params)
+header = {
+ 'charset': 'utf-8', 'Content-Type': 'application/x-www-form-urlencoded'}
+login_url = ('/cgi-bin/authLogin.cgi?')
+
+get_basic_info_url = ('/cgi-bin/authLogin.cgi')
+
+FAKE_RES_DETAIL_DATA_LOGIN = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_NO_AUTHPASSED = """
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TS = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_114 = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TES = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TES_433 = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT_TS = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT_TES = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_LUN_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_LUN_INFO_FAIL = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT_INFO = """
+
+
+
+
+ fakeSnapshotId
+ fakeSnapshotName
+
+
+
+ 0
+ """
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT_INFO_FAIL = """
+
+
+
+
+ fakeSnapshotId
+ fakeSnapshotName
+
+
+
+ -1
+ """
+
+FAKE_RES_DETAIL_DATA_MAPPED_LUN_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_ONE_LUN_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+FAKE_RES_DETAIL_DATA_MAPPED_ONE_LUN_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT_WITHOUT_SNAPSHOT = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT_WITHOUT_LUN = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SNAPSHOT_FAIL = """
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SPECIFIC_POOL_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_SPECIFIC_POOL_INFO_FAIL = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_ISCSI_PORTAL_INFO = """
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_ETHERNET_IP = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_CREATE_LUN = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_CREATE_LUN_FAIL = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_CREATE_LUN_BUSY = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_CREATE_TARGET = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_CREATE_TARGET_FAIL = """
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GETHOSTIDLISTBYINITIQN = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_GET_ALL_ISCSI_PORTAL_SETTING = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_TARGET_INFO = """
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_TARGET_INFO_FAIL = """
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_TARGET_INFO_BY_INITIATOR = """
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_DATA_TARGET_INFO_BY_INITIATOR_FAIL = """
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+FAKE_RES_DETAIL_GET_ALL_ISCSI_PORTAL_SETTING = {
+ 'data': FAKE_RES_DETAIL_DATA_GET_ALL_ISCSI_PORTAL_SETTING,
+ 'error': None,
+ 'http_status': 'fackStatus'
+}
+
+FAKE_RES_DETAIL_ISCSI_PORTAL_INFO = {
+ 'data': FAKE_RES_DETAIL_DATA_ISCSI_PORTAL_INFO,
+ 'error': None,
+ 'http_status': 'fackStatus'
+}
+
+
+def create_configuration(
+ username,
+ password,
+ management_url,
+ san_iscsi_ip,
+ poolname,
+ thin_provision=True):
+ """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.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
+ return configuration
+
+
+class QnapDriverBaseTestCase(test.TestCase):
+ """Base Class for the QnapDriver Tests."""
+
+ def setUp(self):
+ """Setup the Qnap Driver Base TestCase."""
+ super(QnapDriverBaseTestCase, self).setUp()
+ self.driver = None
+ self.mock_HTTPConnection = None
+
+
+class SnapshotClass(object):
+ """Snapshot Class."""
+
+ volume = {}
+ name = ''
+ volume_name = ''
+ volume_size = 0
+ metadata = {}
+
+ def __init__(self, volume, volume_size):
+ """Init."""
+ self.volume = volume
+ self.volume_size = volume_size
+ self.metadata = {'snapshot_id': 'fakeSnapshotId'}
+
+ def __getitem__(self, arg):
+ """Getitem."""
+ return {
+ 'display_name': 'fakeSnapshotDisplayName',
+ 'id': 'fakeSnapshotId',
+ 'volume_size': self.volume_size,
+ 'metadata': self.metadata
+ }[arg]
+
+ def __contains__(self, arg):
+ """Getitem."""
+ return {
+ 'display_name': 'fakeSnapshotDisplayName',
+ 'id': 'fakeSnapshotId',
+ 'volume_size': self.volume_size,
+ 'metadata': self.metadata
+ }[arg]
+
+
+class VolumeClass(object):
+ """Volume Class."""
+
+ display_name = ''
+ id = ''
+ size = 0
+ name = ''
+ volume_metadata = []
+
+ def __init__(self, display_name, id, size, name):
+ """Init."""
+ self.display_name = display_name
+ self.id = id
+ self.size = size
+ self.name = name
+ self.volume_metadata = [{'key': 'LUNNAA', 'value': 'fakeLunNaa'},
+ {'key': 'LUNIndex', 'value': 'fakeLunIndex'}]
+ self.metadata = {'LUNNAA': 'fakeLunNaa',
+ 'LUNIndex': 'fakeLunIndex'}
+ self.provider_location = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': '3260',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'
+ }
+
+ def __getitem__(self, arg):
+ """Getitem."""
+ return {
+ 'display_name': self.display_name,
+ 'size': self.size,
+ 'id': self.id,
+ 'name': self.name,
+ 'volume_metadata': self.volume_metadata,
+ 'metadata': self.metadata,
+ 'provider_location': self.provider_location
+ }[arg]
+
+ def __contains__(self, arg):
+ """Getitem."""
+ return {
+ 'display_name': self.display_name,
+ 'size': self.size,
+ 'id': self.id,
+ 'name': self.name,
+ 'volume_metadata': self.volume_metadata,
+ 'metadata': self.metadata,
+ 'provider_location': self.provider_location
+ }[arg]
+
+ def __setitem__(self, key, value):
+ """Setitem."""
+ if key == 'display_name':
+ self.display_name = value
+
+
+class HostClass(object):
+ """Host Class."""
+
+ def __init__(self, host):
+ """Init."""
+ self.host = host
+
+ def __getitem__(self, arg):
+ """Getitem."""
+ return {
+ 'host': 'fakeHost',
+ }[arg]
+
+
+class FakeLoginResponse(object):
+ """Fake login response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_LOGIN
+
+
+class FakeNoAuthPassedResponse(object):
+ """Fake no auth passed response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_NO_AUTHPASSED
+
+
+class FakeGetBasicInfoResponse(object):
+ """Fake GetBasicInfo response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO
+
+
+class FakeGetBasicInfo114Response(object):
+ """Fake GetBasicInfo114 response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_114
+
+
+class FakeGetBasicInfoTsResponse(object):
+ """Fake GetBasicInfoTs response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TS
+
+
+class FakeGetBasicInfoTesResponse(object):
+ """Fake GetBasicInfoTes response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TES
+
+
+class FakeGetBasicInfoTes433Response(object):
+ """Fake GetBasicInfoTes response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_TES_433
+
+
+class FakeGetBasicInfoUnsupportResponse(object):
+ """Fake GetBasicInfoUnsupport response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT
+
+
+class FakeGetBasicInfoUnsupportTsResponse(object):
+ """Fake GetBasicInfoUnsupportTs response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT_TS
+
+
+class FakeGetBasicInfoUnsupportTesResponse(object):
+ """Fake GetBasicInfoUnsupportTes response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GETBASIC_INFO_UNSUPPORT_TES
+
+
+class FakeLunInfoResponse(object):
+ """Fake lun info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_LUN_INFO
+
+
+class FakeLunInfoFailResponse(object):
+ """Fake lun info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_LUN_INFO_FAIL
+
+
+class FakeSnapshotInfoResponse(object):
+ """Fake snapshot info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT_INFO
+
+
+class FakeSnapshotInfoFailResponse(object):
+ """Fake snapshot info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT_INFO_FAIL
+
+
+class FakeOneLunInfoResponse(object):
+ """Fake one lun info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_ONE_LUN_INFO
+
+
+class FakeMappedOneLunInfoResponse(object):
+ """Fake one lun info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_MAPPED_ONE_LUN_INFO
+
+
+class FakePoolInfoResponse(object):
+ """Fake pool info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SPECIFIC_POOL_INFO
+
+
+class FakePoolInfoFailResponse(object):
+ """Fake pool info response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SPECIFIC_POOL_INFO_FAIL
+
+
+class FakeCreateLunResponse(object):
+ """Fake create lun response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_CREATE_LUN
+
+
+class FakeCreateLunFailResponse(object):
+ """Fake create lun response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_CREATE_LUN_FAIL
+
+
+class FakeCreateLunBusyResponse(object):
+ """Fake create lun response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_CREATE_LUN_BUSY
+
+
+class FakeCreatTargetResponse(object):
+ """Fake create target response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_CREATE_TARGET
+
+
+class FakeCreatTargetFailResponse(object):
+ """Fake create target response."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_CREATE_TARGET_FAIL
+
+
+class FakeGetIscsiPortalInfoResponse(object):
+ """Fake get iscsi portal inforesponse."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_ISCSI_PORTAL_INFO
+
+ def __repr__(self):
+ """Repr."""
+ return six.StringIO(FAKE_RES_DETAIL_DATA_ISCSI_PORTAL_INFO)
+
+
+class FakeCreateSnapshotResponse(object):
+ """Fake Create snapshot inforesponse."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT
+
+
+class FakeCreateSnapshotWithoutSnapshotResponse(object):
+ """Fake Create snapshot inforesponse."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT_WITHOUT_SNAPSHOT
+
+
+class FakeCreateSnapshotWithoutLunResponse(object):
+ """Fake Create snapshot inforesponse."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT_WITHOUT_LUN
+
+
+class FakeCreateSnapshotFailResponse(object):
+ """Fake Create snapshot inforesponse."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_SNAPSHOT_FAIL
+
+
+class FakeGetAllIscsiPortalSetting(object):
+ """Fake get all iSCSI portal setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_GET_ALL_ISCSI_PORTAL_SETTING
+
+
+class FakeGetAllEthernetIp(object):
+ """Fake get all ethernet ip setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_ETHERNET_IP
+
+
+class FakeTargetInfo(object):
+ """Fake target info setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_TARGET_INFO
+
+
+class FakeTargetInfoFail(object):
+ """Fake target info setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_TARGET_INFO_FAIL
+
+
+class FakeTargetInfoByInitiator(object):
+ """Fake target info setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_TARGET_INFO_BY_INITIATOR
+
+
+class FakeTargetInfoByInitiatorFail(object):
+ """Fake target info setting."""
+
+ status = 'fackStatus'
+
+ def read(self):
+ """Mock response.read."""
+ return FAKE_RES_DETAIL_DATA_TARGET_INFO_BY_INITIATOR_FAIL
+
+
+@ddt
+class QnapDriverLoginTestCase(QnapDriverBaseTestCase):
+ """Tests do_setup api."""
+
+ def setUp(self):
+ """Setup the Qnap Share Driver login TestCase."""
+ super(QnapDriverLoginTestCase, self).setUp()
+ self.mock_object(six.moves.http_client, 'HTTPConnection')
+ self.mock_object(six.moves.http_client, 'HTTPSConnection')
+
+ @data({'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False,
+ 'get_basic_info_response': FakeGetBasicInfoResponse()},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True,
+ 'get_basic_info_response': FakeGetBasicInfoResponse()},
+ {'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False,
+ 'get_basic_info_response': FakeGetBasicInfoTsResponse()},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True,
+ 'get_basic_info_response': FakeGetBasicInfoTsResponse()},
+ {'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False,
+ 'get_basic_info_response': FakeGetBasicInfoTesResponse()},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True,
+ 'get_basic_info_response': FakeGetBasicInfoTesResponse()},
+ {'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False,
+ 'get_basic_info_response': FakeGetBasicInfoTes433Response()},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True,
+ 'get_basic_info_response': FakeGetBasicInfoTes433Response()}
+ )
+ @unpack
+ def test_do_setup_positive(self, mng_url, port,
+ ssl, get_basic_info_response):
+ """Test do_setup with http://1.2.3.4:8080."""
+ fake_login_response = FakeLoginResponse()
+ fake_get_basic_info_response = get_basic_info_response
+ if ssl:
+ mock_connection = six.moves.http_client.HTTPSConnection
+ else:
+ mock_connection = six.moves.http_client.HTTPConnection
+ mock_connection.return_value.getresponse.side_effect = ([
+ fake_login_response,
+ fake_get_basic_info_response,
+ fake_login_response])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin', 'qnapadmin', mng_url,
+ '1.2.3.4', 'Storage Pool 1', True))
+ self.driver.do_setup('context')
+
+ self.assertEqual('fakeSid', self.driver.api_executor.sid)
+ self.assertEqual('admin', self.driver.api_executor.username)
+ self.assertEqual('qnapadmin', self.driver.api_executor.password)
+ self.assertEqual('1.2.3.4', self.driver.api_executor.ip)
+ self.assertEqual(port, self.driver.api_executor.port)
+ self.assertEqual(ssl, self.driver.api_executor.ssl)
+
+ @data({'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True})
+ @unpack
+ def test_do_setup_negative_with_configuration_not_set(self, mng_url,
+ port, ssl):
+ """Test do_setup with http://1.2.3.4:8080."""
+ fake_login_response = FakeLoginResponse()
+ fake_get_basic_info_response = FakeGetBasicInfoResponse()
+ if ssl:
+ mock_connection = six.moves.http_client.HTTPSConnection
+ else:
+ mock_connection = six.moves.http_client.HTTPConnection
+ mock_connection.return_value.getresponse.side_effect = ([
+ fake_login_response,
+ fake_get_basic_info_response,
+ fake_login_response])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin', 'qnapadmin', mng_url,
+ '1.2.3.4', 'Storage Pool 1', True))
+ del self.driver.configuration.qnap_management_url
+ self.assertRaises(exception.InvalidInput,
+ self.driver.do_setup, 'context')
+
+ @data({'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False,
+ 'get_basic_info_response': FakeGetBasicInfoUnsupportTsResponse()},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True,
+ 'get_basic_info_response': FakeGetBasicInfoUnsupportTsResponse()})
+ @unpack
+ def test_do_setup_negative_with_unsupport_nas(self, mng_url, port, ssl,
+ get_basic_info_response):
+ """Test do_setup with http://1.2.3.4:8080."""
+ fake_login_response = FakeLoginResponse()
+ fake_get_basic_info_response = get_basic_info_response
+ if ssl:
+ mock_connection = six.moves.http_client.HTTPSConnection
+ else:
+ mock_connection = six.moves.http_client.HTTPConnection
+ mock_connection.return_value.getresponse.side_effect = ([
+ fake_login_response,
+ fake_get_basic_info_response,
+ fake_login_response])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin', 'qnapadmin', mng_url,
+ '1.2.3.4', 'Storage Pool 1', True))
+ self.assertRaises(exception.VolumeDriverException,
+ self.driver.do_setup, 'context')
+
+ @data({'mng_url': 'http://1.2.3.4:8080', 'port': '8080', 'ssl': False},
+ {'mng_url': 'https://1.2.3.4:443', 'port': '443', 'ssl': True})
+ @unpack
+ def test_check_for_setup_error(self, mng_url, port, ssl):
+ """Test check_for_setup_error."""
+ fake_login_response = FakeLoginResponse()
+ fake_get_basic_info_response = FakeGetBasicInfoResponse()
+ if ssl:
+ mock_connection = six.moves.http_client.HTTPSConnection
+ else:
+ mock_connection = six.moves.http_client.HTTPConnection
+ mock_connection.return_value.getresponse.side_effect = ([
+ fake_login_response,
+ fake_get_basic_info_response,
+ fake_login_response])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin', 'qnapadmin', mng_url,
+ '1.2.3.4', 'Storage Pool 1', True))
+ self.driver.do_setup('context')
+ self.driver.check_for_setup_error()
+
+ self.assertEqual('fakeSid', self.driver.api_executor.sid)
+ self.assertEqual('admin', self.driver.api_executor.username)
+ self.assertEqual('qnapadmin', self.driver.api_executor.password)
+ self.assertEqual('1.2.3.4', self.driver.api_executor.ip)
+ self.assertEqual(port, self.driver.api_executor.port)
+ self.assertEqual(ssl, self.driver.api_executor.ssl)
+
+
+class QnapDriverVolumeTestCase(QnapDriverBaseTestCase):
+ """Tests volume related api's."""
+
+ def get_lun_info_return_value(self):
+ """Return the lun form get_lun_info method."""
+ root = ET.fromstring(FAKE_RES_DETAIL_DATA_LUN_INFO)
+
+ lun_list = root.find('iSCSILUNList')
+ lun_info_tree = lun_list.findall('LUNInfo')
+ for lun in lun_info_tree:
+ return lun
+
+ def get_mapped_lun_info_return_value(self):
+ """Return the lun form get_lun_info method."""
+ root = ET.fromstring(FAKE_RES_DETAIL_DATA_MAPPED_LUN_INFO)
+
+ lun_list = root.find('iSCSILUNList')
+ lun_info_tree = lun_list.findall('LUNInfo')
+ for lun in lun_info_tree:
+ return lun
+
+ def get_one_lun_info_return_value(self):
+ """Return the lun form get_one_lun_info method."""
+ fake_one_lun_info_response = FakeOneLunInfoResponse()
+ ret = {'data': fake_one_lun_info_response.read(),
+ 'error': None,
+ 'http_status': fake_one_lun_info_response.status}
+ return ret
+
+ def get_mapped_one_lun_info_return_value(self):
+ """Return the lun form get_one_lun_info method."""
+ fake_mapped_one_lun_info_response = FakeMappedOneLunInfoResponse()
+ ret = {'data': fake_mapped_one_lun_info_response.read(),
+ 'error': None,
+ 'http_status': fake_mapped_one_lun_info_response.status}
+ return ret
+
+ def get_snapshot_info_return_value(self):
+ """Return the lun form get_lun_info method."""
+ root = ET.fromstring(FAKE_RES_DETAIL_DATA_SNAPSHOT)
+
+ snapshot_list = root.find('SnapshotList')
+ snapshot_info_tree = snapshot_list.findall('row')
+ for snapshot in snapshot_info_tree:
+ return snapshot
+
+ def get_target_info_return_value(self):
+ """Return the target form get_target_info method."""
+ root = ET.fromstring(FAKE_RES_DETAIL_DATA_TARGET_INFO)
+
+ target_info = root.find('targetInfo/row')
+ return target_info
+
+ @mock.patch.object(qnap.QnapISCSIDriver, '_get_volume_metadata')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_volume_positive(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_get_volume_metadata):
+ """Test create_volume with fake_volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.side_effect = [
+ None,
+ self.get_lun_info_return_value()]
+ mock_gen_random_name.return_value = 'fakeLun'
+ mock_api_return.create_lun.return_value = 'fakeIndex'
+ mock_get_volume_metadata.return_value = {}
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.create_volume(fake_volume)
+
+ mock_api_return.create_lun.assert_called_once_with(
+ fake_volume,
+ self.driver.configuration.qnap_poolname,
+ 'fakeLun',
+ True)
+
+ expected_call_list = [
+ mock.call(LUNName='fakeLun'),
+ mock.call(LUNIndex='fakeIndex')]
+ self.assertEqual(
+ expected_call_list,
+ mock_api_return.get_lun_info.call_args_list)
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_delete_volume_positive_without_mapped_lun(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test delete_volume with fake_volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_one_lun_info.return_value = (
+ self.get_one_lun_info_return_value())
+ mock_api_return.delete_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.delete_volume(fake_volume)
+
+ mock_api_return.delete_lun.assert_called_once_with(
+ 'fakeLunIndex')
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_delete_volume_positive_with_mapped_lun(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test delete_volume with fake_volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_one_lun_info.return_value = (
+ self.get_mapped_one_lun_info_return_value())
+ mock_api_return.delete_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.delete_volume(fake_volume)
+
+ mock_api_return.delete_lun.assert_called_once_with(
+ 'fakeLunIndex')
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_delete_volume_negative_without_lun_naa(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test delete_volume with fake_volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_lun_naa_from_volume_metadata.return_value = ''
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_one_lun_info.return_value = (
+ self.get_one_lun_info_return_value())
+ mock_api_return.delete_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.delete_volume(fake_volume)
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_create_snapshot_name')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_get_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_cloned_volume_volume_size_less_src_verf(
+ self,
+ mock_api_executor,
+ mock_get_volume_metadata,
+ mock_gen_random_name,
+ mock_create_snapshot_name,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test create cloned volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 90, 'fakeLunName')
+ fake_src_vref = VolumeClass(
+ 'fakeSrcVrefName', 'fakeId', 100, 'fakeSrcVref')
+
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_volume_metadata.return_value = {}
+ mock_api_executor.return_value.get_lun_info.side_effect = [
+ self.get_lun_info_return_value(),
+ None,
+ self.get_lun_info_return_value()]
+ mock_gen_random_name.return_value = 'fakeLun'
+ mock_create_snapshot_name.return_value = 'fakeSnapshot'
+ mock_api_executor.return_value.get_snapshot_info.return_value = (
+ self.get_snapshot_info_return_value())
+ mock_api_executor.return_value.create_snapshot_api.return_value = (
+ 'fakeSnapshotId')
+ mock_api_executor.return_value.clone_snapshot.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.create_cloned_volume(fake_volume, fake_src_vref)
+
+ expected_call_list = [
+ mock.call(LUNNAA='fakeLunNaa'),
+ mock.call(LUNName='fakeLun'),
+ mock.call(LUNName='fakeLun')]
+ self.assertEqual(
+ expected_call_list,
+ mock_api_executor.return_value.get_lun_info.call_args_list)
+ expected_call_list = [
+ mock.call(lun_index='fakeLunIndex', snapshot_name='fakeSnapshot')]
+ self.assertEqual(
+ expected_call_list,
+ mock_api_executor.return_value.get_snapshot_info.call_args_list)
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.create_snapshot_api.assert_called_once_with(
+ 'fakeLunIndex', 'fakeSnapshot')
+ mock_api_return.clone_snapshot.assert_called_once_with(
+ 'fakeSnapshotId', 'fakeLun')
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_extend_lun')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_get_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_cloned_volume_volume_size_morethan_src_verf(
+ self,
+ mock_api_executor,
+ mock_get_volume_metadata,
+ mock_gen_random_name,
+ mock_extend_lun,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test create cloned volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_src_vref = VolumeClass(
+ 'fakeSrcVrefName', 'fakeId', 90, 'fakeSrcVref')
+
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_volume_metadata.return_value = FAKE_LUNNAA
+ mock_api_executor.return_value.get_lun_info.side_effect = [
+ self.get_lun_info_return_value(),
+ None,
+ self.get_lun_info_return_value()]
+ mock_gen_random_name.side_effect = ['fakeSnapshot', 'fakeLun']
+ mock_api_executor.return_value.get_snapshot_info.side_effect = [
+ None, self.get_snapshot_info_return_value()]
+ mock_api_executor.return_value.create_snapshot_api.return_value = (
+ 'fakeSnapshotId')
+ mock_api_executor.return_value.clone_snapshot.return_value = None
+ mock_extend_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ 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):
+ """Test create snapshot."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ snapshot = SnapshotClass(fake_volume, 100)
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_executor.return_value.get_lun_info.return_value = (
+ self.get_lun_info_return_value())
+ mock_create_snapshot_name.return_value = 'fakeSnapshot'
+ mock_api_executor.return_value.get_snapshot_info.side_effect = [
+ None, self.get_snapshot_info_return_value()]
+ mock_api_executor.return_value.create_snapshot_api.return_value = (
+ 'fakeSnapshotId')
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.create_snapshot(snapshot)
+
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.assert_called_once_with(
+ LUNNAA='fakeLunNaa')
+ expected_call_list = [
+ mock.call(lun_index='fakeLunIndex', snapshot_name='fakeSnapshot'),
+ mock.call(lun_index='fakeLunIndex', snapshot_name='fakeSnapshot')]
+ self.assertEqual(
+ expected_call_list,
+ mock_api_return.get_snapshot_info.call_args_list)
+ mock_api_return.create_snapshot_api.assert_called_once_with(
+ 'fakeLunIndex', 'fakeSnapshot')
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_delete_snapshot_positive(
+ self,
+ mock_api_executor):
+ """Test delete snapshot."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_snapshot = SnapshotClass(fake_volume, 100)
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.delete_snapshot_api.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.delete_snapshot(fake_snapshot)
+
+ mock_api_return.delete_snapshot_api.assert_called_once_with(
+ 'fakeSnapshotId')
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_delete_snapshot_negative(
+ self,
+ mock_api_executor):
+ """Test delete snapshot."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_snapshot = SnapshotClass(fake_volume, 100)
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.delete_snapshot_api.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ fake_snapshot.metadata.pop('snapshot_id', None)
+ self.driver.delete_snapshot(fake_snapshot)
+
+ @mock.patch.object(qnap.QnapISCSIDriver, '_get_volume_metadata')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_extend_lun')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_volume_from_snapshot_positive_volsize_more_snapshotvolsize(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_extend_lun,
+ mock_get_volume_metadata):
+ """Test create volume from snapshot positive."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_snapshot = SnapshotClass(fake_volume, 90)
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_gen_random_name.return_value = 'fakeLun'
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.side_effect = [
+ None,
+ self.get_lun_info_return_value()]
+ mock_api_return.clone_snapshot.return_value = None
+
+ mock_api_return.create_snapshot_api.return_value = (
+ 'fakeSnapshotId')
+ mock_extend_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.create_volume_from_snapshot(fake_volume, fake_snapshot)
+
+ expected_call_list = [
+ mock.call(LUNName='fakeLun'),
+ mock.call(LUNName='fakeLun')]
+ self.assertEqual(
+ expected_call_list,
+ mock_api_return.get_lun_info.call_args_list)
+
+ mock_api_return.clone_snapshot.assert_called_once_with(
+ 'fakeSnapshotId', 'fakeLun')
+ mock_extend_lun.assert_called_once_with(fake_volume, 'fakeLunNaa')
+
+ @mock.patch.object(qnap.QnapISCSIDriver, '_get_volume_metadata')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_extend_lun')
+ @mock.patch.object(qnap.QnapISCSIDriver, '_gen_random_name')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_volume_from_snapshot_negative(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_extend_lun,
+ mock_get_volume_metadata):
+ """Test create volume from snapshot positive."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_snapshot = SnapshotClass(fake_volume, 90)
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_gen_random_name.return_value = 'fakeLun'
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.side_effect = [
+ None,
+ self.get_lun_info_return_value()]
+ mock_api_return.clone_snapshot.return_value = None
+
+ mock_api_return.create_snapshot_api.return_value = (
+ 'fakeSnapshotId')
+ mock_extend_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ fake_snapshot.metadata.pop('snapshot_id', None)
+ self.assertRaises(exception.VolumeDriverException,
+ self.driver.create_volume_from_snapshot,
+ fake_volume, fake_snapshot)
+
+ def get_specific_poolinfo_return_value(self):
+ """Get specific pool info."""
+ root = ET.fromstring(FAKE_RES_DETAIL_DATA_SPECIFIC_POOL_INFO)
+ pool_list = root.find('Pool_Index')
+ pool_info_tree = pool_list.findall('row')
+ for pool in pool_info_tree:
+ return pool
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_get_volume_stats(
+ self,
+ mock_api_executor):
+ """Get volume stats."""
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_specific_poolinfo.return_value = (
+ self.get_specific_poolinfo_return_value())
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.VERSION = 'fakeVersion'
+
+ expected_res = {'volume_backend_name': 'QNAP',
+ 'vendor_name': 'QNAP',
+ 'driver_version': 'fakeVersion',
+ 'storage_protocol': 'iscsi'}
+ single_pool = dict(
+ pool_name=self.driver.configuration.qnap_poolname,
+ total_capacity_gb=930213412209 / units.Gi,
+ free_capacity_gb=928732941681 / units.Gi,
+ provisioned_capacity_gb=1480470528 / units.Gi,
+ reserved_percentage=self.driver.configuration.reserved_percentage,
+ QoS_support=False)
+ expected_res['pools'] = [single_pool]
+
+ self.assertEqual(
+ expected_res,
+ self.driver.get_volume_stats(refresh=True))
+ mock_api_return.get_specific_poolinfo.assert_called_once_with(
+ self.driver.configuration.qnap_poolname)
+
+ @mock.patch.object(qnap.QnapISCSIDriver, '_extend_lun')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_extend_volume(
+ self,
+ mock_api_executor,
+ mock_extend_lun):
+ """Test extend volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.extend_volume(fake_volume, 'fakeSize')
+
+ mock_extend_lun.assert_called_once_with(fake_volume, '')
+
+ @mock.patch.object(
+ qnap.QnapISCSIDriver, '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_extend_lun(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test _extend_lun method."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.return_value = (
+ self.get_lun_info_return_value())
+ mock_api_return.edit_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver._extend_lun(fake_volume, '')
+
+ mock_api_return.get_lun_info.assert_called_once_with(
+ LUNNAA='fakeLunNaa')
+ expect_lun = {
+ 'LUNName': 'fakeLunName',
+ 'LUNCapacity': fake_volume['size'],
+ 'LUNIndex': 'fakeLunIndex',
+ 'LUNThinAllocate': 'fakeLunThinAllocate',
+ 'LUNPath': 'fakeLunPath',
+ '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')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_export_positive_without_multipath(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_get_lun_naa_from_volume_metadata,
+ mock_greenthread_sleep):
+ """Test create export."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.return_value = (
+ self.get_lun_info_return_value())
+ mock_api_return.get_iscsi_portal_info.return_value = (
+ 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.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
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': 'fakeServicePort',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'}
+ expected_return = {
+ 'provider_location': expected_properties, 'provider_auth': None}
+
+ 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')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_export_positive_with_multipath(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_get_lun_naa_from_volume_metadata,
+ mock_greenthread_sleep):
+ """Test create export."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn', 'multipath': True}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.return_value = (
+ self.get_lun_info_return_value())
+ mock_api_return.get_iscsi_portal_info.return_value = (
+ 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.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
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': 'fakeServicePort',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'}
+ expected_return = {
+ 'provider_location': expected_properties, 'provider_auth': None}
+
+ 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')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_export_114(
+ self,
+ mock_api_executor,
+ mock_gen_random_name,
+ mock_get_lun_naa_from_volume_metadata,
+ mock_greenthread_sleep):
+ """Test create export."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.4')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_one_lun_info.return_value = (
+ self.get_mapped_one_lun_info_return_value())
+ mock_api_return.get_iscsi_portal_info.return_value = (
+ 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())
+ mock_api_return.add_target_init.return_value = None
+ mock_api_return.map_lun.return_value = None
+ mock_api_return.get_ethernet_ip.return_value = ['1.2.3.4'], None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': 'fakeServicePort',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'}
+ expected_return = {
+ 'provider_location': expected_properties, 'provider_auth': None}
+
+ 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')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutorTS')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_export_positive_ts(
+ self,
+ mock_api_executor,
+ mock_api_executor_ts,
+ mock_gen_random_name,
+ mock_get_lun_naa_from_volume_metadata,
+ mock_greenthread_sleep):
+ """Test create export."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'TS-870U-RP ', 'TS-870U-RP ', '4.3.0')
+ mock_api_executor_ts.return_value.get_basic_info.return_value = (
+ 'TS-870U-RP ', 'TS-870U-RP ', '4.3.0')
+ mock_api_return = mock_api_executor_ts.return_value
+ mock_api_return.get_one_lun_info.return_value = (
+ self.get_mapped_one_lun_info_return_value())
+ mock_api_return.get_iscsi_portal_info.return_value = (
+ 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.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
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ expected_properties = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': 'fakeServicePort',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'}
+ expected_return = {
+ 'provider_location': expected_properties, 'provider_auth': None}
+
+ self.assertEqual(expected_return, self.driver.create_export(
+ 'context', fake_volume, fake_connector))
+
+ @mock.patch.object(qnap.QnapISCSIDriver,
+ '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_create_export_negative_without_lun_naa(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test create export."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ # mock_api_return = mock_api_executor.return_value
+ mock_get_lun_naa_from_volume_metadata.return_value = ''
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeDriverException,
+ self.driver.create_export,
+ 'context', fake_volume, fake_connector)
+
+ @mock.patch.object(qnap.QnapISCSIDriver,
+ '_get_lun_naa_from_volume_metadata')
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_initialize_connection_with_target_exist(
+ self,
+ mock_api_executor,
+ mock_get_lun_naa_from_volume_metadata):
+ """Test initialize connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_iscsi_portal_info.return_value = (
+ FAKE_RES_DETAIL_ISCSI_PORTAL_INFO)
+ mock_get_lun_naa_from_volume_metadata.return_value = 'fakeLunNaa'
+ mock_api_return.get_lun_info.side_effect = [
+ self.get_lun_info_return_value(),
+ self.get_lun_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
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.iscsi_port = 'fakeServicePort'
+ self.driver.do_setup('context')
+
+ expected_properties = {
+ 'target_discovered': False,
+ 'target_portal': '1.2.3.4:fakeServicePort',
+ 'target_iqn': 'fakeTargetIqn',
+ 'target_lun': 1,
+ 'volume_id': fake_volume['id']}
+ expected_return = {
+ 'driver_volume_type': 'iscsi', 'data': expected_properties}
+
+ self.assertEqual(expected_return, self.driver.initialize_connection(
+ fake_volume, fake_connector))
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_initialize_connection_with_target_exist_negative_no_provider(
+ self,
+ mock_api_executor):
+ """Test initialize connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ fake_volume.provider_location = None
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.initialize_connection,
+ fake_volume, fake_connector)
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_initialize_connection_with_target_exist_negative_wrong_provider_1(
+ self,
+ mock_api_executor):
+ """Test initialize connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ fake_volume.provider_location = (
+ '%(host)s:%(port)s,1%(name)s%(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': '3260',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'
+ })
+ self.assertRaises(exception.InvalidInput,
+ self.driver.initialize_connection,
+ fake_volume, fake_connector)
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_initialize_connection_with_target_exist_negative_wrong_provider_2(
+ self, mock_api_executor):
+ """Test initialize connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiatorIqn'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ fake_volume.provider_location = (
+ '%(host)s:%(port)s1 %(name)s %(tgt_lun)s' % {
+ 'host': '1.2.3.4',
+ 'port': '3260',
+ 'name': 'fakeTargetIqn',
+ 'tgt_lun': '1'
+ })
+ self.assertRaises(exception.InvalidInput,
+ self.driver.initialize_connection,
+ fake_volume, fake_connector)
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_terminate_connection_positive_with_lun_mapped(
+ self,
+ mock_api_executor):
+ """Test terminate connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiator'}
+
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.return_value = (
+ self.get_mapped_lun_info_return_value())
+ mock_api_return.unmap_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.terminate_connection(fake_volume, fake_connector)
+
+ mock_api_return.get_lun_info.assert_called_once_with(
+ LUNIndex='fakeLunIndex')
+ mock_api_return.unmap_lun.assert_called_once_with(
+ 'fakeLunIndex', '9')
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_terminate_connection_positive_without_lun_mapped(
+ self,
+ mock_api_executor):
+ """Test terminate connection."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_connector = {'initiator': 'fakeInitiator'}
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ mock_api_return = mock_api_executor.return_value
+ mock_api_return.get_lun_info.return_value = (
+ self.get_lun_info_return_value())
+ mock_api_return.unmap_lun.return_value = None
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.terminate_connection(fake_volume, fake_connector)
+
+ mock_api_return.get_lun_info.assert_called_once_with(
+ LUNIndex='fakeLunIndex')
+
+ @mock.patch('cinder.volume.drivers.qnap.QnapAPIExecutor')
+ def test_update_migrated_volume(
+ self,
+ mock_api_executor):
+ """Test update migrated volume."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ fake_new_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+ mock_api_executor.return_value.get_basic_info.return_value = (
+ 'ES1640dc ', 'ES1640dc ', '1.1.3')
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.update_migrated_volume('context',
+ fake_volume, fake_new_volume,
+ 'fakeOriginalVolumeStatus')
+
+
+class QnapAPIExecutorEsTestCase(QnapDriverBaseTestCase):
+ """Tests QnapAPIExecutor."""
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_with_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', True))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '1'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_without_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', False))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '0'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_lun('fakeLunIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['run_background'] = '1'
+ fake_params['ha_sync'] = '1'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ delete_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', delete_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_negative(self, mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_lun,
+ 'fakeLunIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_lun,
+ 'fakeLunIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_positive_with_busy_result(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunBusyResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_lun('fakeLunIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['run_background'] = '1'
+ fake_params['ha_sync'] = '1'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ delete_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', delete_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_specific_poolinfo(
+ self,
+ mock_http_connection):
+ """Test get specific pool info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakePoolInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_specific_poolinfo('fakePoolId')
+
+ fake_params = {}
+ fake_params['store'] = 'poolInfo'
+ fake_params['func'] = 'extra_get'
+ fake_params['poolID'] = 'fakePoolId'
+ fake_params['Pool_Info'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_specific_poolinfo_url = (
+ '/cgi-bin/disk/disk_manage.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', get_specific_poolinfo_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_specific_poolinfo_negative(
+ self,
+ mock_http_connection):
+ """Test get specific pool info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_specific_poolinfo,
+ 'Pool1')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_specific_poolinfo_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get specific pool info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakePoolInfoFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_specific_poolinfo,
+ 'Pool1')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreatTargetResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.create_target('fakeTargetName', 'sca')
+ fake_params = {}
+ fake_params['func'] = 'add_target'
+ fake_params['targetName'] = 'fakeTargetName'
+ fake_params['targetAlias'] = 'fakeTargetName'
+ fake_params['bTargetDataDigest'] = '0'
+ fake_params['bTargetHeaderDigest'] = '0'
+ fake_params['bTargetClusterEnable'] = '1'
+ fake_params['controller_name'] = 'sca'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_target_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_target_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target_negative(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_target,
+ 'fakeTargetName', 'sca')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreatTargetFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_target,
+ 'fakeTargetName', 'sca')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_add_target_init(self, mock_http_connection):
+ """Test add target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.add_target_init(
+ 'fakeTargetIqn', 'fakeInitiatorIqn')
+
+ fake_params = {}
+ fake_params['func'] = 'add_init'
+ fake_params['targetIQN'] = 'fakeTargetIqn'
+ fake_params['initiatorIQN'] = 'fakeInitiatorIqn'
+ fake_params['initiatorAlias'] = 'fakeInitiatorIqn'
+ fake_params['bCHAPEnable'] = '0'
+ fake_params['CHAPUserName'] = ''
+ fake_params['CHAPPasswd'] = ''
+ fake_params['bMutualCHAPEnable'] = '0'
+ fake_params['mutualCHAPUserName'] = ''
+ fake_params['mutualCHAPPasswd'] = ''
+ fake_params['ha_sync'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ add_target_init_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', add_target_init_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_add_target_init_negative(
+ self,
+ mock_http_connection):
+ """Test add target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.add_target_init,
+ 'fakeTargetIqn', 'fakeInitiatorIqn')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_add_target_init_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test add target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.add_target_init,
+ 'fakeTargetIqn', 'fakeInitiatorIqn')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_remove_target_init(
+ self,
+ mock_http_connection):
+ """Test add target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.remove_target_init(
+ 'fakeTargetIqn', 'fakeInitiatorIqn')
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.map_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ map_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', map_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun_negative(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.map_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.map_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun(
+ self,
+ mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.disable_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'edit_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['LUNEnable'] = 0
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ unmap_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', unmap_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun_negative(self, mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.disable_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.disable_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.unmap_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ unmap_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', unmap_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun_negative(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.unmap_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.unmap_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_iscsi_portal_info(
+ self,
+ mock_http_connection):
+ """Test get iscsi portal info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_iscsi_portal_info()
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['iSCSI_portal'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_iscsi_portal_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', get_iscsi_portal_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_iscsi_portal_info_negative(
+ self,
+ mock_http_connection):
+ """Test get iscsi portal info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_iscsi_portal_info)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_lun_info(self, mock_http_connection):
+ """Test get lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_lun_info()
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['lunList'] = '1'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+
+ get_lun_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_lun_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_lun_info_positive_with_lun_index(
+ self,
+ mock_http_connection):
+ """Test get lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_lun_info(LUNIndex='fakeLunIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['lunList'] = '1'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+
+ get_lun_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_lun_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_lun_info_positive_with_lun_name(
+ self,
+ mock_http_connection):
+ """Test get lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_lun_info(LUNName='fakeLunName')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['lunList'] = '1'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+
+ get_lun_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_lun_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_lun_info_positive_with_lun_naa(
+ self,
+ mock_http_connection):
+ """Test get lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_lun_info(LUNNAA='fakeLunNaa')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['lunList'] = '1'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+
+ get_lun_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_lun_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_lun_info_negative(
+ self,
+ mock_http_connection):
+ """Test get lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_lun_info)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_one_lun_info(
+ self,
+ mock_http_connection):
+ """Test get one lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeOneLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_one_lun_info('fakeLunId')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['lun_info'] = '1'
+ fake_params['lunID'] = 'fakeLunId'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+
+ get_lun_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_lun_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_one_lun_info_negative(
+ self,
+ mock_http_connection):
+ """Test get one lun info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_one_lun_info,
+ 'fakeLunId')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeSnapshotInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_snapshot_info(
+ lun_index='fakeLunIndex', snapshot_name='fakeSnapshotName')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['snapshot_list'] = '1'
+ fake_params['snap_start'] = '0'
+ fake_params['snap_count'] = '100'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_snapshot_info_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_snapshot_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info_negative(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_snapshot_info,
+ lun_index='fakeLunIndex',
+ snapshot_name='fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeSnapshotInfoFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_snapshot_info,
+ lun_index='fakeLunIndex',
+ snapshot_name='fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_snapshot_api(
+ self,
+ mock_http_connection):
+ """Test create snapshot api."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.create_snapshot_api(
+ 'fakeLunIndex', 'fakeSnapshotName')
+
+ fake_params = {}
+ fake_params['func'] = 'create_snapshot'
+ fake_params['lunID'] = 'fakeLunIndex'
+ fake_params['snapshot_name'] = 'fakeSnapshotName'
+ fake_params['expire_min'] = '0'
+ fake_params['vital'] = '1'
+ fake_params['snapshot_type'] = '0'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_snapshot_api_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', create_snapshot_api_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_snapshot_api_negative(
+ self,
+ mock_http_connection):
+ """Test create snapshot api."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_snapshot_api,
+ 'fakeLunIndex', 'fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_snapshot_api_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create snapshot api."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_snapshot_api,
+ 'fakeLunIndex', 'fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_snapshot_api(
+ self,
+ mock_http_connection):
+ """Test api de;ete snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_snapshot_api(
+ 'fakeSnapshotId')
+ fake_params = {}
+ fake_params['func'] = 'del_snapshots'
+ fake_params['snapshotID'] = 'fakeSnapshotId'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ api_delete_snapshot_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', api_delete_snapshot_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_snapshot_api_positive_without_snapshot(
+ self,
+ mock_http_connection):
+ """Test api de;ete snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotWithoutSnapshotResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_snapshot_api(
+ 'fakeSnapshotId')
+ fake_params = {}
+ fake_params['func'] = 'del_snapshots'
+ fake_params['snapshotID'] = 'fakeSnapshotId'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ api_delete_snapshot_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', api_delete_snapshot_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_snapshot_api_positive_without_lun(
+ self,
+ mock_http_connection):
+ """Test api de;ete snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotWithoutLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_snapshot_api(
+ 'fakeSnapshotId')
+ fake_params = {}
+ fake_params['func'] = 'del_snapshots'
+ fake_params['snapshotID'] = 'fakeSnapshotId'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ api_delete_snapshot_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', api_delete_snapshot_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_snapshot_api_negative(
+ self,
+ mock_http_connection):
+ """Test api de;ete snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_snapshot_api,
+ 'fakeSnapshotId')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_snapshot_api_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test api de;ete snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_snapshot_api,
+ 'fakeSnapshotId')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_clone_snapshot(
+ self,
+ mock_http_connection):
+ """Test clone snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.clone_snapshot(
+ 'fakeSnapshotId', 'fakeLunName')
+
+ fake_params = {}
+ fake_params['func'] = 'clone_qsnapshot'
+ fake_params['by_lun'] = '1'
+ fake_params['snapshotID'] = 'fakeSnapshotId'
+ fake_params['new_name'] = 'fakeLunName'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ clone_snapshot_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', clone_snapshot_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_clone_snapshot_negative(
+ self,
+ mock_http_connection):
+ """Test clone snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.clone_snapshot,
+ 'fakeSnapshotId', 'fakeLunName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_clone_snapshot_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test clone snapshot."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeCreateSnapshotFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.clone_snapshot,
+ 'fakeSnapshotId', 'fakeLunName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_edit_lun(
+ self,
+ mock_http_connection):
+ """Test edit lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ fake_lun = {'LUNName': 'fakeLunName',
+ 'LUNCapacity': 100,
+ 'LUNIndex': 'fakeLunIndex',
+ 'LUNThinAllocate': False,
+ 'LUNPath': 'fakeLunPath',
+ 'LUNStatus': 'fakeLunStatus'}
+ self.driver.api_executor.edit_lun(fake_lun)
+
+ fake_params = {}
+ fake_params['func'] = 'edit_lun'
+ fake_params['LUNName'] = 'fakeLunName'
+ fake_params['LUNCapacity'] = 100
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['LUNThinAllocate'] = False
+ fake_params['LUNPath'] = 'fakeLunPath'
+ fake_params['LUNStatus'] = 'fakeLunStatus'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ edit_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', edit_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_edit_lun_negative(
+ self,
+ mock_http_connection):
+ """Test edit lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ fake_lun = {'LUNName': 'fakeLunName',
+ 'LUNCapacity': 100,
+ 'LUNIndex': 'fakeLunIndex',
+ 'LUNThinAllocate': False,
+ 'LUNPath': 'fakeLunPath',
+ 'LUNStatus': 'fakeLunStatus'}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.edit_lun,
+ fake_lun)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_edit_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test edit lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ fake_lun = {'LUNName': 'fakeLunName',
+ 'LUNCapacity': 100,
+ 'LUNIndex': 'fakeLunIndex',
+ 'LUNThinAllocate': False,
+ 'LUNPath': 'fakeLunPath',
+ 'LUNStatus': 'fakeLunStatus'}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.edit_lun,
+ fake_lun)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_all_iscsi_portal_setting(
+ self,
+ mock_http_connection):
+ """Test get all iscsi portal setting."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeLunInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_all_iscsi_portal_setting()
+
+ fake_params = {}
+ fake_params['func'] = 'get_all'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_all_iscsi_portal_setting_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET',
+ get_all_iscsi_portal_setting_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_with_type_data(
+ self,
+ mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeGetAllEthernetIp()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_ethernet_ip(type='data')
+
+ fake_params = {}
+ fake_params['subfunc'] = 'net_setting'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_ethernet_ip_url = (
+ '/cgi-bin/sys/sysRequest.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_ethernet_ip_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_with_type_manage(
+ self,
+ mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeGetAllEthernetIp()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_ethernet_ip(type='manage')
+
+ fake_params = {}
+ fake_params['subfunc'] = 'net_setting'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_ethernet_ip_url = (
+ '/cgi-bin/sys/sysRequest.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_ethernet_ip_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_with_type_all(self, mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeGetAllEthernetIp()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_ethernet_ip(type='all')
+
+ fake_params = {}
+ fake_params['subfunc'] = 'net_setting'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_ethernet_ip_url = (
+ '/cgi-bin/sys/sysRequest.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_ethernet_ip_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_negative(
+ self,
+ mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_ethernet_ip,
+ type='data')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info(
+ self,
+ mock_http_connection):
+ """Test get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfo()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_target_info('fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['targetInfo'] = 1
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_target_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_target_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_negative(
+ self,
+ mock_http_connection):
+ """Test get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_target_info,
+ 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfoFail()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_target_info,
+ 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_by_initiator(
+ self,
+ mock_http_connection):
+ """Test get target info by initiator."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfo114Response(),
+ FakeLoginResponse(),
+ FakeTargetInfoByInitiator()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_target_info_by_initiator(
+ 'fakeInitiatorIQN', 'fakeLunSlotId')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['initiatorIQN'] = 'fakeInitiatorIQN'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_target_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_target_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_by_initiator_negative(
+ self,
+ mock_http_connection):
+ """Test get target info by initiator."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfo114Response(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.
+ get_target_info_by_initiator,
+ 'fakeInitiatorIQN', 'fakeLunSlotId')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_by_initiator_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get target info by initiator."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfo114Response(),
+ FakeLoginResponse(),
+ FakeTargetInfoByInitiatorFail()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_target_info_by_initiator(
+ 'fakeInitiatorIQN', 'fakeLunSlotId')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['initiatorIQN'] = 'fakeInitiatorIQN'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_target_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_target_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+
+class QnapAPIExecutorTsTestCase(QnapDriverBaseTestCase):
+ """Tests QnapAPIExecutorTS."""
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_with_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', True))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '1'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_without_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', False))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '0'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_lun('fakeLunIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['run_background'] = '1'
+ fake_params['ha_sync'] = '1'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ delete_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', delete_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_negative(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_lun,
+ 'fakeLunIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.delete_lun,
+ 'fakeLunIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_delete_lun_positive_with_busy_result(
+ self,
+ mock_http_connection):
+ """Test delete lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunBusyResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.delete_lun('fakeLunIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['run_background'] = '1'
+ fake_params['ha_sync'] = '1'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ delete_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', delete_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.map_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ map_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', map_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun_negative(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.map_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_map_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test map lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.map_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun(
+ self,
+ mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.disable_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'edit_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['LUNEnable'] = 0
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ unmap_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', unmap_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun_negative(
+ self,
+ mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.disable_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_disable_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test disable lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.disable_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.unmap_lun(
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_lun'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ unmap_lun_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', unmap_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun_negative(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.unmap_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_unmap_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test unmap lun."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.unmap_lun,
+ 'fakeLunIndex', 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_remove_target_init(
+ self,
+ mock_http_connection):
+ """Test remove target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfo()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.remove_target_init(
+ 'fakeTargetIqn', 'fakeDefaultAcl')
+
+ fake_params = {}
+ fake_params['func'] = 'remove_init'
+ fake_params['targetIQN'] = 'fakeTargetIqn'
+ fake_params['initiatorIQN'] = 'fakeDefaultAcl'
+ fake_params['ha_sync'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_params = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_params[key] = six.text_type(value)
+
+ fake_post_params = urllib.parse.urlencode(fake_post_params)
+ remove_target_init_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_params)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call(
+ 'GET', remove_target_init_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_remove_target_init_negative(
+ self,
+ mock_http_connection):
+ """Test remove target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.remove_target_init,
+ 'fakeTargetIqn', 'fakeDefaultAcl')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_remove_target_init_negative_with_wrong_result(
+ self, mock_http_connection):
+ """Test remove target init."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfoFail()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.remove_target_init,
+ 'fakeTargetIqn', 'fakeDefaultAcl')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info(
+ self, mock_http_connection):
+ """Test get get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfo()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_target_info(
+ 'fakeTargetIndex')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['targetInfo'] = 1
+ fake_params['targetIndex'] = 'fakeTargetIndex'
+ fake_params['ha_sync'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_params = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_params[key] = six.text_type(value)
+
+ fake_post_params = urllib.parse.urlencode(fake_post_params)
+ get_target_info_url = (
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?' + fake_post_params)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_target_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_negative(
+ self,
+ mock_http_connection):
+ """Test get get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_target_info,
+ 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_target_info_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get get target info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeTargetInfoFail()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_target_info,
+ 'fakeTargetIndex')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_with_type(
+ self,
+ mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeGetAllEthernetIp()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_ethernet_ip(
+ type='data')
+
+ fake_post_parm = 'subfunc=net_setting&sid=fakeSid'
+ get_ethernet_ip_url = (
+ '/cgi-bin/sys/sysRequest.cgi?' + fake_post_parm)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_ethernet_ip_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_negative(self, mock_http_connection):
+ """Test get ethernet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_ethernet_ip,
+ type='data')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeSnapshotInfoResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_snapshot_info(
+ lun_index='fakeLunIndex', snapshot_name='fakeSnapshotName')
+
+ fake_params = {}
+ fake_params['func'] = 'extra_get'
+ fake_params['LUNIndex'] = 'fakeLunIndex'
+ fake_params['smb_snapshot_list'] = '1'
+ fake_params['smb_snapshot'] = '1'
+ fake_params['snapshot_list'] = '1'
+ fake_params['sid'] = 'fakeSid'
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ get_snapshot_info_url = (
+ '/cgi-bin/disk/snapshot.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_snapshot_info_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info_negative(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_snapshot_info,
+ lun_index='fakeLunIndex',
+ snapshot_name='fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_snapshot_info_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test get snapshot info."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeSnapshotInfoFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_snapshot_info,
+ lun_index='fakeLunIndex',
+ snapshot_name='fakeSnapshotName')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreatTargetResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.create_target('fakeTargetName', 'sca')
+ fake_params = {}
+ fake_params['func'] = 'add_target'
+ fake_params['targetName'] = 'fakeTargetName'
+ fake_params['targetAlias'] = 'fakeTargetName'
+ fake_params['bTargetDataDigest'] = '0'
+ fake_params['bTargetHeaderDigest'] = '0'
+ fake_params['bTargetClusterEnable'] = '1'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_target_url = (
+ '/cgi-bin/disk/iscsi_target_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_target_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target_negative(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_target,
+ 'fakeTargetName', 'sca')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_target_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create target."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTsResponse(),
+ FakeLoginResponse(),
+ FakeCreatTargetFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Storage Pool 1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_target,
+ 'fakeTargetName', 'sca')
+
+
+class QnapAPIExecutorTesTestCase(QnapDriverBaseTestCase):
+ """Tests QnapAPIExecutorTES."""
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_with_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', True))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '1'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['sync'] = 'disabled'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_positive_without_thin_allocate(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertEqual(
+ 'fakeLunIndex',
+ self.driver.api_executor.create_lun(
+ fake_volume, 'fakepool', 'fakeLun', False))
+
+ fake_params = {}
+ fake_params['func'] = 'add_lun'
+ fake_params['FileIO'] = 'no'
+ fake_params['LUNThinAllocate'] = '0'
+ fake_params['LUNName'] = 'fakeLun'
+ fake_params['LUNPath'] = 'fakeLun'
+ fake_params['poolID'] = 'fakepool'
+ fake_params['lv_ifssd'] = 'no'
+ fake_params['sync'] = 'disabled'
+ fake_params['LUNCapacity'] = 100
+ fake_params['lv_threshold'] = '80'
+ fake_params['sid'] = 'fakeSid'
+
+ fake_post_parms = {}
+ for key in fake_params:
+ value = fake_params[key]
+ if value is not None:
+ fake_post_parms[key] = six.text_type(value)
+
+ fake_post_parms = urllib.parse.urlencode(fake_post_parms)
+ create_lun_url = (
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?' + fake_post_parms)
+
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', create_lun_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_create_lun_negative_with_wrong_result(
+ self,
+ mock_http_connection):
+ """Test create lun."""
+ fake_volume = VolumeClass(
+ 'fakeDisplayName', 'fakeId', 100, 'fakeLunName')
+
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeCreateLunFailResponse()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.create_lun,
+ fake_volume, 'fakepool', 'fakeLun', 'False')
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_with_type(
+ self,
+ mock_http_connection):
+ """Test get ehternet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeGetAllEthernetIp()])
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.driver.api_executor.get_ethernet_ip(
+ type='data')
+
+ fake_post_parm = 'subfunc=net_setting&sid=fakeSid'
+ get_ethernet_ip_url = (
+ '/cgi-bin/sys/sysRequest.cgi?' + fake_post_parm)
+ expected_call_list = [
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_basic_info_url),
+ mock.call('POST', login_url, global_sanitized_params, header),
+ mock.call('GET', get_ethernet_ip_url)]
+ self.assertEqual(
+ expected_call_list,
+ mock_http_connection.return_value.request.call_args_list)
+
+ @mock.patch('six.moves.http_client.HTTPConnection')
+ def test_get_ethernet_ip_negative(
+ self,
+ mock_http_connection):
+ """Test get ehternet ip."""
+ mock_http_connection.return_value.getresponse.side_effect = ([
+ FakeLoginResponse(),
+ FakeGetBasicInfoTesResponse(),
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] + [
+ FakeLoginResponse(),
+ FakeNoAuthPassedResponse()] * 4)
+
+ self.driver = qnap.QnapISCSIDriver(
+ configuration=create_configuration(
+ 'admin',
+ 'qnapadmin',
+ 'http://1.2.3.4:8080',
+ '1.2.3.4',
+ 'Pool1',
+ True))
+ self.driver.do_setup('context')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.api_executor.get_ethernet_ip,
+ type='data')
diff --git a/cinder/volume/drivers/qnap.py b/cinder/volume/drivers/qnap.py
new file mode 100644
index 00000000000..abd33f7c1a3
--- /dev/null
+++ b/cinder/volume/drivers/qnap.py
@@ -0,0 +1,2133 @@
+# Copyright (c) 2016 QNAP Systems, Inc.
+# 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
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Volume driver for QNAP Storage.
+This driver supports QNAP Storage for iSCSI.
+"""
+import base64
+import eventlet
+import functools
+import re
+import ssl
+import threading
+import time
+try:
+ import xml.etree.cElementTree as ET
+except ImportError:
+ import xml.etree.ElementTree as ET
+
+from oslo_concurrency import lockutils
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import timeutils
+from oslo_utils import units
+import six
+from six.moves import http_client
+from six.moves import urllib
+
+from cinder import exception
+from cinder.i18n import _
+from cinder import interface
+from cinder import utils
+from cinder.volume import configuration
+from cinder.volume.drivers.san import san
+
+LOG = logging.getLogger(__name__)
+
+qnap_opts = [
+ cfg.URIOpt('qnap_management_url',
+ help='The URL to management QNAP Storage'),
+ cfg.StrOpt('qnap_poolname',
+ help='The pool name in the QNAP Storage'),
+ cfg.StrOpt('qnap_storage_protocol',
+ default='iscsi',
+ help='Communication protocol to access QNAP storage'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(qnap_opts, group=configuration.SHARED_CONF_GROUP)
+
+
+@interface.volumedriver
+class QnapISCSIDriver(san.SanISCSIDriver):
+ """OpenStack driver to enable QNAP Storage.
+
+ Version history:
+ 1.0.0 - Initial driver (Only iSCSI)
+ """
+
+ # ThirdPartySystems wiki page
+ CI_WIKI_NAME = "QNAP_CI"
+
+ VERSION = '1.1.021'
+
+ TIME_INTERVAL = 3
+
+ def __init__(self, *args, **kwargs):
+ """Initialize QnapISCSIDriver."""
+ super(QnapISCSIDriver, self).__init__(*args, **kwargs)
+ self.api_executor = None
+ self.group_stats = {}
+ self.configuration.append_config_values(qnap_opts)
+ self.cache_time = 0
+ self.initiator = ''
+ self.iscsi_port = ''
+ self.target_index = ''
+ self.target_iqn = ''
+ self.target_iqns = []
+ self.nasInfoCache = {}
+
+ def _check_config(self):
+ """Ensure that the flags we care about are set."""
+ LOG.debug('in _check_config')
+ required_config = ['qnap_management_url',
+ 'san_login',
+ 'san_password',
+ 'qnap_poolname',
+ 'qnap_storage_protocol']
+
+ for attr in required_config:
+ if not getattr(self.configuration, attr, None):
+ raise exception.InvalidInput(
+ reason=_('%s is not set.') % attr)
+
+ def do_setup(self, context):
+ """Setup the QNAP Cinder volume driver."""
+ self._check_config()
+ self.ctxt = context
+ LOG.debug('context: %s', context)
+
+ # Setup API Executor
+ try:
+ self.api_executor = self.creat_api_executor()
+ except Exception:
+ LOG.error('Failed to create HTTP client. '
+ 'Check ip, port, username, password'
+ ' and make sure the array version is compatible')
+ msg = _('Failed to create HTTP client.')
+ raise exception.VolumeDriverException(message=msg)
+
+ def check_for_setup_error(self):
+ """Check the status of setup."""
+ pass
+
+ def creat_api_executor(self):
+ """Create api executor by nas model."""
+ self.api_executor = QnapAPIExecutor(
+ username=self.configuration.san_login,
+ password=self.configuration.san_password,
+ management_url=self.configuration.qnap_management_url)
+
+ nas_model_name, internal_model_name, fw_version = (
+ self.api_executor.get_basic_info(
+ self.configuration.qnap_management_url))
+
+ if (self.configuration.qnap_management_url not in self.nasInfoCache):
+ self.nasInfoCache[self.configuration.qnap_management_url] = (
+ nas_model_name, internal_model_name, fw_version)
+
+ pattern = re.compile(r"^([A-Z]+)-?[A-Z]{0,2}(\d+)\d{2}(U|[a-z]*)")
+ matches = pattern.match(nas_model_name)
+
+ if not matches:
+ return None
+ model_type = matches.group(1)
+
+ ts_model_types = [
+ "TS", "SS", "IS", "TVS", "TDS", "TBS"
+ ]
+ tes_model_types = [
+ "TES"
+ ]
+ es_model_types = [
+ "ES"
+ ]
+
+ if model_type in ts_model_types:
+ if (fw_version >= "4.2") and (fw_version <= "4.4"):
+ LOG.debug('Create TS API Executor')
+ # modify the pool name to pool index
+ self.configuration.qnap_poolname = (
+ self._get_ts_model_pool_id(
+ self.configuration.qnap_poolname))
+
+ return (QnapAPIExecutorTS(
+ username=self.configuration.san_login,
+ password=self.configuration.san_password,
+ management_url=self.configuration.qnap_management_url))
+ elif model_type in tes_model_types:
+ if 'TS' in internal_model_name:
+ if (fw_version >= "4.2") and (fw_version <= "4.4"):
+ LOG.debug('Create TS API Executor')
+ # modify the pool name to poole index
+ self.configuration.qnap_poolname = (
+ self._get_ts_model_pool_id(
+ self.configuration.qnap_poolname))
+ return (QnapAPIExecutorTS(
+ username=self.configuration.san_login,
+ password=self.configuration.san_password,
+ management_url=self.configuration.qnap_management_url))
+ elif (fw_version >= "1.1.2") or (fw_version <= "1.1.4"):
+ LOG.debug('Create TES API Executor')
+ return (QnapAPIExecutorTES(
+ username=self.configuration.san_login,
+ password=self.configuration.san_password,
+ management_url=self.configuration.qnap_management_url))
+ elif model_type in es_model_types:
+ if (fw_version >= "1.1.2") or (fw_version <= "1.1.4"):
+ LOG.debug('Create ES API Executor')
+ return (QnapAPIExecutor(
+ username=self.configuration.san_login,
+ password=self.configuration.san_password,
+ management_url=self.configuration.qnap_management_url))
+
+ msg = _('Model not support')
+ raise exception.VolumeDriverException(message=msg)
+
+ def _get_ts_model_pool_id(self, pool_name):
+ """Modify the pool name to poole index."""
+ pattern = re.compile(r"^(\d+)+|^Storage Pool (\d+)+")
+ matches = pattern.match(pool_name)
+ if matches.group(1):
+ return matches.group(1)
+ else:
+ return matches.group(2)
+
+ def _gen_random_name(self):
+ return "cinder-{0}".format(timeutils.
+ utcnow().
+ strftime('%Y%m%d%H%M%S%f'))
+
+ def _get_volume_metadata(self, volume):
+ volume_metadata = {}
+ if 'volume_metadata' in volume:
+ for metadata in volume['volume_metadata']:
+ volume_metadata[metadata['key']] = metadata['value']
+ return volume_metadata
+
+ def _gen_lun_name(self):
+ create_lun_name = ''
+ while True:
+ create_lun_name = self._gen_random_name()
+ # If lun name with the name exists, need to change to
+ # a different name
+ created_lun = self.api_executor.get_lun_info(
+ LUNName=create_lun_name)
+ if created_lun is None:
+ break
+ return create_lun_name
+
+ 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
+
+ # User could create two volume with the same name on horizon.
+ # Therefore, We should not use display name to create lun on nas.
+ create_lun_name = self._gen_lun_name()
+
+ create_lun_index = self.api_executor.create_lun(
+ volume,
+ self.configuration.qnap_poolname,
+ create_lun_name,
+ reserve)
+
+ max_wait_sec = 600
+ try_times = 0
+ lun_naa = ""
+ while True:
+ created_lun = self.api_executor.get_lun_info(
+ LUNIndex=create_lun_index)
+ if created_lun.find('LUNNAA') is not None:
+ lun_naa = created_lun.find('LUNNAA').text
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or lun_naa is not None):
+ break
+
+ LOG.debug('LUNNAA: %s', lun_naa)
+ _metadata = self._get_volume_metadata(volume)
+
+ _metadata['LUNIndex'] = create_lun_index
+ _metadata['LUNNAA'] = lun_naa
+ _metadata['LunName'] = create_lun_name
+
+ elapsed_time = time.time() - start_time
+ LOG.debug('create_volume elapsed_time: %s', elapsed_time)
+
+ LOG.debug('create_volume volid: %(volid)s, metadata: %(meta)s',
+ {'volid': volume['id'], 'meta': _metadata})
+
+ return {'metadata': _metadata}
+
+ @lockutils.synchronized('delete_volume', 'cinder-', True)
+ def delete_volume(self, volume):
+ """Delete the specified volume."""
+ start_time = time.time()
+ LOG.debug('volume: %s', volume.__dict__)
+ lun_naa = self._get_lun_naa_from_volume_metadata(volume)
+ if lun_naa == '':
+ LOG.debug('Volume %s does not exist.', volume.id)
+ return
+
+ lun_index = ''
+ for metadata in volume['volume_metadata']:
+ if metadata['key'] == 'LUNIndex':
+ lun_index = metadata['value']
+ break
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ internal_model_name = (self.nasInfoCache
+ [self.configuration.qnap_management_url][1])
+ LOG.debug('internal_model_name: %s', internal_model_name)
+ fw_version = self.nasInfoCache[self.configuration
+ .qnap_management_url][2]
+ LOG.debug('fw_version: %s', fw_version)
+
+ if 'TS' in internal_model_name.upper():
+ LOG.debug('in TS FW: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ del_lun = ET.fromstring(ret['data']).find('LUNInfo').find('row')
+ elif 'ES' in internal_model_name.upper():
+ if fw_version >= "1.1.2" and fw_version <= "1.1.3":
+ LOG.debug('in ES FW before 1.1.2/1.1.3: get_lun_info')
+ del_lun = self.api_executor.get_lun_info(
+ LUNIndex=lun_index)
+ elif fw_version == "1.1.4":
+ LOG.debug('in ES FW after 1.1.4: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ del_lun = (ET.fromstring(ret['data']).find('LUNInfo')
+ .find('row'))
+
+ if del_lun is None:
+ LOG.debug('Volume %s does not exist.', lun_naa)
+ return
+
+ # if lun is mapping at target, the delete action will fail
+ if del_lun.find('LUNStatus').text == '2':
+ target_index = (del_lun.find('LUNTargetList')
+ .find('row').find('targetIndex').text)
+ LOG.debug('target_index: %s', target_index)
+ self.api_executor.disable_lun(lun_index, target_index)
+ self.api_executor.unmap_lun(lun_index, target_index)
+
+ retry_delete = False
+ while True:
+ retry_delete = self.api_executor.delete_lun(lun_index)
+ if not retry_delete:
+ break
+
+ elapsed_time = time.time() - start_time
+ LOG.debug('delete_volume elapsed_time: %s', elapsed_time)
+
+ def _get_lun_naa_from_volume_metadata(self, volume):
+ lun_naa = ''
+ for metadata in volume['volume_metadata']:
+ if metadata['key'] == 'LUNNAA':
+ lun_naa = metadata['value']
+ break
+ return lun_naa
+
+ def _extend_lun(self, volume, lun_naa):
+ LOG.debug('volume: %s', volume.__dict__)
+ if lun_naa == '':
+ lun_naa = self._get_lun_naa_from_volume_metadata(volume)
+
+ LOG.debug('lun_naa: %s', lun_naa)
+ selected_lun = self.api_executor.get_lun_info(
+ LUNNAA=lun_naa)
+ lun_index = selected_lun.find('LUNIndex').text
+ LOG.debug('LUNIndex: %s', lun_index)
+ lun_name = selected_lun.find('LUNName').text
+ LOG.debug('LUNName: %s', lun_name)
+ lun_thin_allocate = selected_lun.find('LUNThinAllocate').text
+ LOG.debug('LUNThinAllocate: %s', lun_thin_allocate)
+ lun_path = ''
+ if selected_lun.find('LUNPath') is not None:
+ lun_path = selected_lun.find('LUNPath').text
+ LOG.debug('LUNPath: %s', lun_path)
+ lun_status = selected_lun.find('LUNStatus').text
+ LOG.debug('LUNStatus: %s', lun_status)
+
+ lun = {'LUNName': lun_name,
+ 'LUNCapacity': volume['size'],
+ 'LUNIndex': lun_index,
+ 'LUNThinAllocate': lun_thin_allocate,
+ 'LUNPath': lun_path,
+ 'LUNStatus': lun_status}
+ self.api_executor.edit_lun(lun)
+
+ def _create_snapshot_name(self, lun_index):
+ create_snapshot_name = ''
+ while True:
+ # If snapshot with the name exists, need to change to
+ # a different name
+ create_snapshot_name = 'Q%d' % int(time.time())
+ snapshot = self.api_executor.get_snapshot_info(
+ lun_index=lun_index, snapshot_name=create_snapshot_name)
+ if snapshot is None:
+ break
+ return create_snapshot_name
+
+ def create_cloned_volume(self, volume, src_vref):
+ """Create a clone of the specified volume."""
+ LOG.debug('Entering create_cloned_volume...')
+ LOG.debug('volume: %s', volume.__dict__)
+ LOG.debug('src_vref: %s', src_vref.__dict__)
+ LOG.debug('volume_metadata: %s', volume['volume_metadata'])
+ src_lun_naa = self._get_lun_naa_from_volume_metadata(src_vref)
+ # Below is to clone a volume from a snapshot in the snapshot manager
+ src_lun = self.api_executor.get_lun_info(
+ LUNNAA=src_lun_naa)
+ lun_index = src_lun.find('LUNIndex').text
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ # User could create two snapshot with the same name on horizon.
+ # Therefore, we should not use displayname to create snapshot on nas.
+ create_snapshot_name = self._create_snapshot_name(lun_index)
+
+ self.api_executor.create_snapshot_api(lun_index, create_snapshot_name)
+ created_snapshot = self.api_executor.get_snapshot_info(
+ lun_index=lun_index, snapshot_name=create_snapshot_name)
+ snapshot_id = created_snapshot.find('snapshot_id').text
+ LOG.debug('snapshot_id: %s', snapshot_id)
+
+ # User could create two volume with the same name on horizon.
+ # Therefore, We should not use displayname to create lun on nas.
+ while True:
+ cloned_lun_name = self._gen_random_name()
+ # If lunname with the name exists, need to change to
+ # a different name
+ cloned_lun = self.api_executor.get_lun_info(
+ LUNName=cloned_lun_name)
+
+ if cloned_lun is None:
+ break
+
+ self.api_executor.clone_snapshot(snapshot_id, cloned_lun_name)
+
+ max_wait_sec = 600
+ try_times = 0
+ lun_naa = ""
+ lun_index = ""
+ while True:
+ created_lun = self.api_executor.get_lun_info(
+ LUNName=cloned_lun_name)
+ if created_lun.find('LUNNAA') is not None:
+ lun_naa = created_lun.find('LUNNAA').text
+ lun_index = created_lun.find('LUNIndex').text
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or lun_naa is not None):
+ break
+
+ LOG.debug('LUNNAA: %s', lun_naa)
+ if (volume['size'] > src_vref['size']):
+ self._extend_lun(volume, lun_naa)
+ internal_model_name = (self.nasInfoCache
+ [self.configuration.qnap_management_url][1])
+
+ if 'TS' in internal_model_name.upper():
+ LOG.debug('in TS FW: delete_snapshot_api')
+ self.api_executor.delete_snapshot_api(snapshot_id)
+ elif 'ES' in internal_model_name.upper():
+ LOG.debug('in ES FW: do nothing')
+ pass
+ _metadata = self._get_volume_metadata(volume)
+ _metadata['LUNIndex'] = lun_index
+ _metadata['LUNNAA'] = lun_naa
+ _metadata['LunName'] = cloned_lun_name
+ return {'metadata': _metadata}
+
+ def create_snapshot(self, snapshot):
+ """Create a snapshot."""
+ LOG.debug('snapshot: %s', snapshot.__dict__)
+ LOG.debug('snapshot id: %s', snapshot['id'])
+
+ # Below is to create snapshot in the snapshot manager
+ LOG.debug('volume_metadata: %s', snapshot.volume['metadata'])
+ volume_metadata = snapshot.volume['metadata']
+ LOG.debug('lun_naa: %s', volume_metadata['LUNNAA'])
+ lun_naa = volume_metadata['LUNNAA']
+ src_lun = self.api_executor.get_lun_info(LUNNAA=lun_naa)
+ lun_index = src_lun.find('LUNIndex').text
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ # User could create two snapshot with the same name on horizon.
+ # Therefore, We should not use displayname to create snapshot on nas.
+ create_snapshot_name = self._create_snapshot_name(lun_index)
+ LOG.debug('create_snapshot_name: %s', create_snapshot_name)
+
+ self.api_executor.create_snapshot_api(lun_index, create_snapshot_name)
+ max_wait_sec = 600
+ try_times = 0
+ snapshot_id = ""
+ while True:
+ created_snapshot = self.api_executor.get_snapshot_info(
+ lun_index=lun_index, snapshot_name=create_snapshot_name)
+ if created_snapshot is not None:
+ snapshot_id = created_snapshot.find('snapshot_id').text
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or created_snapshot is not None):
+ break
+
+ LOG.debug('created_snapshot: %s', created_snapshot)
+ LOG.debug('snapshot_id: %s', snapshot_id)
+
+ _metadata = snapshot['metadata']
+ _metadata['snapshot_id'] = snapshot_id
+ _metadata['SnapshotName'] = create_snapshot_name
+ return {'metadata': _metadata}
+
+ def delete_snapshot(self, snapshot):
+ """Delete a snapshot."""
+ LOG.debug('snapshot: %s', snapshot.__dict__)
+
+ # Below is to delete snapshot in the snapshot manager
+ snap_metadata = snapshot['metadata']
+ if 'snapshot_id' not in snap_metadata:
+ return
+ LOG.debug('snapshot_id: %s', snap_metadata['snapshot_id'])
+ snapshot_id = snap_metadata['snapshot_id']
+
+ self.api_executor.delete_snapshot_api(snapshot_id)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Create a volume from a snapshot."""
+ LOG.debug('in create_volume_from_snapshot')
+ LOG.debug('volume: %s', volume.__dict__)
+ LOG.debug('snapshot: %s', snapshot.__dict__)
+ # Below is to clone a volume from a snapshot in the snapshot manager
+ snap_metadata = snapshot['metadata']
+ if 'snapshot_id' not in snap_metadata:
+ LOG.debug('Metadata of the snapshot is invalid')
+ msg = _('Metadata of the snapshot is invalid')
+ raise exception.VolumeDriverException(message=msg)
+ LOG.debug('snapshot_id: %s', snap_metadata['snapshot_id'])
+ snapshot_id = snap_metadata['snapshot_id']
+
+ # User could create two volume with the same name on horizon.
+ # Therefore, We should not use displayname to create lun on nas.
+ create_lun_name = self._gen_lun_name()
+
+ self.api_executor.clone_snapshot(
+ snapshot_id, create_lun_name)
+
+ max_wait_sec = 600
+ try_times = 0
+ lun_naa = ""
+ lun_index = ""
+ while True:
+ created_lun = self.api_executor.get_lun_info(
+ LUNName=create_lun_name)
+ if created_lun.find('LUNNAA') is not None:
+ lun_naa = created_lun.find('LUNNAA').text
+ lun_index = created_lun.find('LUNIndex').text
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or lun_naa is not None):
+ break
+
+ if (volume['size'] > snapshot['volume_size']):
+ self._extend_lun(volume, lun_naa)
+
+ _metadata = self._get_volume_metadata(volume)
+ _metadata['LUNIndex'] = lun_index
+ _metadata['LUNNAA'] = lun_naa
+ _metadata['LunName'] = create_lun_name
+ return {'metadata': _metadata}
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume stats. This is more of getting group stats."""
+ LOG.debug('in get_volume_stats refresh: %s', refresh)
+
+ if refresh:
+ backend_name = (self.configuration.safe_get(
+ 'volume_backend_name') or
+ self.__class__.__name__)
+ LOG.debug('backend_name=%(backend_name)s',
+ {'backend_name': backend_name})
+
+ selected_pool = self.api_executor.get_specific_poolinfo(
+ self.configuration.qnap_poolname)
+ capacity_bytes = int(selected_pool.find('capacity_bytes').text)
+ LOG.debug('capacity_bytes: %s GB', capacity_bytes / units.Gi)
+ freesize_bytes = int(selected_pool.find('freesize_bytes').text)
+ LOG.debug('freesize_bytes: %s GB', freesize_bytes / units.Gi)
+ provisioned_bytes = int(selected_pool.find('allocated_bytes').text)
+ driver_protocol = self.configuration.qnap_storage_protocol
+ LOG.debug(
+ 'provisioned_bytes: %s GB', provisioned_bytes / units.Gi)
+ self.group_stats = {'volume_backend_name': backend_name,
+ 'vendor_name': 'QNAP',
+ 'driver_version': self.VERSION,
+ 'storage_protocol': driver_protocol}
+ # single pool now, need support multiple pools in the future
+ single_pool = dict(
+ pool_name=self.configuration.qnap_poolname,
+ total_capacity_gb=capacity_bytes / units.Gi,
+ free_capacity_gb=freesize_bytes / units.Gi,
+ provisioned_capacity_gb=provisioned_bytes / units.Gi,
+ reserved_percentage=self.configuration.reserved_percentage,
+ QoS_support=False)
+ self.group_stats['pools'] = [single_pool]
+
+ return self.group_stats
+
+ def extend_volume(self, volume, new_size):
+ """Extend an existing volume."""
+ LOG.debug('Entering extend_volume volume=%(vol)s '
+ 'new_size=%(size)s',
+ {'vol': volume['display_name'], 'size': new_size})
+
+ volume['size'] = new_size
+ self._extend_lun(volume, '')
+
+ def _get_portal_info(self, volume, connector, lun_slot_id, lun_owner):
+ """Get portal info."""
+ # Cache portal info for twenty seconds
+ # If connectors were the same then use the portal info which was cached
+ LOG.debug('get into _get_portal_info')
+ self.initiator = connector['initiator']
+ ret = self.api_executor.get_iscsi_portal_info()
+ root = ET.fromstring(ret['data'])
+ iscsi_port = root.find('iSCSIPortal').find('servicePort').text
+ LOG.debug('iscsiPort: %s', iscsi_port)
+ target_iqn_prefix = root.find(
+ 'iSCSIPortal').find('targetIQNPrefix').text
+ LOG.debug('targetIQNPrefix: %s', target_iqn_prefix)
+
+ internal_model_name = (self.nasInfoCache
+ [self.configuration.qnap_management_url][1])
+ LOG.debug('internal_model_name: %s', internal_model_name)
+ fw_version = (self.nasInfoCache
+ [self.configuration.qnap_management_url][2])
+ LOG.debug('fw_version: %s', fw_version)
+
+ 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)
+ if not target_index:
+ target_name = self._gen_random_name()
+ LOG.debug('target_name: %s', target_name)
+ target_index = self.api_executor.create_target(
+ target_name, lun_owner)
+ LOG.debug('targetIndex: %s', target_index)
+
+ retryCount = 0
+ retrySleepTime = 2
+ while retryCount <= 5:
+ target_info = self.api_executor.get_target_info(target_index)
+ if target_info.find('targetIQN').text is not None:
+ break
+ eventlet.sleep(retrySleepTime)
+ retrySleepTime = retrySleepTime + 2
+ retryCount = retryCount + 1
+
+ target_iqn = target_info.find('targetIQN').text
+ LOG.debug('target_iqn: %s', target_iqn)
+
+ # TS NAS have to remove default ACL
+ default_acl = (
+ target_iqn_prefix[:target_iqn_prefix.find(":") + 1])
+ default_acl = default_acl + "all:iscsi.default.ffffff"
+ LOG.debug('default_acl: %s', default_acl)
+ self.api_executor.remove_target_init(target_iqn, default_acl)
+ # add ACL
+ self.api_executor.add_target_init(
+ target_iqn, connector['initiator'])
+
+ # Get information for multipath
+ target_iqns = []
+ slotid_list = []
+ eth_list, slotid_list = Util.retriveFormCache(
+ self.configuration.qnap_management_url,
+ lambda: self.api_executor.get_ethernet_ip(type='data'),
+ 30)
+
+ LOG.debug('slotid_list: %s', slotid_list)
+ target_portals = []
+ target_portals.append(
+ self.configuration.iscsi_ip_address + ':' + iscsi_port)
+ # target_iqns.append(target_iqn)
+ for index, eth in enumerate(eth_list):
+ # TS NAS do not have slot_id
+ if not slotid_list:
+ target_iqns.append(target_iqn)
+ else:
+ # To support ALUA, target portal and target inq should
+ # be consistent.
+ # EX: 10.77.230.31:3260 at controller B and it should map
+ # to the target at controller B
+ target_iqns.append(
+ target_iqn[:-2] + '.' + slotid_list[index])
+
+ if eth == self.configuration.iscsi_ip_address:
+ continue
+ target_portals.append(eth + ':' + iscsi_port)
+
+ self.iscsi_port = iscsi_port
+ self.target_index = target_index
+ self.target_iqn = target_iqn
+ self.target_iqns = target_iqns
+ self.target_portals = target_portals
+
+ return (iscsi_port, target_index, target_iqn,
+ target_iqns, target_portals)
+
+ @lockutils.synchronized('create_export', 'cinder-', True)
+ def create_export(self, context, volume, connector):
+ start_time = time.time()
+ LOG.debug('in create_export')
+ LOG.debug('volume: %s', volume.__dict__)
+ LOG.debug('connector: %s', connector)
+
+ lun_naa = self._get_lun_naa_from_volume_metadata(volume)
+ if lun_naa == '':
+ msg = (_("Volume %s does not exist.") % volume.id)
+ LOG.error(msg)
+ raise exception.VolumeDriverException(message=msg)
+
+ LOG.debug('volume[name]: %s', volume['name'])
+ LOG.debug('volume[display_name]: %s', volume['display_name'])
+
+ lun_index = ''
+ for metadata in volume['volume_metadata']:
+ if metadata['key'] == 'LUNIndex':
+ lun_index = metadata['value']
+ break
+ LOG.debug('LUNIndex: %s', lun_index)
+ internal_model_name = (self.nasInfoCache
+ [self.configuration.qnap_management_url][1])
+ LOG.debug('internal_model_name: %s', internal_model_name)
+ fw_version = self.nasInfoCache[self.configuration
+ .qnap_management_url][2]
+ LOG.debug('fw_version: %s', fw_version)
+ if 'TS' in internal_model_name.upper():
+ LOG.debug('in TS FW: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ selected_lun = (ET.fromstring(ret['data']).find('LUNInfo')
+ .find('row'))
+ elif 'ES' in internal_model_name.upper():
+ if fw_version >= "1.1.2" and fw_version <= "1.1.3":
+ LOG.debug('in ES FW before 1.1.2/1.1.3: get_lun_info')
+ selected_lun = self.api_executor.get_lun_info(
+ LUNNAA=lun_naa)
+ elif fw_version == "1.1.4":
+ LOG.debug('in ES FW after 1.1.4: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ selected_lun = (ET.fromstring(ret['data']).find('LUNInfo')
+ .find('row'))
+
+ lun_owner = ''
+ lun_slot_id = ''
+ if selected_lun.find('lun_owner') is not None:
+ lun_owner = selected_lun.find('lun_owner').text
+ LOG.debug('lun_owner: %s', lun_owner)
+ lun_slot_id = '0' if (lun_owner == 'SCA') else '1'
+ LOG.debug('lun_slot_id: %s', lun_slot_id)
+
+ # LOG.debug('self.initiator: %s', self.initiator)
+ 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.api_executor.map_lun(lun_index, target_index)
+
+ max_wait_sec = 600
+ try_times = 0
+ LUNNumber = ""
+ target_lun_id = -999
+ while True:
+ if 'TS' in internal_model_name.upper():
+ LOG.debug('in TS FW: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ root = ET.fromstring(ret['data'])
+ target_lun_id = int(root.find('LUNInfo').find('row')
+ .find('LUNTargetList').find('row')
+ .find('LUNNumber').text)
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or target_lun_id != -999):
+ break
+
+ elif 'ES' in internal_model_name.upper():
+ if fw_version >= "1.1.2" and fw_version <= "1.1.3":
+ LOG.debug('in ES FW before 1.1.2/1.1.3: get_lun_info')
+ root = self.api_executor.get_lun_info(LUNNAA=lun_naa)
+ if len(list(root.find('LUNTargetList'))) != 0:
+ LUNNumber = root.find('LUNTargetList').find(
+ 'row').find('LUNNumber').text
+ target_lun_id = int(LUNNumber)
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or LUNNumber != ""):
+ break
+ elif fw_version == "1.1.4":
+ LOG.debug('in ES FW after 1.1.4: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ root = ET.fromstring(ret['data'])
+ target_lun_id = int(root.find('LUNInfo')
+ .find('row').find('LUNTargetList')
+ .find('row').find('LUNNumber').text)
+
+ try_times = try_times + 3
+ eventlet.sleep(self.TIME_INTERVAL)
+ if(try_times > max_wait_sec or target_lun_id != -999):
+ break
+ else:
+ break
+ else:
+ break
+
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_portal'] = (self.configuration.iscsi_ip_address +
+ ':' + iscsi_port)
+ properties['target_iqn'] = target_iqn
+ LOG.debug('properties[target_iqn]: %s', properties['target_iqn'])
+
+ LOG.debug('target_lun_id: %s', target_lun_id)
+ properties['target_lun'] = target_lun_id
+ properties['volume_id'] = volume['id'] # used by xen currently
+
+ multipath = connector.get('multipath', False)
+ if multipath:
+ """Below are settings for multipath"""
+ properties['target_portals'] = target_portals
+ properties['target_iqns'] = target_iqns
+ properties['target_luns'] = (
+ [target_lun_id] * len(target_portals))
+ LOG.debug('properties: %s', properties)
+
+ provider_location = '%(host)s:%(port)s,1 %(name)s %(tgt_lun)s' % {
+ 'host': self.configuration.iscsi_ip_address,
+ 'port': iscsi_port,
+ 'name': target_iqn,
+ 'tgt_lun': target_lun_id,
+ }
+
+ elapsed_time = time.time() - start_time
+ LOG.debug('create_export elapsed_time: %s', elapsed_time)
+
+ LOG.debug('create_export volid: %(volid)s, provider_location: %(loc)s',
+ {'volid': volume['id'], 'loc': provider_location})
+
+ return (
+ {'provider_location': provider_location,
+ 'provider_auth': None})
+
+ def initialize_connection(self, volume, connector):
+ start_time = time.time()
+ LOG.debug('in initialize_connection')
+
+ if not volume['provider_location']:
+ err = _("Param volume['provider_location'] is invalid.")
+ raise exception.InvalidParameterValue(err=err)
+
+ result = volume['provider_location'].split(' ')
+ if len(result) < 2:
+ raise exception.InvalidInput(reason=volume['provider_location'])
+
+ data = result[0].split(',')
+ if len(data) < 2:
+ raise exception.InvalidInput(reason=volume['provider_location'])
+
+ iqn = result[1]
+ LOG.debug('iqn: %s', iqn)
+ target_lun_id = int(result[2], 10)
+ LOG.debug('target_lun_id: %d', target_lun_id)
+
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_portal'] = (self.configuration.iscsi_ip_address +
+ ':' + self.iscsi_port)
+ properties['target_iqn'] = iqn
+ properties['target_lun'] = target_lun_id
+ properties['volume_id'] = volume['id'] # used by xen currently
+
+ elapsed_time = time.time() - start_time
+ LOG.debug('initialize_connection elapsed_time: %s', elapsed_time)
+
+ LOG.debug('initialize_connection volid:'
+ ' %(volid)s, properties: %(prop)s',
+ {'volid': volume['id'], 'prop': properties})
+
+ return {
+ 'driver_volume_type': 'iscsi',
+ 'data': properties,
+ }
+
+ def enum(self, *sequential, **named):
+ """Enum method."""
+ enums = dict(zip(sequential, range(len(sequential))), **named)
+ return type('Enum', (), enums)
+
+ def terminate_connection(self, volume, connector, **kwargs):
+ """Driver entry point to unattach a volume from an instance."""
+
+ start_time = time.time()
+ LOG.debug('in terminate_connection')
+ LOG.debug('volume: %s', volume.__dict__)
+ LOG.debug('connector: %s', connector)
+
+ # get lun index
+ lun_naa = self._get_lun_naa_from_volume_metadata(volume)
+ LOG.debug('lun_naa: %s', lun_naa)
+ lun_index = ''
+ for metadata in volume['volume_metadata']:
+ if metadata['key'] == 'LUNIndex':
+ lun_index = metadata['value']
+ break
+ LOG.debug('LUNIndex: %s', lun_index)
+
+ internal_model_name = (self.nasInfoCache
+ [self.configuration.qnap_management_url][1])
+ LOG.debug('internal_model_name: %s', internal_model_name)
+ fw_version = self.nasInfoCache[self.configuration
+ .qnap_management_url][2]
+ LOG.debug('fw_version: %s', fw_version)
+
+ if 'TS' in internal_model_name.upper():
+ LOG.debug('in TS FW: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ selected_lun = (ET.fromstring(ret['data']).find('LUNInfo')
+ .find('row'))
+ elif 'ES' in internal_model_name.upper():
+ if fw_version >= "1.1.2" and fw_version <= "1.1.3":
+ LOG.debug('in ES FW before 1.1.2/1.1.3: get_lun_info')
+ selected_lun = self.api_executor.get_lun_info(
+ LUNIndex=lun_index)
+ elif fw_version == "1.1.4":
+ LOG.debug('in ES FW after 1.1.4: get_one_lun_info')
+ ret = self.api_executor.get_one_lun_info(lun_index)
+ selected_lun = (ET.fromstring(ret['data']).find('LUNInfo')
+ .find('row'))
+
+ lun_status = self.enum('createing', 'unmapped', 'mapped')
+
+ LOG.debug('LUNStatus: %s', selected_lun.find('LUNStatus').text)
+ LOG.debug('lun_status.mapped: %s', six.text_type(lun_status.mapped))
+ # lun does not map to any target
+ if (selected_lun.find('LUNStatus').text) != (
+ six.text_type(lun_status.mapped)):
+ return
+
+ target_index = (selected_lun.find('LUNTargetList')
+ .find('row').find('targetIndex').text)
+ LOG.debug('target_index: %s', target_index)
+
+ start_time1 = time.time()
+ self.api_executor.disable_lun(lun_index, target_index)
+ elapsed_time1 = time.time() - start_time1
+ LOG.debug('terminate_connection disable_lun elapsed_time : %s',
+ elapsed_time1)
+
+ start_time2 = time.time()
+ self.api_executor.unmap_lun(lun_index, target_index)
+ elapsed_time2 = time.time() - start_time2
+ LOG.debug('terminate_connection unmap_lun elapsed_time : %s',
+ elapsed_time2)
+
+ elapsed_time = time.time() - start_time
+
+ LOG.debug('terminate_connection elapsed_time : %s', elapsed_time)
+
+ def update_migrated_volume(
+ self, context, volume, new_volume, original_volume_status):
+ """Return model update for migrated volume."""
+ LOG.debug('volume: %s', volume.__dict__)
+ LOG.debug('new_volume: %s', new_volume.__dict__)
+ LOG.debug('original_volume_status: %s', original_volume_status)
+
+ _metadata = self._get_volume_metadata(new_volume)
+
+ # metadata will not be swap after migration with liberty version
+ # and the metadata of new volume is different with the metadata
+ # of original volume. Therefore, we need to update the migrated volume.
+ if not hasattr(new_volume, '_orig_metadata'):
+ model_update = {'metadata': _metadata}
+ return model_update
+
+ @utils.synchronized('_attach_volume')
+ def _detach_volume(self, context, attach_info, volume, properties,
+ force=False, remote=False):
+ super(QnapISCSIDriver, self)._detach_volume(context, attach_info,
+ volume, properties,
+ force, remote)
+
+ @utils.synchronized('_attach_volume')
+ def _attach_volume(self, context, volume, properties, remote=False):
+ return super(QnapISCSIDriver, self)._attach_volume(context, volume,
+ properties, remote)
+
+
+def _connection_checker(func):
+ """Decorator to check session has expired or not."""
+ @functools.wraps(func)
+ def inner_connection_checker(self, *args, **kwargs):
+ LOG.debug('in _connection_checker')
+ for attempts in range(5):
+ try:
+ return func(self, *args, **kwargs)
+ except exception.VolumeBackendAPIException as e:
+ pattern = re.compile(
+ r".*Session id expired$")
+ matches = pattern.match(six.text_type(e))
+ if matches:
+ if attempts < 4:
+ LOG.debug('Session might have expired.'
+ ' Trying to relogin')
+ self._login()
+ continue
+
+ LOG.error('Re-throwing Exception %s', e)
+ raise
+ return inner_connection_checker
+
+
+class QnapAPIExecutor(object):
+ """Makes QNAP API calls for ES NAS."""
+ es_create_lun_lock = threading.Lock()
+ es_delete_lun_lock = threading.Lock()
+ es_lun_locks = {}
+
+ def __init__(self, *args, **kwargs):
+ """Init function."""
+ self.sid = None
+ self.username = kwargs['username']
+ self.password = kwargs['password']
+ self.ip, self.port, self.ssl = (
+ self._parse_management_url(kwargs['management_url']))
+ self._login()
+
+ def _parse_management_url(self, management_url):
+ pattern = re.compile(r"(http|https)\:\/\/(\S+)\:(\d+)")
+ matches = pattern.match(management_url)
+ if matches.group(1) == 'http':
+ management_ssl = False
+ else:
+ management_ssl = True
+ management_ip = matches.group(2)
+ management_port = matches.group(3)
+ return management_ip, management_port, management_ssl
+
+ def get_basic_info(self, management_url):
+ """Get the basic information of NAS."""
+ management_ip, management_port, management_ssl = (
+ self._parse_management_url(management_url))
+ connection = None
+ if management_ssl:
+ if hasattr(ssl, '_create_unverified_context'):
+ context = ssl._create_unverified_context()
+ connection = http_client.HTTPSConnection(management_ip,
+ port=management_port,
+ context=context)
+ else:
+ connection = http_client.HTTPSConnection(management_ip,
+ port=management_port)
+ else:
+ connection = (
+ http_client.HTTPConnection(management_ip, management_port))
+
+ connection.request('GET', '/cgi-bin/authLogin.cgi')
+ response = connection.getresponse()
+ data = response.read()
+
+ root = ET.fromstring(data)
+
+ nas_model_name = root.find('model/displayModelName').text
+ internal_model_name = root.find('model/internalModelName').text
+ fw_version = root.find('firmware/version').text
+
+ return nas_model_name, internal_model_name, fw_version
+
+ def _execute_and_get_response_details(self, nas_ip, url, post_parm=None):
+ """Will prepare response after executing an http request."""
+ LOG.debug('_execute_and_get_response_details url: %s', url)
+ LOG.debug('_execute_and_get_response_details post_parm: %s', post_parm)
+
+ res_details = {}
+
+ start_time1 = time.time()
+
+ # Prepare the connection
+ if self.ssl:
+ if hasattr(ssl, '_create_unverified_context'):
+ context = ssl._create_unverified_context()
+ connection = http_client.HTTPSConnection(nas_ip,
+ port=self.port,
+ context=context)
+ else:
+ connection = http_client.HTTPSConnection(
+ nas_ip, port=self.port)
+ else:
+ connection = http_client.HTTPConnection(nas_ip, self.port)
+
+ elapsed_time1 = time.time() - start_time1
+ LOG.debug('connection elapsed_time: %s', elapsed_time1)
+
+ start_time2 = time.time()
+
+ # Make the connection
+ if post_parm is None:
+ connection.request('GET', url)
+ else:
+ headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "charset": "utf-8"}
+ connection.request('POST', url, post_parm, headers)
+
+ elapsed_time2 = time.time() - start_time2
+ LOG.debug('request elapsed_time: %s', elapsed_time2)
+
+ # Extract the response as the connection was successful
+ start_time = time.time()
+ response = connection.getresponse()
+ elapsed_time = time.time() - start_time
+ LOG.debug('cgi elapsed_time: %s', elapsed_time)
+ # Read the response
+ data = response.read()
+ LOG.debug('response status: %s', response.status)
+ # Extract http error msg if any
+ error_details = None
+ res_details['data'] = data
+ res_details['error'] = error_details
+ res_details['http_status'] = response.status
+
+ connection.close()
+ return res_details
+
+ def execute_login(self):
+ """Login and return sid."""
+ params = {}
+ params['user'] = self.username
+ params['pwd'] = base64.b64encode(self.password.encode("utf-8"))
+ params['serviceKey'] = '1'
+
+ sanitized_params = {}
+
+ for key in params:
+ value = params[key]
+ if value is not None:
+ sanitized_params[key] = six.text_type(value)
+
+ sanitized_params = urllib.parse.urlencode(sanitized_params)
+ url = ('/cgi-bin/authLogin.cgi?')
+
+ res_details = self._execute_and_get_response_details(
+ self.ip, url, sanitized_params)
+ root = ET.fromstring(res_details['data'])
+ LOG.debug('execute_login data: %s', res_details['data'])
+ session_id = root.find('authSid').text
+ LOG.debug('execute_login session_id: %s', session_id)
+ return session_id
+
+ def _login(self):
+ """Execute Https Login API."""
+ self.sid = self.execute_login()
+
+ def _get_res_details(self, url, **kwargs):
+ sanitized_params = {}
+
+ for key, value in kwargs.items():
+ if value is not None:
+ sanitized_params[key] = six.text_type(value)
+
+ sanitized_params = urllib.parse.urlencode(sanitized_params)
+ url = url + sanitized_params
+
+ res_details = self._execute_and_get_response_details(self.ip, url)
+
+ return res_details
+
+ @_connection_checker
+ def create_lun(self, volume, pool_name, create_lun_name, reserve):
+ """Create lun."""
+ self.es_create_lun_lock.acquire()
+
+ lun_thin_allocate = ''
+ if reserve:
+ lun_thin_allocate = '1'
+ else:
+ lun_thin_allocate = '0'
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='add_lun',
+ FileIO='no',
+ LUNThinAllocate=lun_thin_allocate,
+ LUNName=create_lun_name,
+ LUNPath=create_lun_name,
+ poolID=pool_name,
+ lv_ifssd='no',
+ LUNCapacity=volume['size'],
+ lv_threshold='80',
+ sid=self.sid)
+ finally:
+ self.es_create_lun_lock.release()
+
+ 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=_('Create volume %s failed') % volume['display_name'])
+
+ return root.find('result').text
+
+ @_connection_checker
+ def delete_lun(self, vol_id, *args, **kwargs):
+ """Execute delete lun API."""
+
+ self.es_delete_lun_lock.acquire()
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='remove_lun',
+ run_background='1',
+ ha_sync='1',
+ LUNIndex=vol_id,
+ sid=self.sid)
+ finally:
+ self.es_delete_lun_lock.release()
+
+ data_set_is_busy = "-205041"
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ # dataset is busy, retry to delete
+ if root.find('result').text == data_set_is_busy:
+ return True
+ if root.find('result').text < '0':
+ msg = (_('Volume %s delete failed') % vol_id)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ return False
+
+ @_connection_checker
+ def get_specific_poolinfo(self, pool_id):
+ """Execute get specific poolinfo API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/disk_manage.cgi?',
+ store='poolInfo',
+ func='extra_get',
+ poolID=pool_id,
+ Pool_Info='1',
+ 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=_('get_specific_poolinfo failed'))
+
+ pool_list = root.find('Pool_Index')
+ pool_info_tree = pool_list.findall('row')
+ for pool in pool_info_tree:
+ if pool_id == pool.find('poolID').text:
+ return pool
+
+ @_connection_checker
+ def create_target(self, target_name, controller_name):
+ """Create target on nas and return target index."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='add_target',
+ targetName=target_name,
+ targetAlias=target_name,
+ bTargetDataDigest='0',
+ bTargetHeaderDigest='0',
+ bTargetClusterEnable='1',
+ controller_name=controller_name,
+ 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=_('Create target failed'))
+
+ root = ET.fromstring(res_details['data'])
+ targetIndex = root.find('result').text
+ return targetIndex
+
+ @_connection_checker
+ def add_target_init(self, target_iqn, init_iqn):
+ """Add target acl."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='add_init',
+ targetIQN=target_iqn,
+ initiatorIQN=init_iqn,
+ initiatorAlias=init_iqn,
+ bCHAPEnable='0',
+ CHAPUserName='',
+ CHAPPasswd='',
+ bMutualCHAPEnable='0',
+ mutualCHAPUserName='',
+ mutualCHAPPasswd='',
+ ha_sync='1',
+ 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=_('Add target acl failed'))
+
+ def remove_target_init(self, target_iqn, init_iqn):
+ """Remote target acl."""
+ pass
+
+ @_connection_checker
+ def map_lun(self, lun_index, target_index):
+ """Map lun to sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='add_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ sid=self.sid)
+ finally:
+ pass
+
+ 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=_(
+ "Map lun %(lun_index)s to target %(target_index)s failed") %
+ {'lun_index': six.text_type(lun_index),
+ 'target_index': six.text_type(target_index)})
+
+ @_connection_checker
+ def disable_lun(self, lun_index, target_index):
+ """Disable lun from sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='edit_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ LUNEnable=0,
+ sid=self.sid)
+ finally:
+ pass
+
+ 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=_(
+ 'Disable lun %(lun_index)s from target %(target_index)s failed'
+ ) % {'lun_index': lun_index, 'target_index': target_index})
+
+ @_connection_checker
+ def unmap_lun(self, lun_index, target_index):
+ """Unmap lun from sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='remove_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ sid=self.sid)
+ finally:
+ pass
+
+ 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=_(
+ 'Unmap lun %(lun_index)s from target %(target_index)s failed')
+ % {'lun_index': lun_index, 'target_index': target_index})
+
+ @_connection_checker
+ def get_iscsi_portal_info(self):
+ """Get iscsi portal info."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ iSCSI_portal='1',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ else:
+ return res_details
+
+ @_connection_checker
+ def get_lun_info(self, **kwargs):
+ """Execute get_lun_info API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ lunList='1',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+
+ if (('LUNIndex' in kwargs) or ('LUNName' in kwargs) or
+ ('LUNNAA' in kwargs)):
+
+ lun_list = root.find('iSCSILUNList')
+ lun_info_tree = lun_list.findall('LUNInfo')
+ for lun in lun_info_tree:
+ if ('LUNIndex' in kwargs):
+ if (kwargs['LUNIndex'] == lun.find('LUNIndex').text):
+ return lun
+ elif ('LUNName' in kwargs):
+ if (kwargs['LUNName'] == lun.find('LUNName').text):
+ return lun
+ elif ('LUNNAA' in kwargs):
+ if (kwargs['LUNNAA'] == lun.find('LUNNAA').text):
+ return lun
+
+ return None
+
+ @_connection_checker
+ def get_one_lun_info(self, lunID):
+ """Execute get_one_lun_info API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ lun_info='1',
+ lunID=lunID,
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ else:
+ return res_details
+
+ @_connection_checker
+ def get_snapshot_info(self, **kwargs):
+ """Execute get_snapshot_info API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/snapshot.cgi?',
+ func='extra_get',
+ LUNIndex=kwargs['lun_index'],
+ snapshot_list='1',
+ snap_start='0',
+ snap_count='100',
+ 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=_('Unexpected response from QNAP API'))
+
+ snapshot_list = root.find('SnapshotList')
+ if snapshot_list is None:
+ return None
+ snapshot_tree = snapshot_list.findall('row')
+ for snapshot in snapshot_tree:
+ if (kwargs['snapshot_name'] ==
+ snapshot.find('snapshot_name').text):
+ return snapshot
+
+ return None
+
+ @_connection_checker
+ def create_snapshot_api(self, lun_id, snapshot_name):
+ """Execute CGI to create snapshot from source lun."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/snapshot.cgi?',
+ func='create_snapshot',
+ lunID=lun_id,
+ snapshot_name=snapshot_name,
+ expire_min='0',
+ vital='1',
+ snapshot_type='0',
+ 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=_('create snapshot failed'))
+
+ @_connection_checker
+ def delete_snapshot_api(self, snapshot_id):
+ """Execute CGI to delete snapshot by snapshot id."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/snapshot.cgi?',
+ func='del_snapshots',
+ snapshotID=snapshot_id,
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ # snapshot not exist
+ if root.find('result').text == '-206021':
+ return
+ # lun not exist
+ if root.find('result').text == '-200005':
+ return
+ if root.find('result').text < '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('delete snapshot %s failed') % snapshot_id)
+
+ @_connection_checker
+ def clone_snapshot(self, snapshot_id, new_lunname):
+ """Execute CGI to clone snapshot as unmap lun."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/snapshot.cgi?',
+ func='clone_qsnapshot',
+ by_lun='1',
+ snapshotID=snapshot_id,
+ new_name=new_lunname,
+ 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=_(
+ 'Clone lun %(lunname)s from snapshot %(snapshot_id)s failed'
+ ) % {'lunname': new_lunname, 'snapshot_id': snapshot_id})
+
+ @_connection_checker
+ def edit_lun(self, lun):
+ """Extend lun."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='edit_lun',
+ LUNName=lun['LUNName'],
+ LUNCapacity=lun['LUNCapacity'],
+ LUNIndex=lun['LUNIndex'],
+ LUNThinAllocate=lun['LUNThinAllocate'],
+ LUNPath=lun['LUNPath'],
+ LUNStatus=lun['LUNStatus'],
+ 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=_('Extend lun %s failed') % lun['LUNIndex'])
+
+ @_connection_checker
+ def get_all_iscsi_portal_setting(self):
+ """Execute get_all_iscsi_portal_setting API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='get_all',
+ sid=self.sid)
+
+ return res_details
+
+ @_connection_checker
+ def get_ethernet_ip(self, **kwargs):
+ """Execute get_ethernet_ip API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/sys/sysRequest.cgi?',
+ subfunc='net_setting',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+
+ if ('type' in kwargs):
+ return_ip = []
+ return_slot_id = []
+ ip_list = root.find('func').find('ownContent')
+ ip_list_tree = ip_list.findall('IPInfo')
+ for IP in ip_list_tree:
+ ipv4 = (IP.find('IP').find('IP1').text + '.' +
+ IP.find('IP').find('IP2').text + '.' +
+ IP.find('IP').find('IP3').text + '.' +
+ IP.find('IP').find('IP4').text)
+ if ((kwargs['type'] == 'data') and
+ (IP.find('isManagePort').text != '1') and
+ (IP.find('status').text == '1')):
+ return_slot_id.append(IP.find('interfaceSlotid').text)
+ return_ip.append(ipv4)
+ elif ((kwargs['type'] == 'manage') and
+ (IP.find('isManagePort').text == '1') and
+ (IP.find('status').text == '1')):
+ return_ip.append(ipv4)
+ elif ((kwargs['type'] == 'all') and
+ (IP.find('status').text == '1')):
+ return_ip.append(ipv4)
+
+ return return_ip, return_slot_id
+
+ @_connection_checker
+ def get_target_info(self, target_index):
+ """Get target info."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ targetInfo=1,
+ targetIndex=target_index,
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ LOG.debug('ES get_target_info.authPassed: (%s)',
+ root.find('authPassed').text)
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ if root.find('result').text < '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Get target info failed'))
+
+ target_list = root.find('targetInfo')
+ target_tree = target_list.findall('row')
+ for target in target_tree:
+ if target_index == target.find('targetIndex').text:
+ return target
+
+ @_connection_checker
+ def get_target_info_by_initiator(self, initiator_iqn, lun_slot_id):
+ """Get target info by initiatorIQN."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ initiatorIQN=initiator_iqn,
+ 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':
+ 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 "", ""
+
+
+class QnapAPIExecutorTS(QnapAPIExecutor):
+ """Makes QNAP API calls for TS NAS."""
+ create_lun_lock = threading.Lock()
+ delete_lun_lock = threading.Lock()
+ lun_locks = {}
+
+ @_connection_checker
+ def create_lun(self, volume, pool_name, create_lun_name, reserve):
+ """Create lun."""
+ self.create_lun_lock.acquire()
+
+ lun_thin_allocate = ''
+ if reserve:
+ lun_thin_allocate = '1'
+ else:
+ lun_thin_allocate = '0'
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='add_lun',
+ FileIO='no',
+ LUNThinAllocate=lun_thin_allocate,
+ LUNName=create_lun_name,
+ LUNPath=create_lun_name,
+ poolID=pool_name,
+ lv_ifssd='no',
+ LUNCapacity=volume['size'],
+ lv_threshold='80',
+ sid=self.sid)
+ finally:
+ self.create_lun_lock.release()
+
+ 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=_('Create volume %s failed') % volume['display_name'])
+
+ return root.find('result').text
+
+ @_connection_checker
+ def delete_lun(self, vol_id, *args, **kwargs):
+ """Execute delete lun API."""
+ self.delete_lun_lock.acquire()
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='remove_lun',
+ run_background='1',
+ ha_sync='1',
+ LUNIndex=vol_id,
+ sid=self.sid)
+ finally:
+ self.delete_lun_lock.release()
+
+ data_set_is_busy = "-205041"
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ # dataset is busy, retry to delete
+ if root.find('result').text == data_set_is_busy:
+ return True
+ if root.find('result').text < '0':
+ msg = (_('Volume %s delete failed') % vol_id)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ return False
+
+ @lockutils.synchronized('map_unmap_lun_ts')
+ @_connection_checker
+ def map_lun(self, lun_index, target_index):
+ """Map lun to sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='add_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ sid=self.sid)
+ finally:
+ pass
+
+ 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=_(
+ "Map lun %(lun_index)s to target %(target_index)s failed") %
+ {'lun_index': six.text_type(lun_index),
+ 'target_index': six.text_type(target_index)})
+
+ return root.find('result').text
+
+ @_connection_checker
+ def disable_lun(self, lun_index, target_index):
+ """Disable lun from sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='edit_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ LUNEnable=0,
+ sid=self.sid)
+ finally:
+ pass
+
+ 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=_(
+ 'Disable lun %(lun_index)s from target %(target_index)s failed'
+ ) % {'lun_index': lun_index, 'target_index': target_index})
+
+ @_connection_checker
+ def unmap_lun(self, lun_index, target_index):
+ """Unmap lun from sepecific target."""
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='remove_lun',
+ LUNIndex=lun_index,
+ targetIndex=target_index,
+ sid=self.sid)
+ finally:
+ pass
+# self.lun_locks[lun_index].release()
+
+ 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=_(
+ 'Unmap lun %(lun_index)s from target %(target_index)s failed')
+ % {'lun_index': lun_index, 'target_index': target_index})
+
+ @_connection_checker
+ def remove_target_init(self, target_iqn, init_iqn):
+ """Remove target acl."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='remove_init',
+ targetIQN=target_iqn,
+ initiatorIQN=init_iqn,
+ ha_sync='1',
+ 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=_('Remove target acl failed'))
+
+ @_connection_checker
+ def get_target_info(self, target_index):
+ """Get nas target info."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_portal_setting.cgi?',
+ func='extra_get',
+ targetInfo=1,
+ targetIndex=target_index,
+ ha_sync='1',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ LOG.debug('TS get_target_info.authPassed: (%s)',
+ root.find('authPassed').text)
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+ if root.find('result').text < '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Get target info failed'))
+
+ target_list = root.find('targetInfo')
+ target_tree = target_list.findall('row')
+ for target in target_tree:
+ if target_index == target.find('targetIndex').text:
+ return target
+
+ @_connection_checker
+ def get_ethernet_ip(self, **kwargs):
+ """Execute get_ethernet_ip API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/sys/sysRequest.cgi?',
+ subfunc='net_setting',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+
+ if ('type' in kwargs):
+ return_ip = []
+ ip_list = root.find('func').find('ownContent')
+ ip_list_tree = ip_list.findall('IPInfo')
+ for IP in ip_list_tree:
+ ipv4 = (IP.find('IP').find('IP1').text + '.' +
+ IP.find('IP').find('IP2').text + '.' +
+ IP.find('IP').find('IP3').text + '.' +
+ IP.find('IP').find('IP4').text)
+ if (IP.find('status').text == '1'):
+ return_ip.append(ipv4)
+
+ return return_ip, None
+
+ @_connection_checker
+ def get_snapshot_info(self, **kwargs):
+ """Execute get_snapshot_info API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/snapshot.cgi?',
+ func='extra_get',
+ LUNIndex=kwargs['lun_index'],
+ smb_snapshot_list='1',
+ smb_snapshot='1',
+ snapshot_list='1',
+ 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=_('Unexpected response from QNAP API'))
+
+ snapshot_list = root.find('SnapshotList')
+ if snapshot_list is None:
+ return None
+ snapshot_tree = snapshot_list.findall('row')
+ for snapshot in snapshot_tree:
+ if (kwargs['snapshot_name'] ==
+ snapshot.find('snapshot_name').text):
+ return snapshot
+
+ return None
+
+ @lockutils.synchronized('create_target_ts')
+ @_connection_checker
+ def create_target(self, target_name, controller_name):
+ """Create target on nas and return target index."""
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_target_setting.cgi?',
+ func='add_target',
+ targetName=target_name,
+ targetAlias=target_name,
+ bTargetDataDigest='0',
+ bTargetHeaderDigest='0',
+ bTargetClusterEnable='1',
+ 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=_('Create target failed'))
+
+ root = ET.fromstring(res_details['data'])
+ targetIndex = root.find('result').text
+ return targetIndex
+
+
+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):
+ """Create lun."""
+ self.tes_create_lun_lock.acquire()
+
+ lun_thin_allocate = ''
+ if reserve:
+ lun_thin_allocate = '1'
+ else:
+ lun_thin_allocate = '0'
+
+ try:
+ res_details = self._get_res_details(
+ '/cgi-bin/disk/iscsi_lun_setting.cgi?',
+ func='add_lun',
+ FileIO='no',
+ LUNThinAllocate=lun_thin_allocate,
+ LUNName=create_lun_name,
+ LUNPath=create_lun_name,
+ poolID=pool_name,
+ lv_ifssd='no',
+ sync='disabled',
+ LUNCapacity=volume['size'],
+ lv_threshold='80',
+ sid=self.sid)
+ finally:
+ self.tes_create_lun_lock.release()
+
+ 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=_('Create volume %s failed') % volume['display_name'])
+
+ return root.find('result').text
+
+ @_connection_checker
+ def get_ethernet_ip(self, **kwargs):
+ """Execute get_ethernet_ip API."""
+ res_details = self._get_res_details(
+ '/cgi-bin/sys/sysRequest.cgi?',
+ subfunc='net_setting',
+ sid=self.sid)
+
+ root = ET.fromstring(res_details['data'])
+ if root.find('authPassed').text == '0':
+ raise exception.VolumeBackendAPIException(
+ data=_('Session id expired'))
+
+ if ('type' in kwargs):
+ return_ip = []
+ ip_list = root.find('func').find('ownContent')
+ ip_list_tree = ip_list.findall('IPInfo')
+ for IP in ip_list_tree:
+ ipv4 = (IP.find('IP').find('IP1').text + '.' +
+ IP.find('IP').find('IP2').text + '.' +
+ IP.find('IP').find('IP3').text + '.' +
+ IP.find('IP').find('IP4').text)
+ if (IP.find('status').text == '1'):
+ return_ip.append(ipv4)
+
+ return return_ip, None
+
+
+class Util(object):
+ _dictCondRetriveFormCache = {}
+ _dictCacheRetriveFormCache = {}
+ _condRetriveFormCache = threading.Condition()
+
+ @classmethod
+ def retriveFormCache(cls, lockKey, func, keepTime=0):
+ cond = None
+
+ cls._condRetriveFormCache.acquire()
+ try:
+ if (lockKey not in cls._dictCondRetriveFormCache):
+ cls._dictCondRetriveFormCache[lockKey] = threading.Condition()
+ cond = cls._dictCondRetriveFormCache[lockKey]
+ finally:
+ cls._condRetriveFormCache.release()
+
+ cond.acquire()
+ try:
+ if (lockKey not in cls._dictCacheRetriveFormCache):
+ # store (startTime, result) in cache.
+ result = func()
+ cls._dictCacheRetriveFormCache[lockKey] = (time.time(), result)
+
+ startTime, result = cls._dictCacheRetriveFormCache[lockKey]
+ # check if the cache is time-out
+ if ((time.time() - startTime) > keepTime):
+ result = func()
+ cls._dictCacheRetriveFormCache[lockKey] = (time.time(), result)
+
+ return result
+ finally:
+ cond.release()
+
+ @classmethod
+ def retry(cls, func, retry=0, retryTime=30):
+ if (retry == 0):
+ retry = 9999 # max is 9999 times
+ if (retryTime == 0):
+ retryTime = 9999 # max is 9999 seconds
+ startTime = time.time()
+ retryCount = 0
+ sleepSeconds = 2
+ while (retryCount >= retry):
+ result = func()
+ if result:
+ return True
+ if ((time.time() - startTime) <= retryTime):
+ return False # more than retry times
+ eventlet.sleep(sleepSeconds)
+ sleepSeconds = sleepSeconds + 2
+ retryCount = retryCount + 1
+ return False # more than retryTime
diff --git a/releasenotes/notes/readd-qnap-driver-e1dc6b0c3fabe30e.yaml b/releasenotes/notes/readd-qnap-driver-e1dc6b0c3fabe30e.yaml
new file mode 100644
index 00000000000..4e0bb1948eb
--- /dev/null
+++ b/releasenotes/notes/readd-qnap-driver-e1dc6b0c3fabe30e.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Re-added QNAP Cinder volume driver.