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