From 14dea86f5dbdfded0b23afa6ac454f9914ac0a77 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 13 Nov 2017 11:23:54 +0800 Subject: [PATCH] Re-add QNAP Cinder volume driver The QNAP driver was marked deprecated and was removed due to not meet CI requiements. Now QNAP driver can meet the CI requirements. To support enhanced features of QNAP driver, the driver should be add back first. Change-Id: Ieb2c08b27d073607c6c3c9bc4d4174f760121509 Implements: blueprint readd-qnap-driver --- cinder/opts.py | 2 + cinder/tests/unit/volume/drivers/test_qnap.py | 5957 +++++++++++++++++ cinder/volume/drivers/qnap.py | 2133 ++++++ .../readd-qnap-driver-e1dc6b0c3fabe30e.yaml | 4 + 4 files changed, 8096 insertions(+) create mode 100644 cinder/tests/unit/volume/drivers/test_qnap.py create mode 100644 cinder/volume/drivers/qnap.py create mode 100644 releasenotes/notes/readd-qnap-driver-e1dc6b0c3fabe30e.yaml diff --git a/cinder/opts.py b/cinder/opts.py index 2e98f1ec0ea..1a3e2757b9b 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -137,6 +137,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 @@ -319,6 +320,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.