Remove old iSCSI initiator utils modules
Now that we have an iSCSI initiator module that uses the iscsidsc lib directly, there is no need to keep the old modules that were using iscsicli.exe or WMI for interacting with the MS iSCSI initiator. Nova stopped using the old modules since Mitaka. As 2 cycles have passed, we can now completely remove them along with the according config option. Change-Id: Id514a15646aaacb8f90c998ae51381e2a868edd1
This commit is contained in:
@@ -32,7 +32,6 @@ from os_win.utils import hostutils
|
||||
from os_win.utils.network import networkutils
|
||||
from os_win.utils import pathutils
|
||||
from os_win.utils.storage import diskutils
|
||||
from os_win.utils.storage.initiator import iscsi_cli_utils
|
||||
from os_win.utils.storage.initiator import iscsi_utils
|
||||
from os_win.utils.storage import smbutils
|
||||
from os_win.utils.storage.virtdisk import vhdutils
|
||||
@@ -107,25 +106,9 @@ class TestHyperVUtilsFactory(test_base.OsWinBaseTestCase):
|
||||
self._check_get_class(expected_class=rdpconsoleutils.RDPConsoleUtils,
|
||||
class_type='rdpconsoleutils')
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, '__init__',
|
||||
lambda *args, **kwargs: None)
|
||||
def test_get_iscsi_initiator_utils(self):
|
||||
self._test_get_initiator_utils(
|
||||
expected_class=iscsi_utils.ISCSIInitiatorUtils)
|
||||
|
||||
def test_get_iscsi_initiator_utils_force_v1(self):
|
||||
self._test_get_initiator_utils(
|
||||
expected_class=iscsi_cli_utils.ISCSIInitiatorCLIUtils,
|
||||
force_v1=True)
|
||||
|
||||
@mock.patch.object(utilsfactory.utils, 'get_windows_version')
|
||||
def _test_get_initiator_utils(self, mock_get_windows_version,
|
||||
expected_class, force_v1=False):
|
||||
CONF.set_override('force_volumeutils_v1', force_v1, 'hyperv')
|
||||
mock_get_windows_version.return_value = '6.2'
|
||||
|
||||
actual_class = type(utilsfactory.get_iscsi_initiator_utils())
|
||||
self.assertEqual(expected_class, actual_class)
|
||||
self._check_get_class(expected_class=iscsi_utils.ISCSIInitiatorUtils,
|
||||
class_type='iscsi_initiator_utils')
|
||||
|
||||
@mock.patch('os_win.utils.storage.initiator.fc_utils.FCUtils')
|
||||
def test_get_fc_utils(self, mock_cls_fcutils):
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage.initiator import base_iscsi_utils
|
||||
|
||||
|
||||
class BaseISCSIInitiatorUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V BaseISCSIInitiatorUtils class."""
|
||||
|
||||
_FAKE_COMPUTER_NAME = "fake_computer_name"
|
||||
_FAKE_DOMAIN_NAME = "fake_domain_name"
|
||||
_FAKE_INITIATOR_NAME = "fake_initiator_name"
|
||||
_FAKE_INITIATOR_IQN_NAME = "iqn.1991-05.com.microsoft:fake_computer_name"
|
||||
_FAKE_DISK_PATH = 'fake_path DeviceID="123\\\\2"'
|
||||
_FAKE_MOUNT_DEVICE = '/dev/fake/mount'
|
||||
_FAKE_DEVICE_NAME = '/dev/fake/path'
|
||||
_FAKE_SWAP = {'device_name': _FAKE_DISK_PATH}
|
||||
|
||||
def setUp(self):
|
||||
self._utils = base_iscsi_utils.BaseISCSIInitiatorUtils()
|
||||
self._utils._conn_wmi = mock.MagicMock()
|
||||
self._utils._conn_cimv2 = mock.MagicMock()
|
||||
|
||||
super(BaseISCSIInitiatorUtilsTestCase, self).setUp()
|
||||
|
||||
def test_get_iscsi_initiator_ok(self):
|
||||
self._check_get_iscsi_initiator(
|
||||
self._FAKE_INITIATOR_NAME)
|
||||
|
||||
def test_get_iscsi_initiator_exception(self):
|
||||
initiator_name = "%(iqn)s.%(domain)s" % {
|
||||
'iqn': self._FAKE_INITIATOR_IQN_NAME,
|
||||
'domain': self._FAKE_DOMAIN_NAME
|
||||
}
|
||||
|
||||
self._check_get_iscsi_initiator(initiator_name,
|
||||
side_effect=Exception)
|
||||
|
||||
def _check_get_iscsi_initiator(self, expected=None, side_effect=None):
|
||||
mock_computer = mock.MagicMock()
|
||||
mock_computer.name = self._FAKE_COMPUTER_NAME
|
||||
mock_computer.Domain = self._FAKE_DOMAIN_NAME
|
||||
self._utils._conn_cimv2.Win32_ComputerSystem.return_value = [
|
||||
mock_computer]
|
||||
|
||||
expected_key_path = (
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
|
||||
"iSCSI\\Discovery")
|
||||
|
||||
with mock.patch.object(base_iscsi_utils,
|
||||
'winreg', create=True) as mock_winreg:
|
||||
mock_winreg.CloseKey.side_effect = side_effect
|
||||
mock_winreg.QueryValueEx.return_value = [expected]
|
||||
mock_winreg.OpenKey.return_value = mock.sentinel.key
|
||||
|
||||
initiator_name = self._utils.get_iscsi_initiator()
|
||||
self.assertEqual(expected, initiator_name)
|
||||
mock_winreg.OpenKey.assert_called_once_with(
|
||||
mock_winreg.HKEY_LOCAL_MACHINE,
|
||||
expected_key_path,
|
||||
0,
|
||||
mock_winreg.KEY_WOW64_64KEY + mock_winreg.KEY_ALL_ACCESS)
|
||||
mock_winreg.QueryValueEx.assert_called_once_with(
|
||||
mock.sentinel.key, "DefaultInitiatorName")
|
||||
mock_winreg.CloseKey.assert_called_once_with(mock.sentinel.key)
|
||||
|
||||
def test_get_drive_number_from_disk_path(self):
|
||||
fake_disk_path = (
|
||||
'\\\\WIN-I5BTVHOIFGK\\root\\virtualization\\v2:Msvm_DiskDrive.'
|
||||
'CreationClassName="Msvm_DiskDrive",DeviceID="Microsoft:353B3BE8-'
|
||||
'310C-4cf4-839E-4E1B14616136\\\\1",SystemCreationClassName='
|
||||
'"Msvm_ComputerSystem",SystemName="WIN-I5BTVHOIFGK"')
|
||||
expected_disk_number = 1
|
||||
|
||||
ret_val = self._utils._get_drive_number_from_disk_path(
|
||||
fake_disk_path)
|
||||
|
||||
self.assertEqual(expected_disk_number, ret_val)
|
||||
|
||||
def test_get_drive_number_not_found(self):
|
||||
fake_disk_path = 'fake_disk_path'
|
||||
|
||||
ret_val = self._utils._get_drive_number_from_disk_path(
|
||||
fake_disk_path)
|
||||
|
||||
self.assertFalse(ret_val)
|
||||
|
||||
@mock.patch.object(base_iscsi_utils.BaseISCSIInitiatorUtils,
|
||||
"_get_drive_number_from_disk_path")
|
||||
def test_get_session_id_from_mounted_disk(self, mock_get_session_id):
|
||||
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
mock_initiator_session = self._create_initiator_session()
|
||||
mock_ses_class = self._utils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [mock_initiator_session]
|
||||
|
||||
session_id = self._utils.get_session_id_from_mounted_disk(
|
||||
self._FAKE_DISK_PATH)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_SESSION_ID, session_id)
|
||||
|
||||
def test_get_devices_for_target(self):
|
||||
init_session = self._create_initiator_session()
|
||||
mock_ses_class = self._utils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_session]
|
||||
devices = self._utils._get_devices_for_target(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(init_session.Devices, devices)
|
||||
|
||||
def test_get_devices_for_target_not_found(self):
|
||||
mock_ses_class = self._utils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = []
|
||||
devices = self._utils._get_devices_for_target(mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(0, len(devices))
|
||||
|
||||
@mock.patch.object(base_iscsi_utils.BaseISCSIInitiatorUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_device_number_for_target(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
fake_get_devices.return_value = init_session.Devices
|
||||
mock_ses_class = self._utils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_session]
|
||||
device_number = self._utils.get_device_number_for_target(
|
||||
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_DEVICE_NUMBER, device_number)
|
||||
|
||||
@mock.patch.object(base_iscsi_utils.BaseISCSIInitiatorUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_target_lun_count(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
# Only disk devices are being counted.
|
||||
disk_device = mock.Mock(DeviceType=self._utils._FILE_DEVICE_DISK)
|
||||
init_session.Devices.append(disk_device)
|
||||
fake_get_devices.return_value = init_session.Devices
|
||||
|
||||
lun_count = self._utils.get_target_lun_count(mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(1, lun_count)
|
||||
|
||||
@mock.patch.object(base_iscsi_utils.BaseISCSIInitiatorUtils,
|
||||
"_get_drive_number_from_disk_path")
|
||||
def test_get_target_from_disk_path(self, mock_get_session_id):
|
||||
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
init_sess = self._create_initiator_session()
|
||||
mock_ses_class = self._utils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_sess]
|
||||
|
||||
(target_name, scsi_lun) = self._utils.get_target_from_disk_path(
|
||||
self._FAKE_DISK_PATH)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_TARGET_NAME, target_name)
|
||||
self.assertEqual(mock.sentinel.FAKE_LUN, scsi_lun)
|
||||
|
||||
def _create_initiator_session(self):
|
||||
device = mock.MagicMock()
|
||||
device.ScsiLun = mock.sentinel.FAKE_LUN
|
||||
device.DeviceNumber = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
device.TargetName = mock.sentinel.FAKE_TARGET_NAME
|
||||
init_session = mock.MagicMock()
|
||||
init_session.Devices = [device]
|
||||
init_session.SessionId = mock.sentinel.FAKE_SESSION_ID
|
||||
|
||||
return init_session
|
||||
@@ -1,160 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
from oslotest import base
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.utils.storage.initiator import iscsi_cli_utils
|
||||
|
||||
|
||||
class ISCSIInitiatorCLIUtilsTestCase(base.BaseTestCase):
|
||||
"""Unit tests for the Hyper-V ISCSIInitiatorCLIUtils class."""
|
||||
|
||||
_FAKE_PORTAL_ADDR = '10.1.1.1'
|
||||
_FAKE_PORTAL_PORT = '3260'
|
||||
_FAKE_LUN = 0
|
||||
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
|
||||
|
||||
_FAKE_STDOUT_VALUE = 'The operation completed successfully'
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSIInitiatorCLIUtilsTestCase, self).setUp()
|
||||
self._initiator = iscsi_cli_utils.ISCSIInitiatorCLIUtils()
|
||||
self._initiator._conn_wmi = mock.MagicMock()
|
||||
self._initiator._conn_cimv2 = mock.MagicMock()
|
||||
|
||||
def _test_login_target_portal(self, portal_connected):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
|
||||
self._initiator.execute = mock.MagicMock()
|
||||
if portal_connected:
|
||||
exec_output = 'Address and Socket: %s %s' % (
|
||||
self._FAKE_PORTAL_ADDR, self._FAKE_PORTAL_PORT)
|
||||
else:
|
||||
exec_output = ''
|
||||
|
||||
self._initiator.execute.return_value = exec_output
|
||||
|
||||
self._initiator._login_target_portal(fake_portal)
|
||||
|
||||
call_list = self._initiator.execute.call_args_list
|
||||
all_call_args = [arg for call in call_list for arg in call[0]]
|
||||
|
||||
if portal_connected:
|
||||
self.assertIn('RefreshTargetPortal', all_call_args)
|
||||
else:
|
||||
self.assertIn('AddTargetPortal', all_call_args)
|
||||
|
||||
def test_login_connected_portal(self):
|
||||
self._test_login_target_portal(True)
|
||||
|
||||
def test_login_new_portal(self):
|
||||
self._test_login_target_portal(False)
|
||||
|
||||
@mock.patch.object(iscsi_cli_utils, 'CONF')
|
||||
def _test_login_target(self, mock_CONF, target_connected=False,
|
||||
raise_exception=False, use_chap=False):
|
||||
mock_CONF.hyperv.volume_attach_retry_count = 4
|
||||
mock_CONF.hyperv.volume_attach_retry_interval = 0
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
self._initiator.execute = mock.MagicMock()
|
||||
self._initiator._login_target_portal = mock.MagicMock()
|
||||
|
||||
if use_chap:
|
||||
username, password = (mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
else:
|
||||
username, password = None, None
|
||||
|
||||
if target_connected:
|
||||
self._initiator.execute.return_value = self._FAKE_TARGET
|
||||
elif raise_exception:
|
||||
self._initiator.execute.return_value = ''
|
||||
else:
|
||||
self._initiator.execute.side_effect = (
|
||||
['', '', '', self._FAKE_TARGET, ''])
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self._initiator.login_storage_target,
|
||||
self._FAKE_LUN, self._FAKE_TARGET,
|
||||
fake_portal, username, password)
|
||||
else:
|
||||
self._initiator.login_storage_target(self._FAKE_LUN,
|
||||
self._FAKE_TARGET,
|
||||
fake_portal,
|
||||
username, password)
|
||||
|
||||
if target_connected:
|
||||
call_list = self._initiator.execute.call_args_list
|
||||
all_call_args = [arg for call in call_list for arg in call[0]]
|
||||
self.assertNotIn('qlogintarget', all_call_args)
|
||||
else:
|
||||
self._initiator.execute.assert_any_call(
|
||||
'iscsicli.exe', 'qlogintarget',
|
||||
self._FAKE_TARGET, username, password)
|
||||
|
||||
def test_login_connected_target(self):
|
||||
self._test_login_target(target_connected=True)
|
||||
|
||||
def test_login_disconncted_target(self):
|
||||
self._test_login_target()
|
||||
|
||||
def test_login_target_exception(self):
|
||||
self._test_login_target(raise_exception=True)
|
||||
|
||||
def test_login_target_using_chap(self):
|
||||
self._test_login_target(use_chap=True)
|
||||
|
||||
def _test_execute_wrapper(self, raise_exception):
|
||||
fake_cmd = ('iscsicli.exe', 'ListTargetPortals')
|
||||
|
||||
if raise_exception:
|
||||
output = 'fake error'
|
||||
else:
|
||||
output = 'The operation completed successfully'
|
||||
|
||||
with mock.patch('os_win._utils.execute') as fake_execute:
|
||||
fake_execute.return_value = (output, None)
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self._initiator.execute,
|
||||
*fake_cmd)
|
||||
else:
|
||||
ret_val = self._initiator.execute(*fake_cmd)
|
||||
self.assertEqual(output, ret_val)
|
||||
|
||||
def test_execute_raise_exception(self):
|
||||
self._test_execute_wrapper(True)
|
||||
|
||||
def test_execute_exception(self):
|
||||
self._test_execute_wrapper(False)
|
||||
|
||||
@mock.patch.object(iscsi_cli_utils, '_utils')
|
||||
def test_logout_storage_target(self, mock_utils):
|
||||
mock_utils.execute.return_value = (self._FAKE_STDOUT_VALUE,
|
||||
mock.sentinel.FAKE_STDERR_VALUE)
|
||||
session = mock.MagicMock()
|
||||
session.SessionId = mock.sentinel.FAKE_SESSION_ID
|
||||
self._initiator._conn_wmi.query.return_value = [session]
|
||||
|
||||
self._initiator.logout_storage_target(mock.sentinel.FAKE_IQN)
|
||||
mock_utils.execute.assert_called_once_with(
|
||||
'iscsicli.exe', 'logouttarget', mock.sentinel.FAKE_SESSION_ID)
|
||||
@@ -1,161 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage.initiator import iscsi_wmi_utils
|
||||
|
||||
|
||||
class ISCSIInitiatorWMIUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V ISCSIInitiatorWMIUtils class."""
|
||||
|
||||
_FAKE_PORTAL_ADDR = '10.1.1.1'
|
||||
_FAKE_PORTAL_PORT = '3260'
|
||||
_FAKE_LUN = 0
|
||||
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSIInitiatorWMIUtilsTestCase, self).setUp()
|
||||
self._initiator = iscsi_wmi_utils.ISCSIInitiatorWMIUtils()
|
||||
self._initiator._conn_storage = mock.MagicMock()
|
||||
self._initiator._conn_wmi = mock.MagicMock()
|
||||
|
||||
def _test_login_target_portal(self, portal_connected):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
fake_portal_object = mock.MagicMock()
|
||||
_query = self._initiator._conn_storage.query
|
||||
self._initiator._conn_storage.MSFT_iSCSITargetPortal = (
|
||||
fake_portal_object)
|
||||
|
||||
if portal_connected:
|
||||
_query.return_value = [fake_portal_object]
|
||||
else:
|
||||
_query.return_value = None
|
||||
|
||||
self._initiator._login_target_portal(fake_portal)
|
||||
|
||||
if portal_connected:
|
||||
fake_portal_object.Update.assert_called_once_with()
|
||||
else:
|
||||
fake_portal_object.New.assert_called_once_with(
|
||||
TargetPortalAddress=self._FAKE_PORTAL_ADDR,
|
||||
TargetPortalPortNumber=self._FAKE_PORTAL_PORT)
|
||||
|
||||
def test_login_connected_portal(self):
|
||||
self._test_login_target_portal(True)
|
||||
|
||||
def test_login_new_portal(self):
|
||||
self._test_login_target_portal(False)
|
||||
|
||||
@mock.patch.object(iscsi_wmi_utils, 'CONF')
|
||||
def _test_login_target(self, mock_CONF, target_connected=False,
|
||||
raise_exception=False, use_chap=False):
|
||||
mock_CONF.hyperv.volume_attach_retry_count = 4
|
||||
mock_CONF.hyperv.volume_attach_retry_interval = 0
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
|
||||
fake_target_object = mock.MagicMock()
|
||||
|
||||
if target_connected:
|
||||
fake_target_object.IsConnected = True
|
||||
elif not raise_exception:
|
||||
type(fake_target_object).IsConnected = mock.PropertyMock(
|
||||
side_effect=[False, True])
|
||||
else:
|
||||
fake_target_object.IsConnected = False
|
||||
|
||||
_query = self._initiator._conn_storage.query
|
||||
_query.return_value = [fake_target_object]
|
||||
|
||||
self._initiator._conn_storage.MSFT_iSCSITarget = (
|
||||
fake_target_object)
|
||||
|
||||
if use_chap:
|
||||
username, password = (mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
auth = {
|
||||
'AuthenticationType': self._initiator._CHAP_AUTH_TYPE,
|
||||
'ChapUsername': username,
|
||||
'ChapSecret': password,
|
||||
}
|
||||
else:
|
||||
username, password = None, None
|
||||
auth = {}
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self._initiator.login_storage_target,
|
||||
self._FAKE_LUN, self._FAKE_TARGET, fake_portal)
|
||||
else:
|
||||
self._initiator.login_storage_target(self._FAKE_LUN,
|
||||
self._FAKE_TARGET,
|
||||
fake_portal,
|
||||
username, password)
|
||||
|
||||
if target_connected:
|
||||
fake_target_object.Update.assert_called_with()
|
||||
else:
|
||||
fake_target_object.Connect.assert_called_once_with(
|
||||
IsPersistent=True, NodeAddress=self._FAKE_TARGET, **auth)
|
||||
|
||||
def test_login_connected_target(self):
|
||||
self._test_login_target(target_connected=True)
|
||||
|
||||
def test_login_disconncted_target(self):
|
||||
self._test_login_target()
|
||||
|
||||
def test_login_target_exception(self):
|
||||
self._test_login_target(raise_exception=True)
|
||||
|
||||
def test_login_target_using_chap(self):
|
||||
self._test_login_target(use_chap=True)
|
||||
|
||||
def test_logout_storage_target(self):
|
||||
mock_msft_target = self._initiator._conn_storage.MSFT_iSCSITarget
|
||||
mock_msft_session = self._initiator._conn_storage.MSFT_iSCSISession
|
||||
|
||||
mock_target = mock.MagicMock()
|
||||
mock_target.IsConnected = True
|
||||
mock_msft_target.return_value = [mock_target]
|
||||
|
||||
mock_session = mock.MagicMock()
|
||||
mock_session.IsPersistent = True
|
||||
mock_msft_session.return_value = [mock_session]
|
||||
|
||||
self._initiator.logout_storage_target(self._FAKE_TARGET)
|
||||
|
||||
mock_msft_target.assert_called_once_with(NodeAddress=self._FAKE_TARGET)
|
||||
mock_msft_session.assert_called_once_with(
|
||||
TargetNodeAddress=self._FAKE_TARGET)
|
||||
|
||||
mock_session.Unregister.assert_called_once_with()
|
||||
mock_target.Disconnect.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(iscsi_wmi_utils.ISCSIInitiatorWMIUtils,
|
||||
'logout_storage_target')
|
||||
def test_execute_log_out(self, mock_logout_target):
|
||||
sess_class = self._initiator._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
|
||||
mock_session = mock.MagicMock()
|
||||
sess_class.return_value = [mock_session]
|
||||
|
||||
self._initiator.execute_log_out(mock.sentinel.FAKE_SESSION_ID)
|
||||
|
||||
sess_class.assert_called_once_with(
|
||||
SessionId=mock.sentinel.FAKE_SESSION_ID)
|
||||
mock_logout_target.assert_called_once_with(mock_session.TargetName)
|
||||
@@ -1,132 +0,0 @@
|
||||
#
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes,
|
||||
and storage repositories
|
||||
"""
|
||||
|
||||
import abc
|
||||
import re
|
||||
import sys
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _LI
|
||||
from os_win.utils import baseutils
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from six.moves import winreg
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseISCSIInitiatorUtils(baseutils.BaseUtils):
|
||||
_FILE_DEVICE_DISK = 7
|
||||
|
||||
def __init__(self, host='.'):
|
||||
self._conn_wmi = self._get_wmi_conn('//%s/root/wmi' % host)
|
||||
self._conn_cimv2 = self._get_wmi_conn('//%s/root/cimv2' % host)
|
||||
self._drive_number_regex = re.compile(r'DeviceID=\"[^,]*\\(\d+)\"')
|
||||
|
||||
@abc.abstractmethod
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def logout_storage_target(self, target_iqn):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute_log_out(self, session_id):
|
||||
pass
|
||||
|
||||
def get_iscsi_initiator(self):
|
||||
"""Get iscsi initiator name for this machine."""
|
||||
|
||||
computer_system = self._conn_cimv2.Win32_ComputerSystem()[0]
|
||||
hostname = computer_system.name
|
||||
keypath = ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
|
||||
"iSCSI\\Discovery")
|
||||
try:
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
keypath,
|
||||
0,
|
||||
winreg.KEY_WOW64_64KEY + winreg.KEY_ALL_ACCESS)
|
||||
temp = winreg.QueryValueEx(key, 'DefaultInitiatorName')
|
||||
initiator_name = str(temp[0])
|
||||
winreg.CloseKey(key)
|
||||
except Exception:
|
||||
LOG.info(_LI("The ISCSI initiator name can't be found. "
|
||||
"Choosing the default one"))
|
||||
initiator_name = "iqn.1991-05.com.microsoft:" + hostname.lower()
|
||||
if computer_system.PartofDomain:
|
||||
initiator_name += '.' + computer_system.Domain.lower()
|
||||
return initiator_name
|
||||
|
||||
def _get_drive_number_from_disk_path(self, disk_path):
|
||||
drive_number = self._drive_number_regex.findall(disk_path)
|
||||
if drive_number:
|
||||
return int(drive_number[0])
|
||||
|
||||
def get_session_id_from_mounted_disk(self, physical_drive_path):
|
||||
drive_number = self._get_drive_number_from_disk_path(
|
||||
physical_drive_path)
|
||||
if not drive_number:
|
||||
return None
|
||||
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
|
||||
for initiator_session in initiator_sessions:
|
||||
devices = initiator_session.Devices
|
||||
for device in devices:
|
||||
device_number = device.DeviceNumber
|
||||
if device_number == drive_number:
|
||||
return initiator_session.SessionId
|
||||
|
||||
def _get_devices_for_target(self, target_iqn):
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
|
||||
TargetName=target_iqn)
|
||||
if not initiator_sessions:
|
||||
return []
|
||||
|
||||
return initiator_sessions[0].Devices
|
||||
|
||||
def get_device_number_for_target(self, target_iqn, target_lun):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
|
||||
for device in devices:
|
||||
if device.ScsiLun == target_lun:
|
||||
return device.DeviceNumber
|
||||
|
||||
def get_target_lun_count(self, target_iqn):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
disk_devices = [device for device in devices
|
||||
if device.DeviceType == self._FILE_DEVICE_DISK]
|
||||
return len(disk_devices)
|
||||
|
||||
def get_target_from_disk_path(self, disk_path):
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
|
||||
drive_number = self._get_drive_number_from_disk_path(disk_path)
|
||||
if not drive_number:
|
||||
return None
|
||||
|
||||
for initiator_session in initiator_sessions:
|
||||
devices = initiator_session.Devices
|
||||
for device in devices:
|
||||
if device.DeviceNumber == drive_number:
|
||||
return (device.TargetName, device.ScsiLun)
|
||||
@@ -1,119 +0,0 @@
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes,
|
||||
and storage repositories
|
||||
|
||||
Official Microsoft iSCSI Initiator and iSCSI command line interface
|
||||
documentation can be retrieved at:
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=34750
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from six.moves import range # noqa
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
from os_win.utils.storage.initiator import base_iscsi_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class ISCSIInitiatorCLIUtils(base_iscsi_utils.BaseISCSIInitiatorUtils):
|
||||
|
||||
def execute(self, *args, **kwargs):
|
||||
stdout_value, stderr_value = _utils.execute(*args, **kwargs)
|
||||
if stdout_value.find('The operation completed successfully') == -1:
|
||||
raise exceptions.HyperVException
|
||||
(_('An error has occurred when calling the iscsi initiator: %s')
|
||||
% stdout_value)
|
||||
return stdout_value
|
||||
|
||||
def _login_target_portal(self, target_portal):
|
||||
(target_address,
|
||||
target_port) = _utils.parse_server_string(target_portal)
|
||||
|
||||
output = self.execute('iscsicli.exe', 'ListTargetPortals')
|
||||
pattern = r'Address and Socket *: (.*)'
|
||||
portals = [addr.split() for addr in re.findall(pattern, output)]
|
||||
LOG.debug("Ensuring connection to portal: %s" % target_portal)
|
||||
if [target_address, str(target_port)] in portals:
|
||||
self.execute('iscsicli.exe', 'RefreshTargetPortal',
|
||||
target_address, target_port)
|
||||
else:
|
||||
# Adding target portal to iscsi initiator. Sending targets
|
||||
self.execute('iscsicli.exe', 'AddTargetPortal',
|
||||
target_address, target_port,
|
||||
'*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*',
|
||||
'*', '*')
|
||||
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal,
|
||||
auth_username=None, auth_password=None):
|
||||
"""Ensure that the target is logged in."""
|
||||
|
||||
self._login_target_portal(target_portal)
|
||||
# Listing targets
|
||||
self.execute('iscsicli.exe', 'ListTargets')
|
||||
|
||||
retry_count = CONF.hyperv.volume_attach_retry_count
|
||||
|
||||
# If the target is not connected, at least two iterations are needed:
|
||||
# one for performing the login and another one for checking if the
|
||||
# target was logged in successfully.
|
||||
if retry_count < 2:
|
||||
retry_count = 2
|
||||
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
session_info = self.execute('iscsicli.exe', 'SessionList')
|
||||
if session_info.find(target_iqn) == -1:
|
||||
# Sending login
|
||||
self.execute('iscsicli.exe', 'qlogintarget', target_iqn,
|
||||
auth_username, auth_password)
|
||||
else:
|
||||
return
|
||||
except exceptions.HyperVException as exc:
|
||||
LOG.debug("Attempt %(attempt)d to connect to target "
|
||||
"%(target_iqn)s failed. Retrying. "
|
||||
"Exceptipn: %(exc)s ",
|
||||
{'target_iqn': target_iqn,
|
||||
'exc': exc,
|
||||
'attempt': attempt})
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
|
||||
raise exceptions.HyperVException(_('Failed to login target %s') %
|
||||
target_iqn)
|
||||
|
||||
def logout_storage_target(self, target_iqn):
|
||||
"""Logs out storage target through its session id."""
|
||||
|
||||
sessions = self._conn_wmi.query("SELECT * FROM "
|
||||
"MSiSCSIInitiator_SessionClass "
|
||||
"WHERE TargetName='%s'" % target_iqn)
|
||||
for session in sessions:
|
||||
self.execute_log_out(session.SessionId)
|
||||
|
||||
def execute_log_out(self, session_id):
|
||||
"""Executes log out of the session described by its session ID."""
|
||||
self.execute('iscsicli.exe', 'logouttarget', session_id)
|
||||
@@ -1,127 +0,0 @@
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes
|
||||
and storage repositories on Windows Server 2012 and above
|
||||
"""
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from six.moves import range # noqa
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
from os_win.utils.storage.initiator import base_iscsi_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class ISCSIInitiatorWMIUtils(base_iscsi_utils.BaseISCSIInitiatorUtils):
|
||||
_CHAP_AUTH_TYPE = 'ONEWAYCHAP'
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(ISCSIInitiatorWMIUtils, self).__init__(host)
|
||||
|
||||
storage_namespace = '//%s/root/microsoft/windows/storage' % host
|
||||
self._conn_storage = self._get_wmi_conn(storage_namespace)
|
||||
|
||||
def _login_target_portal(self, target_portal):
|
||||
(target_address,
|
||||
target_port) = _utils.parse_server_string(target_portal)
|
||||
|
||||
# Checking if the portal is already connected.
|
||||
portal = self._conn_storage.query("SELECT * FROM "
|
||||
"MSFT_iSCSITargetPortal "
|
||||
"WHERE TargetPortalAddress='%s' "
|
||||
"AND TargetPortalPortNumber='%s'"
|
||||
% (target_address, target_port))
|
||||
if portal:
|
||||
portal[0].Update()
|
||||
else:
|
||||
# Adding target portal to iscsi initiator. Sending targets
|
||||
portal = self._conn_storage.MSFT_iSCSITargetPortal
|
||||
portal.New(TargetPortalAddress=target_address,
|
||||
TargetPortalPortNumber=target_port)
|
||||
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal,
|
||||
auth_username=None, auth_password=None):
|
||||
"""Ensure that the target is logged in."""
|
||||
|
||||
self._login_target_portal(target_portal)
|
||||
|
||||
retry_count = CONF.hyperv.volume_attach_retry_count
|
||||
|
||||
# If the target is not connected, at least two iterations are needed:
|
||||
# one for performing the login and another one for checking if the
|
||||
# target was logged in successfully.
|
||||
if retry_count < 2:
|
||||
retry_count = 2
|
||||
|
||||
for attempt in range(retry_count):
|
||||
target = self._conn_storage.query("SELECT * FROM MSFT_iSCSITarget "
|
||||
"WHERE NodeAddress='%s' " %
|
||||
target_iqn)
|
||||
|
||||
if target and target[0].IsConnected:
|
||||
if attempt == 0:
|
||||
# The target was already connected but an update may be
|
||||
# required
|
||||
target[0].Update()
|
||||
return
|
||||
try:
|
||||
target = self._conn_storage.MSFT_iSCSITarget
|
||||
auth = {}
|
||||
if auth_username and auth_password:
|
||||
auth['AuthenticationType'] = self._CHAP_AUTH_TYPE
|
||||
auth['ChapUsername'] = auth_username
|
||||
auth['ChapSecret'] = auth_password
|
||||
target.Connect(NodeAddress=target_iqn,
|
||||
IsPersistent=True, **auth)
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
except exceptions.x_wmi as exc:
|
||||
LOG.debug("Attempt %(attempt)d to connect to target "
|
||||
"%(target_iqn)s failed. Retrying. "
|
||||
"WMI exception: %(exc)s " %
|
||||
{'target_iqn': target_iqn,
|
||||
'exc': exc,
|
||||
'attempt': attempt})
|
||||
raise exceptions.HyperVException(_('Failed to login target %s') %
|
||||
target_iqn)
|
||||
|
||||
def logout_storage_target(self, target_iqn):
|
||||
"""Logs out storage target through its session id."""
|
||||
targets = self._conn_storage.MSFT_iSCSITarget(NodeAddress=target_iqn)
|
||||
if targets:
|
||||
target = targets[0]
|
||||
if target.IsConnected:
|
||||
sessions = self._conn_storage.MSFT_iSCSISession(
|
||||
TargetNodeAddress=target_iqn)
|
||||
|
||||
for session in sessions:
|
||||
if session.IsPersistent:
|
||||
session.Unregister()
|
||||
|
||||
target.Disconnect()
|
||||
|
||||
def execute_log_out(self, session_id):
|
||||
sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
|
||||
SessionId=session_id)
|
||||
if sessions:
|
||||
self.logout_storage_target(sessions[0].TargetName)
|
||||
@@ -13,24 +13,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
from os_win._i18n import _, _LW # noqa
|
||||
from os_win import exceptions
|
||||
from os_win.utils import hostutils
|
||||
from os_win.utils.io import namedpipe
|
||||
from os_win.utils.storage.initiator import iscsi_cli_utils
|
||||
|
||||
hyper_opts = [
|
||||
cfg.BoolOpt('force_volumeutils_v1',
|
||||
default=False,
|
||||
help='DEPRECATED: Force V1 volume utility class',
|
||||
deprecated_for_removal=True),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(hyper_opts, 'hyperv')
|
||||
|
||||
utils = hostutils.HostUtils()
|
||||
|
||||
@@ -196,10 +184,7 @@ def get_pathutils():
|
||||
return _get_class(class_type='pathutils')
|
||||
|
||||
|
||||
def get_iscsi_initiator_utils(use_iscsi_cli=False):
|
||||
use_iscsi_cli = use_iscsi_cli or CONF.hyperv.force_volumeutils_v1
|
||||
if use_iscsi_cli:
|
||||
return iscsi_cli_utils.ISCSIInitiatorCLIUtils()
|
||||
def get_iscsi_initiator_utils():
|
||||
return _get_class(class_type='iscsi_initiator_utils')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user