Merge "Switch to using os-brick"
This commit is contained in:
commit
a43a1a3703
@ -180,9 +180,6 @@ mkfs.ext3: CommandFilter, mkfs.ext3, root
|
|||||||
mkfs.ext4: CommandFilter, mkfs.ext4, root
|
mkfs.ext4: CommandFilter, mkfs.ext4, root
|
||||||
mkfs.ntfs: CommandFilter, mkfs.ntfs, root
|
mkfs.ntfs: CommandFilter, mkfs.ntfs, root
|
||||||
|
|
||||||
# nova/virt/libvirt/connection.py:
|
|
||||||
read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi
|
|
||||||
|
|
||||||
# nova/virt/libvirt/connection.py:
|
# nova/virt/libvirt/connection.py:
|
||||||
lvremove: CommandFilter, lvremove, root
|
lvremove: CommandFilter, lvremove, root
|
||||||
|
|
||||||
@ -203,13 +200,12 @@ tgtadm: CommandFilter, tgtadm, root
|
|||||||
read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd
|
read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd
|
||||||
read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow
|
read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow
|
||||||
|
|
||||||
# nova/virt/libvirt/volume.py: 'multipath' '-R'
|
# os-brick needed commands
|
||||||
|
read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi
|
||||||
multipath: CommandFilter, multipath, root
|
multipath: CommandFilter, multipath, root
|
||||||
|
# multipathd show status
|
||||||
# nova/virt/libvirt/utils.py:
|
multipathd: CommandFilter, multipathd, root
|
||||||
systool: CommandFilter, systool, root
|
systool: CommandFilter, systool, root
|
||||||
|
|
||||||
# nova/storage/linuxscsi.py: sginfo -r
|
|
||||||
sginfo: CommandFilter, sginfo, root
|
sginfo: CommandFilter, sginfo, root
|
||||||
|
|
||||||
# nova/storage/linuxscsi.py: sg_scan device
|
# nova/storage/linuxscsi.py: sg_scan device
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Generic linux scsi subsystem utilities."""
|
|
||||||
|
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_service import loopingcall
|
|
||||||
|
|
||||||
from nova.i18n import _LW
|
|
||||||
from nova import utils
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
MULTIPATH_WWID_REGEX = re.compile("\((?P<wwid>.+)\)")
|
|
||||||
|
|
||||||
|
|
||||||
def echo_scsi_command(path, content):
|
|
||||||
"""Used to echo strings to scsi subsystem."""
|
|
||||||
args = ["-a", path]
|
|
||||||
kwargs = dict(process_input=content, run_as_root=True)
|
|
||||||
utils.execute('tee', *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def rescan_hosts(hbas):
|
|
||||||
for hba in hbas:
|
|
||||||
echo_scsi_command("/sys/class/scsi_host/%s/scan"
|
|
||||||
% hba['host_device'], "- - -")
|
|
||||||
|
|
||||||
|
|
||||||
def get_device_list():
|
|
||||||
(out, err) = utils.execute('sginfo', '-r', run_as_root=True)
|
|
||||||
devices = []
|
|
||||||
if out:
|
|
||||||
line = out.strip()
|
|
||||||
devices = line.split(" ")
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def get_device_info(device):
|
|
||||||
(out, err) = utils.execute('sg_scan', device, run_as_root=True)
|
|
||||||
dev_info = {'device': device, 'host': None,
|
|
||||||
'channel': None, 'id': None, 'lun': None}
|
|
||||||
if out:
|
|
||||||
line = out.strip()
|
|
||||||
line = line.replace(device + ": ", "")
|
|
||||||
info = line.split(" ")
|
|
||||||
|
|
||||||
for item in info:
|
|
||||||
if '=' in item:
|
|
||||||
pair = item.split('=')
|
|
||||||
dev_info[pair[0]] = pair[1]
|
|
||||||
elif 'scsi' in item:
|
|
||||||
dev_info['host'] = item.replace('scsi', '')
|
|
||||||
|
|
||||||
return dev_info
|
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_remove(device, tries):
|
|
||||||
tries = tries + 1
|
|
||||||
LOG.debug("Trying (%(tries)s) to remove device %(device)s",
|
|
||||||
{'tries': tries, 'device': device["device"]})
|
|
||||||
|
|
||||||
path = "/sys/bus/scsi/drivers/sd/%s:%s:%s:%s/delete"
|
|
||||||
echo_scsi_command(path % (device["host"], device["channel"],
|
|
||||||
device["id"], device["lun"]),
|
|
||||||
"1")
|
|
||||||
|
|
||||||
devices = get_device_list()
|
|
||||||
if device["device"] not in devices:
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_device(device):
|
|
||||||
tries = 0
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_remove, device,
|
|
||||||
tries)
|
|
||||||
timer.start(interval=2).wait()
|
|
||||||
timer.stop()
|
|
||||||
|
|
||||||
|
|
||||||
def find_multipath_device(device):
|
|
||||||
"""Try and discover the multipath device for a volume."""
|
|
||||||
mdev = None
|
|
||||||
devices = []
|
|
||||||
out = None
|
|
||||||
try:
|
|
||||||
(out, err) = utils.execute('multipath', '-l', device,
|
|
||||||
run_as_root=True)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
LOG.warning(_LW("Multipath call failed exit (%(code)s)"),
|
|
||||||
{'code': exc.exit_code})
|
|
||||||
return None
|
|
||||||
|
|
||||||
if out:
|
|
||||||
lines = out.strip()
|
|
||||||
lines = lines.split("\n")
|
|
||||||
if lines:
|
|
||||||
|
|
||||||
# Use the device name, be it the WWID, mpathN or custom alias of
|
|
||||||
# a device to build the device path. This should be the first item
|
|
||||||
# on the first line of output from `multipath -l /dev/${path}`.
|
|
||||||
mdev_name = lines[0].split(" ")[0]
|
|
||||||
mdev = '/dev/mapper/%s' % mdev_name
|
|
||||||
|
|
||||||
# Find the WWID for the LUN if we are using mpathN or aliases.
|
|
||||||
wwid_search = MULTIPATH_WWID_REGEX.search(lines[0])
|
|
||||||
if wwid_search is not None:
|
|
||||||
mdev_id = wwid_search.group('wwid')
|
|
||||||
else:
|
|
||||||
mdev_id = mdev_name
|
|
||||||
|
|
||||||
# Confirm that the device is present.
|
|
||||||
try:
|
|
||||||
os.stat(mdev)
|
|
||||||
except OSError:
|
|
||||||
LOG.warning(_LW("Couldn't find multipath device %s"), mdev)
|
|
||||||
return None
|
|
||||||
|
|
||||||
LOG.debug("Found multipath device = %s", mdev)
|
|
||||||
|
|
||||||
device_lines = lines[3:]
|
|
||||||
for dev_line in device_lines:
|
|
||||||
if dev_line.find("policy") != -1:
|
|
||||||
continue
|
|
||||||
if '#' in dev_line:
|
|
||||||
LOG.warning(_LW('Skip faulty line "%(dev_line)s" of'
|
|
||||||
' multipath device %(mdev)s'),
|
|
||||||
{'mdev': mdev, 'dev_line': dev_line})
|
|
||||||
continue
|
|
||||||
|
|
||||||
dev_line = dev_line.lstrip(' |-`')
|
|
||||||
dev_info = dev_line.split()
|
|
||||||
address = dev_info[0].split(":")
|
|
||||||
|
|
||||||
dev = {'device': '/dev/%s' % dev_info[1],
|
|
||||||
'host': address[0], 'channel': address[1],
|
|
||||||
'id': address[2], 'lun': address[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
devices.append(dev)
|
|
||||||
|
|
||||||
if mdev is not None:
|
|
||||||
info = {"device": mdev,
|
|
||||||
"id": mdev_id,
|
|
||||||
"name": mdev_name,
|
|
||||||
"devices": devices}
|
|
||||||
return info
|
|
||||||
return None
|
|
@ -1,175 +0,0 @@
|
|||||||
# Copyright 2010 OpenStack Foundation
|
|
||||||
# (c) Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from nova.storage import linuxscsi
|
|
||||||
from nova import test
|
|
||||||
from nova import utils
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class StorageLinuxSCSITestCase(test.NoDBTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(StorageLinuxSCSITestCase, self).setUp()
|
|
||||||
self.executes = []
|
|
||||||
self.fake_stat_result = os.stat(__file__)
|
|
||||||
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
self.executes.append(cmd)
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def fake_stat(path):
|
|
||||||
return self.fake_stat_result
|
|
||||||
|
|
||||||
self.stubs.Set(utils, 'execute', fake_execute)
|
|
||||||
self.stubs.Set(os, 'stat', fake_stat)
|
|
||||||
|
|
||||||
def test_find_multipath_device_3par(self):
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
out = ("350002ac20398383d dm-3 3PARdata,VV\n"
|
|
||||||
"size=2.0G features='0' hwhandler='0' wp=rw\n"
|
|
||||||
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
||||||
" |- 0:0:0:1 sde 8:64 active undef running\n"
|
|
||||||
" `- 2:0:0:1 sdf 8:80 active undef running\n"
|
|
||||||
)
|
|
||||||
return out, None
|
|
||||||
|
|
||||||
self.stubs.Set(utils, 'execute', fake_execute)
|
|
||||||
|
|
||||||
info = linuxscsi.find_multipath_device('/dev/sde')
|
|
||||||
LOG.error("info = %s" % info)
|
|
||||||
|
|
||||||
self.assertEqual("350002ac20398383d", info["name"])
|
|
||||||
self.assertEqual("350002ac20398383d", info["id"])
|
|
||||||
self.assertEqual("/dev/mapper/350002ac20398383d", info["device"])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sde", info['devices'][0]['device'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['channel'])
|
|
||||||
self.assertEqual("1", info['devices'][0]['lun'])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sdf", info['devices'][1]['device'])
|
|
||||||
self.assertEqual("2", info['devices'][1]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][1]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][1]['channel'])
|
|
||||||
self.assertEqual("1", info['devices'][1]['lun'])
|
|
||||||
|
|
||||||
def test_find_multipath_device_3par_ufn(self):
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
out = ("mpath6 (350002ac20398383d) dm-3 3PARdata,VV\n"
|
|
||||||
"size=2.0G features='0' hwhandler='0' wp=rw\n"
|
|
||||||
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
||||||
" |- 0:0:0:1 sde 8:64 active undef running\n"
|
|
||||||
" `- 2:0:0:1 sdf 8:80 active undef running\n"
|
|
||||||
)
|
|
||||||
return out, None
|
|
||||||
|
|
||||||
self.stubs.Set(utils, 'execute', fake_execute)
|
|
||||||
|
|
||||||
info = linuxscsi.find_multipath_device('/dev/sde')
|
|
||||||
LOG.error("info = %s" % info)
|
|
||||||
|
|
||||||
self.assertEqual("mpath6", info["name"])
|
|
||||||
self.assertEqual("350002ac20398383d", info["id"])
|
|
||||||
self.assertEqual("/dev/mapper/mpath6", info["device"])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sde", info['devices'][0]['device'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['channel'])
|
|
||||||
self.assertEqual("1", info['devices'][0]['lun'])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sdf", info['devices'][1]['device'])
|
|
||||||
self.assertEqual("2", info['devices'][1]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][1]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][1]['channel'])
|
|
||||||
self.assertEqual("1", info['devices'][1]['lun'])
|
|
||||||
|
|
||||||
def test_find_multipath_device_svc(self):
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
out = ("36005076da00638089c000000000004d5 dm-2 IBM,2145\n"
|
|
||||||
"size=954M features='1 queue_if_no_path' hwhandler='0'"
|
|
||||||
" wp=rw\n"
|
|
||||||
"|-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
||||||
"| |- 6:0:2:0 sde 8:64 active undef running\n"
|
|
||||||
"| `- 6:0:4:0 sdg 8:96 active undef running\n"
|
|
||||||
"`-+- policy='round-robin 0' prio=-1 status=enabled\n"
|
|
||||||
" |- 6:0:3:0 sdf 8:80 active undef running\n"
|
|
||||||
" `- 6:0:5:0 sdh 8:112 active undef running\n"
|
|
||||||
)
|
|
||||||
return out, None
|
|
||||||
|
|
||||||
self.stubs.Set(utils, 'execute', fake_execute)
|
|
||||||
|
|
||||||
info = linuxscsi.find_multipath_device('/dev/sde')
|
|
||||||
LOG.error("info = %s" % info)
|
|
||||||
|
|
||||||
self.assertEqual("36005076da00638089c000000000004d5", info["name"])
|
|
||||||
self.assertEqual("36005076da00638089c000000000004d5", info["id"])
|
|
||||||
self.assertEqual("/dev/mapper/36005076da00638089c000000000004d5",
|
|
||||||
info["device"])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sde", info['devices'][0]['device'])
|
|
||||||
self.assertEqual("6", info['devices'][0]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['channel'])
|
|
||||||
self.assertEqual("2", info['devices'][0]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['lun'])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sdf", info['devices'][2]['device'])
|
|
||||||
self.assertEqual("6", info['devices'][2]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][2]['channel'])
|
|
||||||
self.assertEqual("3", info['devices'][2]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][2]['lun'])
|
|
||||||
|
|
||||||
def test_find_multipath_device_ds8000(self):
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
out = ("36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n"
|
|
||||||
"size=1.0G features='1 queue_if_no_path' hwhandler='0'"
|
|
||||||
" wp=rw\n"
|
|
||||||
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
||||||
" |- 6:0:2:0 sdd 8:64 active undef running\n"
|
|
||||||
" `- 6:1:0:3 sdc 8:32 active undef running\n"
|
|
||||||
)
|
|
||||||
return out, None
|
|
||||||
|
|
||||||
self.stubs.Set(utils, 'execute', fake_execute)
|
|
||||||
|
|
||||||
info = linuxscsi.find_multipath_device('/dev/sdd')
|
|
||||||
LOG.error("info = %s" % info)
|
|
||||||
|
|
||||||
self.assertEqual("36005076303ffc48e0000000000000101", info["name"])
|
|
||||||
self.assertEqual("36005076303ffc48e0000000000000101", info["id"])
|
|
||||||
self.assertEqual("/dev/mapper/36005076303ffc48e0000000000000101",
|
|
||||||
info["device"])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sdd", info['devices'][0]['device'])
|
|
||||||
self.assertEqual("6", info['devices'][0]['host'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['channel'])
|
|
||||||
self.assertEqual("2", info['devices'][0]['id'])
|
|
||||||
self.assertEqual("0", info['devices'][0]['lun'])
|
|
||||||
|
|
||||||
self.assertEqual("/dev/sdc", info['devices'][1]['device'])
|
|
||||||
self.assertEqual("6", info['devices'][1]['host'])
|
|
||||||
self.assertEqual("1", info['devices'][1]['channel'])
|
|
||||||
self.assertEqual("0", info['devices'][1]['id'])
|
|
||||||
self.assertEqual("3", info['devices'][1]['lun'])
|
|
@ -24,64 +24,6 @@ disk_backing_files = {}
|
|||||||
disk_type = "qcow2"
|
disk_type = "qcow2"
|
||||||
|
|
||||||
|
|
||||||
def get_iscsi_initiator():
|
|
||||||
return "fake.initiator.iqn"
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_hbas():
|
|
||||||
return [{'ClassDevice': 'host1',
|
|
||||||
'ClassDevicePath': '/sys/devices/pci0000:00/0000:00:03.0'
|
|
||||||
'/0000:05:00.2/host1/fc_host/host1',
|
|
||||||
'dev_loss_tmo': '30',
|
|
||||||
'fabric_name': '0x1000000533f55566',
|
|
||||||
'issue_lip': '<store method only>',
|
|
||||||
'max_npiv_vports': '255',
|
|
||||||
'maxframe_size': '2048 bytes',
|
|
||||||
'node_name': '0x200010604b019419',
|
|
||||||
'npiv_vports_inuse': '0',
|
|
||||||
'port_id': '0x680409',
|
|
||||||
'port_name': '0x100010604b019419',
|
|
||||||
'port_state': 'Online',
|
|
||||||
'port_type': 'NPort (fabric via point-to-point)',
|
|
||||||
'speed': '10 Gbit',
|
|
||||||
'supported_classes': 'Class 3',
|
|
||||||
'supported_speeds': '10 Gbit',
|
|
||||||
'symbolic_name': 'Emulex 554M FV4.0.493.0 DV8.3.27',
|
|
||||||
'tgtid_bind_type': 'wwpn (World Wide Port Name)',
|
|
||||||
'uevent': None,
|
|
||||||
'vport_create': '<store method only>',
|
|
||||||
'vport_delete': '<store method only>'}]
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_hbas_info():
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
info = [{'port_name': hbas[0]['port_name'].replace('0x', ''),
|
|
||||||
'node_name': hbas[0]['node_name'].replace('0x', ''),
|
|
||||||
'host_device': hbas[0]['ClassDevice'],
|
|
||||||
'device_path': hbas[0]['ClassDevicePath']}]
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_wwpns():
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
wwpns = []
|
|
||||||
for hba in hbas:
|
|
||||||
wwpn = hba['port_name'].replace('0x', '')
|
|
||||||
wwpns.append(wwpn)
|
|
||||||
|
|
||||||
return wwpns
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_wwnns():
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
wwnns = []
|
|
||||||
for hba in hbas:
|
|
||||||
wwnn = hba['node_name'].replace('0x', '')
|
|
||||||
wwnns.append(wwnn)
|
|
||||||
|
|
||||||
return wwnns
|
|
||||||
|
|
||||||
|
|
||||||
def create_image(disk_format, path, size):
|
def create_image(disk_format, path, size):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
42
nova/tests/unit/virt/libvirt/fake_os_brick_connector.py
Normal file
42
nova/tests/unit/virt/libvirt/fake_os_brick_connector.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath,
|
||||||
|
host=None):
|
||||||
|
"""Fake os-brick."""
|
||||||
|
|
||||||
|
props = {}
|
||||||
|
props['ip'] = my_ip
|
||||||
|
props['host'] = host
|
||||||
|
iscsi = ISCSIConnector('')
|
||||||
|
props['initiator'] = iscsi.get_initiator()
|
||||||
|
props['wwpns'] = ['100010604b019419']
|
||||||
|
props['wwnns'] = ['200010604b019419']
|
||||||
|
props['multipath'] = multipath
|
||||||
|
props['platform'] = 'x86_64'
|
||||||
|
props['os_type'] = 'linux2'
|
||||||
|
return props
|
||||||
|
|
||||||
|
|
||||||
|
class ISCSIConnector(object):
|
||||||
|
"""Mimick the iSCSI connector."""
|
||||||
|
|
||||||
|
def __init__(self, root_helper, driver=None,
|
||||||
|
execute=None, use_multipath=False,
|
||||||
|
device_scan_attempts=3,
|
||||||
|
*args, **kwargs):
|
||||||
|
self.root_herlp = root_helper,
|
||||||
|
self.execute = execute
|
||||||
|
|
||||||
|
def get_initiator(self):
|
||||||
|
return "fake_iscsi.iqn"
|
@ -33,6 +33,7 @@ import fixtures
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
import mock
|
import mock
|
||||||
from mox3 import mox
|
from mox3 import mox
|
||||||
|
from os_brick.initiator import connector
|
||||||
from oslo_concurrency import lockutils
|
from oslo_concurrency import lockutils
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -105,6 +106,7 @@ CONF.import_opt('host', 'nova.netconf')
|
|||||||
CONF.import_opt('my_ip', 'nova.netconf')
|
CONF.import_opt('my_ip', 'nova.netconf')
|
||||||
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
|
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
|
||||||
CONF.import_opt('instances_path', 'nova.compute.manager')
|
CONF.import_opt('instances_path', 'nova.compute.manager')
|
||||||
|
CONF.import_opt('iscsi_use_multipath', 'nova.virt.libvirt.volume', 'libvirt')
|
||||||
|
|
||||||
_fake_network_info = fake_network.fake_get_instance_nw_info
|
_fake_network_info = fake_network.fake_get_instance_nw_info
|
||||||
|
|
||||||
@ -864,7 +866,8 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
|||||||
self.assertRaises(exception.PciDeviceDetachFailed,
|
self.assertRaises(exception.PciDeviceDetachFailed,
|
||||||
drvr._detach_pci_devices, guest, pci_devices)
|
drvr._detach_pci_devices, guest, pci_devices)
|
||||||
|
|
||||||
def test_get_connector(self):
|
@mock.patch.object(connector, 'get_connector_properties')
|
||||||
|
def test_get_connector(self, fake_get_connector):
|
||||||
initiator = 'fake.initiator.iqn'
|
initiator = 'fake.initiator.iqn'
|
||||||
ip = 'fakeip'
|
ip = 'fakeip'
|
||||||
host = 'fakehost'
|
host = 'fakehost'
|
||||||
@ -873,7 +876,6 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
|||||||
self.flags(my_ip=ip)
|
self.flags(my_ip=ip)
|
||||||
self.flags(host=host)
|
self.flags(host=host)
|
||||||
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
|
||||||
expected = {
|
expected = {
|
||||||
'ip': ip,
|
'ip': ip,
|
||||||
'initiator': initiator,
|
'initiator': initiator,
|
||||||
@ -884,16 +886,26 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
|||||||
volume = {
|
volume = {
|
||||||
'id': 'fake'
|
'id': 'fake'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO(walter-boring) add the fake in os-brick
|
||||||
|
fake_get_connector.return_value = expected
|
||||||
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||||
result = drvr.get_volume_connector(volume)
|
result = drvr.get_volume_connector(volume)
|
||||||
self.assertThat(expected, matchers.DictMatches(result))
|
self.assertThat(expected, matchers.DictMatches(result))
|
||||||
|
|
||||||
def test_get_connector_storage_ip(self):
|
@mock.patch.object(connector, 'get_connector_properties')
|
||||||
|
def test_get_connector_storage_ip(self, fake_get_connector):
|
||||||
ip = '100.100.100.100'
|
ip = '100.100.100.100'
|
||||||
storage_ip = '101.101.101.101'
|
storage_ip = '101.101.101.101'
|
||||||
self.flags(my_block_storage_ip=storage_ip, my_ip=ip)
|
self.flags(my_block_storage_ip=storage_ip, my_ip=ip)
|
||||||
volume = {
|
volume = {
|
||||||
'id': 'fake'
|
'id': 'fake'
|
||||||
}
|
}
|
||||||
|
expected = {
|
||||||
|
'ip': storage_ip
|
||||||
|
}
|
||||||
|
# TODO(walter-boring) add the fake in os-brick
|
||||||
|
fake_get_connector.return_value = expected
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||||
result = drvr.get_volume_connector(volume)
|
result = drvr.get_volume_connector(volume)
|
||||||
self.assertEqual(storage_ip, result['ip'])
|
self.assertEqual(storage_ip, result['ip'])
|
||||||
|
@ -25,7 +25,6 @@ import six
|
|||||||
|
|
||||||
from nova.compute import arch
|
from nova.compute import arch
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.storage import linuxscsi
|
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests.unit import fake_instance
|
from nova.tests.unit import fake_instance
|
||||||
from nova import utils
|
from nova import utils
|
||||||
@ -750,64 +749,3 @@ disk size: 4.4M
|
|||||||
image_meta = {'properties': {'architecture': "X86_64"}}
|
image_meta = {'properties': {'architecture': "X86_64"}}
|
||||||
image_arch = libvirt_utils.get_arch(image_meta)
|
image_arch = libvirt_utils.get_arch(image_meta)
|
||||||
self.assertEqual(arch.X86_64, image_arch)
|
self.assertEqual(arch.X86_64, image_arch)
|
||||||
|
|
||||||
@mock.patch.object(linuxscsi, 'echo_scsi_command')
|
|
||||||
def test_perform_unit_add_for_s390(self, mock_execute):
|
|
||||||
device_number = "0.0.2319"
|
|
||||||
target_wwn = "0x50014380242b9751"
|
|
||||||
lun = 1
|
|
||||||
libvirt_utils.perform_unit_add_for_s390(device_number, target_wwn, lun)
|
|
||||||
|
|
||||||
mock_execute.assert_called_once_with(
|
|
||||||
'/sys/bus/ccw/drivers/zfcp/0.0.2319/0x50014380242b9751/unit_add',
|
|
||||||
lun)
|
|
||||||
|
|
||||||
@mock.patch.object(libvirt_utils.LOG, 'warn')
|
|
||||||
@mock.patch.object(linuxscsi, 'echo_scsi_command')
|
|
||||||
def test_perform_unit_add_for_s390_failed(self, mock_execute, mock_warn):
|
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError(
|
|
||||||
exit_code=1, stderr='oops')
|
|
||||||
device_number = "0.0.2319"
|
|
||||||
target_wwn = "0x50014380242b9751"
|
|
||||||
lun = 1
|
|
||||||
libvirt_utils.perform_unit_add_for_s390(device_number, target_wwn, lun)
|
|
||||||
|
|
||||||
mock_execute.assert_called_once_with(
|
|
||||||
'/sys/bus/ccw/drivers/zfcp/0.0.2319/0x50014380242b9751/unit_add',
|
|
||||||
lun)
|
|
||||||
# NOTE(mriedem): A better test is to probably make sure that the stderr
|
|
||||||
# message is logged in the warning but that gets messy with Message
|
|
||||||
# objects and mock.call_args.
|
|
||||||
self.assertEqual(1, mock_warn.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(linuxscsi, 'echo_scsi_command')
|
|
||||||
def test_perform_unit_remove_for_s390(self, mock_execute):
|
|
||||||
device_number = "0.0.2319"
|
|
||||||
target_wwn = "0x50014380242b9751"
|
|
||||||
lun = 1
|
|
||||||
libvirt_utils.perform_unit_remove_for_s390(device_number,
|
|
||||||
target_wwn, lun)
|
|
||||||
|
|
||||||
mock_execute.assert_called_once_with(
|
|
||||||
'/sys/bus/ccw/drivers/zfcp/'
|
|
||||||
'0.0.2319/0x50014380242b9751/unit_remove', lun)
|
|
||||||
|
|
||||||
@mock.patch.object(libvirt_utils.LOG, 'warn')
|
|
||||||
@mock.patch.object(linuxscsi, 'echo_scsi_command')
|
|
||||||
def test_perform_unit_remove_for_s390_failed(self, mock_execute,
|
|
||||||
mock_warn):
|
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError(
|
|
||||||
exit_code=1, stderr='oops')
|
|
||||||
device_number = "0.0.2319"
|
|
||||||
target_wwn = "0x50014380242b9751"
|
|
||||||
lun = 1
|
|
||||||
libvirt_utils.perform_unit_remove_for_s390(device_number,
|
|
||||||
target_wwn, lun)
|
|
||||||
|
|
||||||
mock_execute.assert_called_once_with(
|
|
||||||
'/sys/bus/ccw/drivers/zfcp/'
|
|
||||||
'0.0.2319/0x50014380242b9751/unit_remove', lun)
|
|
||||||
# NOTE(mriedem): A better test is to probably make sure that the stderr
|
|
||||||
# message is logged in the warning but that gets messy with Message
|
|
||||||
# objects and mock.call_args.
|
|
||||||
self.assertEqual(1, mock_warn.call_count)
|
|
||||||
|
@ -13,24 +13,18 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import glob
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import time
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import fixtures
|
import fixtures
|
||||||
import mock
|
import mock
|
||||||
|
from os_brick.initiator import connector
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import six
|
|
||||||
|
|
||||||
from nova.compute import arch
|
from nova.compute import arch
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.storage import linuxscsi
|
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests.unit.virt.libvirt import fake_libvirt_utils
|
|
||||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova.virt.libvirt import host
|
from nova.virt.libvirt import host
|
||||||
@ -314,12 +308,6 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
|
|||||||
fake_dev_path = "/dev/disk/by-path/" + dev_format
|
fake_dev_path = "/dev/disk/by-path/" + dev_format
|
||||||
return fake_dev_path
|
return fake_dev_path
|
||||||
|
|
||||||
def test_rescan_multipath(self):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
libvirt_driver._rescan_multipath()
|
|
||||||
expected_multipath_cmd = ('multipath', '-r')
|
|
||||||
self.assertIn(expected_multipath_cmd, self.executes)
|
|
||||||
|
|
||||||
def test_iscsiadm_discover_parsing(self):
|
def test_iscsiadm_discover_parsing(self):
|
||||||
# Ensure that parsing iscsiadm discover ignores cruft.
|
# Ensure that parsing iscsiadm discover ignores cruft.
|
||||||
|
|
||||||
@ -340,223 +328,35 @@ Setting up iSCSI targets: unused
|
|||||||
%s %s
|
%s %s
|
||||||
""" % (targets[0][0], targets[0][1], targets[1][0], targets[1][1])
|
""" % (targets[0][0], targets[0][1], targets[1][0], targets[1][1])
|
||||||
driver = volume.LibvirtISCSIVolumeDriver("none")
|
driver = volume.LibvirtISCSIVolumeDriver("none")
|
||||||
out = driver._get_target_portals_from_iscsiadm_output(sample_input)
|
out = driver.connector._get_target_portals_from_iscsiadm_output(
|
||||||
|
sample_input)
|
||||||
self.assertEqual(out, targets)
|
self.assertEqual(out, targets)
|
||||||
|
|
||||||
def test_libvirt_iscsi_get_host_device(self, transport=None):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
iscsi_properties = connection_info['data']
|
|
||||||
expected_device = self.generate_device(transport, 1, False)
|
|
||||||
if transport:
|
|
||||||
self.stubs.Set(glob, 'glob', lambda x: [expected_device])
|
|
||||||
self.stubs.Set(libvirt_driver, '_validate_transport',
|
|
||||||
lambda x: True)
|
|
||||||
device = libvirt_driver._get_host_device(iscsi_properties)
|
|
||||||
self.assertEqual(expected_device, device)
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_get_host_device_with_transport(self):
|
|
||||||
self.flags(iscsi_iface='fake_transport', group='libvirt')
|
|
||||||
self.test_libvirt_iscsi_get_host_device('fake_transport')
|
|
||||||
|
|
||||||
@mock.patch.object(volume.utils, 'execute')
|
|
||||||
def test_libvirt_iscsi_validate_transport(self, mock_execute):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
sample_output = ('# BEGIN RECORD 2.0-872\n'
|
|
||||||
'iface.iscsi_ifacename = %s.fake_suffix\n'
|
|
||||||
'iface.net_ifacename = <empty>\n'
|
|
||||||
'iface.ipaddress = <empty>\n'
|
|
||||||
'iface.hwaddress = 00:53:00:00:53:00\n'
|
|
||||||
'iface.transport_name = %s\n'
|
|
||||||
'iface.initiatorname = <empty>\n'
|
|
||||||
'# END RECORD')
|
|
||||||
for tport in libvirt_driver.supported_transports:
|
|
||||||
mock_execute.return_value = (sample_output % (tport, tport), '')
|
|
||||||
self.assertTrue(libvirt_driver._validate_transport(
|
|
||||||
tport + '.fake_suffix'))
|
|
||||||
|
|
||||||
mock_execute.return_value = ("", 'iscsiadm: Could not '
|
|
||||||
'read iface fake_transport (6)')
|
|
||||||
self.assertFalse(libvirt_driver._validate_transport('fake_transport'))
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver(self, transport=None):
|
def test_libvirt_iscsi_driver(self, transport=None):
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
self.assertIsInstance(libvirt_driver.connector,
|
||||||
self.iqn, False, transport)
|
connector.ISCSIConnector)
|
||||||
if transport is not None:
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
|
||||||
lambda x: self.generate_device(transport, 1, False))
|
|
||||||
self.stubs.Set(libvirt_driver, '_validate_transport',
|
|
||||||
lambda x: True)
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
expected_commands = [('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location),
|
|
||||||
('iscsiadm', '-m', 'session'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--login'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--op', 'update',
|
|
||||||
'-n', 'node.startup', '-v', 'automatic'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--rescan'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--op', 'update',
|
|
||||||
'-n', 'node.startup', '-v', 'manual'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--logout'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--op', 'delete')]
|
|
||||||
self.assertEqual(expected_commands, self.executes)
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_with_transport(self):
|
|
||||||
self.flags(iscsi_iface='fake_transport', group='libvirt')
|
|
||||||
self.test_libvirt_iscsi_driver('fake_transport')
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_still_in_use(self, transport=None):
|
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
dev_name = self.generate_device(transport, 1, True)
|
|
||||||
if transport is not None:
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
|
||||||
lambda x: self.generate_device(transport, 1, False))
|
|
||||||
self.stubs.Set(libvirt_driver, '_validate_transport',
|
|
||||||
lambda x: True)
|
|
||||||
devs = [self.generate_device(transport, 2, False)]
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
|
||||||
vol = {'id': 1, 'name': self.name}
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location, self.iqn)
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
expected_commands = [('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location),
|
|
||||||
('iscsiadm', '-m', 'session'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--login'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--op', 'update',
|
|
||||||
'-n', 'node.startup', '-v', 'automatic'),
|
|
||||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
|
||||||
'-p', self.location, '--rescan'),
|
|
||||||
('cp', '/dev/stdin',
|
|
||||||
'/sys/block/%s/device/delete' % dev_name)]
|
|
||||||
self.assertEqual(self.executes, expected_commands)
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_still_in_use_with_transport(self):
|
|
||||||
self.flags(iscsi_iface='fake_transport', group='libvirt')
|
|
||||||
self.test_libvirt_iscsi_driver_still_in_use('fake_transport')
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_disconnect_multipath_error(self,
|
|
||||||
transport=None):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
if transport is None:
|
|
||||||
prefix = ""
|
|
||||||
else:
|
|
||||||
prefix = "pci-0000:00:00.0-"
|
|
||||||
devs = [self.generate_device(transport, 2, False)]
|
|
||||||
iscsi_devs = ['%sip-fake-ip-iscsi-fake-portal-lun-2' % prefix]
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch.object(os.path, 'exists', return_value=True),
|
|
||||||
mock.patch.object(self.fake_conn, '_get_all_block_devices',
|
|
||||||
return_value=devs),
|
|
||||||
mock.patch.object(libvirt_driver, '_rescan_multipath'),
|
|
||||||
mock.patch.object(libvirt_driver, '_run_multipath'),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_multipath_device_name',
|
|
||||||
return_value='/dev/mapper/fake-multipath-devname'),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_iscsi_devices',
|
|
||||||
return_value=iscsi_devs),
|
|
||||||
mock.patch.object(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
return_value=[('fake-ip', 'fake-portal')]),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_multipath_iqn',
|
|
||||||
return_value='fake-portal'),
|
|
||||||
) as (mock_exists, mock_devices, mock_rescan_multipath,
|
|
||||||
mock_run_multipath, mock_device_name, mock_iscsi_devices,
|
|
||||||
mock_get_portals, mock_get_iqn):
|
|
||||||
mock_run_multipath.side_effect = processutils.ProcessExecutionError
|
|
||||||
vol = {'id': 1, 'name': self.name}
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
mock_run_multipath.assert_called_once_with(
|
|
||||||
['-f', 'fake-multipath-devname'],
|
|
||||||
check_exit_code=[0, 1])
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_get_config(self, transport=None):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
dev_name = self.generate_device(transport, 1, True)
|
|
||||||
dev_path = '/dev/disk/by-path/%s' % (dev_name)
|
|
||||||
vol = {'id': 1, 'name': self.name}
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location,
|
|
||||||
self.iqn, False, transport)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual('block', tree.get('type'))
|
|
||||||
self.assertEqual(dev_path, tree.find('./source').get('dev'))
|
|
||||||
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual('block', tree.get('type'))
|
|
||||||
self.assertEqual(dev_path, tree.find('./source').get('dev'))
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_get_config_with_transport(self):
|
|
||||||
self.flags(iscsi_iface = 'fake_transport', group='libvirt')
|
|
||||||
self.test_libvirt_iscsi_driver_get_config('fake_transport')
|
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_multipath_id(self):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
self.stubs.Set(libvirt_driver, '_run_iscsiadm_bare',
|
|
||||||
lambda x, check_exit_code: ('',))
|
|
||||||
self.stubs.Set(libvirt_driver, '_rescan_iscsi', lambda: None)
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_host_device', lambda x: None)
|
|
||||||
self.stubs.Set(libvirt_driver, '_rescan_multipath', lambda: None)
|
|
||||||
fake_multipath_id = 'fake_multipath_id'
|
|
||||||
fake_multipath_device = '/dev/mapper/%s' % fake_multipath_id
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_multipath_device_name',
|
|
||||||
lambda x: fake_multipath_device)
|
|
||||||
|
|
||||||
def fake_disconnect_volume_multipath_iscsi(iscsi_properties,
|
|
||||||
multipath_device):
|
|
||||||
if fake_multipath_device != multipath_device:
|
|
||||||
raise Exception('Invalid multipath_device.')
|
|
||||||
|
|
||||||
self.stubs.Set(libvirt_driver, '_disconnect_volume_multipath_iscsi',
|
|
||||||
fake_disconnect_volume_multipath_iscsi)
|
|
||||||
with mock.patch.object(os.path, 'exists', return_value=True):
|
|
||||||
vol = {'id': 1, 'name': self.name}
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
libvirt_driver.connect_volume(connection_info,
|
|
||||||
self.disk_info)
|
|
||||||
self.assertEqual(fake_multipath_id,
|
|
||||||
connection_info['data']['multipath_id'])
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "fake")
|
|
||||||
|
|
||||||
def test_sanitize_log_run_iscsiadm(self):
|
def test_sanitize_log_run_iscsiadm(self):
|
||||||
# Tests that the parameters to the _run_iscsiadm function are sanitized
|
# Tests that the parameters to the os-brick connector's
|
||||||
# for passwords when logged.
|
# _run_iscsiadm function are sanitized for passwords when logged.
|
||||||
def fake_debug(*args, **kwargs):
|
def fake_debug(*args, **kwargs):
|
||||||
self.assertIn('node.session.auth.password', args[0])
|
self.assertIn('node.session.auth.password', args[0])
|
||||||
self.assertNotIn('scrubme', args[0])
|
self.assertNotIn('scrubme', args[0])
|
||||||
|
|
||||||
|
def fake_execute(*args, **kwargs):
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||||
|
libvirt_driver.connector.set_execute(fake_execute)
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
connection_info = self.iscsi_connection(self.vol, self.location,
|
||||||
self.iqn)
|
self.iqn)
|
||||||
iscsi_properties = connection_info['data']
|
iscsi_properties = connection_info['data']
|
||||||
with mock.patch.object(volume.LOG, 'debug',
|
with mock.patch.object(connector.LOG, 'debug',
|
||||||
side_effect=fake_debug) as debug_mock:
|
side_effect=fake_debug) as debug_mock:
|
||||||
libvirt_driver._iscsiadm_update(iscsi_properties,
|
libvirt_driver.connector._iscsiadm_update(
|
||||||
'node.session.auth.password',
|
iscsi_properties, 'node.session.auth.password', 'scrubme')
|
||||||
'scrubme')
|
|
||||||
# we don't care what the log message is, we just want to make sure
|
# we don't care what the log message is, we just want to make sure
|
||||||
# our stub method is called which asserts the password is scrubbed
|
# our stub method is called which asserts the password is scrubbed
|
||||||
self.assertTrue(debug_mock.called)
|
self.assertTrue(debug_mock.called)
|
||||||
@ -733,337 +533,6 @@ Setting up iSCSI targets: unused
|
|||||||
self.assertEqual(tree.find('./auth/secret').get('uuid'), SECRET_UUID)
|
self.assertEqual(tree.find('./auth/secret').get('uuid'), SECRET_UUID)
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
||||||
|
|
||||||
def test_libvirt_iscsi_driver_discovery_chap_enable(self):
|
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
connection_info = self.iscsi_connection_discovery_chap_enable(
|
|
||||||
self.vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
expected_commands = [('iscsiadm', '-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', self.location, '--op', 'update',
|
|
||||||
'-n', 'discovery.sendtargets.auth.authmethod',
|
|
||||||
'-v', 'CHAP',
|
|
||||||
'-n', 'discovery.sendtargets.auth.username',
|
|
||||||
'-v', 'testuser',
|
|
||||||
'-n', 'discovery.sendtargets.auth.password',
|
|
||||||
'-v', '123456'),
|
|
||||||
('iscsiadm', '-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', self.location, '--discover'),
|
|
||||||
('iscsiadm', '-m', 'node', '--rescan'),
|
|
||||||
('iscsiadm', '-m', 'session', '--rescan'),
|
|
||||||
('multipath', '-r'),
|
|
||||||
('multipath', '-r'),
|
|
||||||
('iscsiadm', '-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', self.location, '--op', 'update',
|
|
||||||
'-n', 'discovery.sendtargets.auth.authmethod',
|
|
||||||
'-v', 'CHAP',
|
|
||||||
'-n', 'discovery.sendtargets.auth.username',
|
|
||||||
'-v', 'testuser',
|
|
||||||
'-n', 'discovery.sendtargets.auth.password',
|
|
||||||
'-v', '123456'),
|
|
||||||
('iscsiadm', '-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', self.location, '--discover'),
|
|
||||||
('multipath', '-r')]
|
|
||||||
self.assertEqual(self.executes, expected_commands)
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume(self):
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
dev_str = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (self.location,
|
|
||||||
self.iqn)
|
|
||||||
self.assertEqual(tree.get('type'), 'block')
|
|
||||||
self.assertEqual(tree.find('./source').get('dev'), dev_str)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume_with_multipath(self):
|
|
||||||
self.flags(iscsi_use_multipath=True, group='libvirt')
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
devs = ['/dev/mapper/sda', '/dev/mapper/sdb']
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
connection_info['data']['device_path'] = mpdev_filepath
|
|
||||||
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
|
|
||||||
iscsi_devs = ['ip-%s-iscsi-%s-lun-0' % (self.location, self.iqn)]
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_iscsi_devices',
|
|
||||||
lambda: iscsi_devs)
|
|
||||||
self.stubs.Set(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
lambda x: [[self.location, self.iqn]])
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
|
|
||||||
libvirt_driver._get_multipath_iqn = lambda x: self.iqn
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
expected_multipath_cmd = ('multipath', '-f', 'foo')
|
|
||||||
self.assertIn(expected_multipath_cmd, self.executes)
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume_with_multipath_connecting(self):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
ip_iqns = [[self.location, self.iqn],
|
|
||||||
['10.0.2.16:3260', self.iqn],
|
|
||||||
[self.location,
|
|
||||||
'iqn.2010-10.org.openstack:volume-00000002']]
|
|
||||||
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch.object(os.path, 'exists', return_value=True),
|
|
||||||
mock.patch.object(libvirt_driver, '_run_iscsiadm_bare'),
|
|
||||||
mock.patch.object(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
return_value=ip_iqns),
|
|
||||||
mock.patch.object(libvirt_driver, '_connect_to_iscsi_portal'),
|
|
||||||
mock.patch.object(libvirt_driver, '_rescan_iscsi'),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_host_device',
|
|
||||||
return_value='fake-device'),
|
|
||||||
mock.patch.object(libvirt_driver, '_rescan_multipath'),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_multipath_device_name',
|
|
||||||
return_value='/dev/mapper/fake-mpath-devname')
|
|
||||||
) as (mock_exists, mock_run_iscsiadm_bare, mock_get_portals,
|
|
||||||
mock_connect_iscsi, mock_rescan_iscsi, mock_host_device,
|
|
||||||
mock_rescan_multipath, mock_device_name):
|
|
||||||
vol = {'id': 1, 'name': self.name}
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
|
|
||||||
# Verify that the supplied iqn is used when it shares the same
|
|
||||||
# iqn between multiple portals.
|
|
||||||
connection_info = self.iscsi_connection(vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
props1 = connection_info['data'].copy()
|
|
||||||
props2 = connection_info['data'].copy()
|
|
||||||
props2['target_portal'] = '10.0.2.16:3260'
|
|
||||||
expected_calls = [mock.call(props1), mock.call(props2),
|
|
||||||
mock.call(props1)]
|
|
||||||
self.assertEqual(expected_calls, mock_connect_iscsi.call_args_list)
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume_with_multipath_still_in_use(self):
|
|
||||||
name = 'volume-00000001'
|
|
||||||
location = '10.0.2.15:3260'
|
|
||||||
iqn = 'iqn.2010-10.org.openstack:%s' % name
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
|
|
||||||
def _get_multipath_device_name(path):
|
|
||||||
if '%s-lun-1' % iqn in path:
|
|
||||||
return mpdev_filepath
|
|
||||||
return '/dev/mapper/donotdisconnect'
|
|
||||||
|
|
||||||
self.flags(iscsi_use_multipath=True, group='libvirt')
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
libvirt_driver._get_multipath_device_name =\
|
|
||||||
lambda x: _get_multipath_device_name(x)
|
|
||||||
|
|
||||||
block_devs = ['/dev/disks/by-path/%s-iscsi-%s-lun-2' % (location, iqn)]
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices',
|
|
||||||
lambda: block_devs)
|
|
||||||
|
|
||||||
vol = {'id': 1, 'name': name}
|
|
||||||
connection_info = self.iscsi_connection(vol, location, iqn)
|
|
||||||
connection_info['data']['device_path'] = mpdev_filepath
|
|
||||||
|
|
||||||
libvirt_driver._get_multipath_iqn = lambda x: iqn
|
|
||||||
|
|
||||||
iscsi_devs = ['1.2.3.4-iscsi-%s-lun-1' % iqn,
|
|
||||||
'%s-iscsi-%s-lun-1' % (location, iqn),
|
|
||||||
'%s-iscsi-%s-lun-2' % (location, iqn),
|
|
||||||
'pci-0000:00:00.0-ip-%s-iscsi-%s-lun-3' % (location,
|
|
||||||
iqn)]
|
|
||||||
libvirt_driver._get_iscsi_devices = lambda: iscsi_devs
|
|
||||||
|
|
||||||
self.stubs.Set(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
lambda x: [[location, iqn]])
|
|
||||||
|
|
||||||
# Set up disconnect volume mock expectations
|
|
||||||
self.mox.StubOutWithMock(libvirt_driver, '_delete_device')
|
|
||||||
self.mox.StubOutWithMock(libvirt_driver, '_rescan_multipath')
|
|
||||||
libvirt_driver._rescan_multipath()
|
|
||||||
libvirt_driver._delete_device('/dev/disk/by-path/%s' % iscsi_devs[0])
|
|
||||||
libvirt_driver._delete_device('/dev/disk/by-path/%s' % iscsi_devs[1])
|
|
||||||
libvirt_driver._rescan_multipath()
|
|
||||||
|
|
||||||
# Ensure that the mpath devices are deleted
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume_with_multipath_disconnected(self):
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
volumes = [{'name': self.name,
|
|
||||||
'location': self.location,
|
|
||||||
'iqn': self.iqn,
|
|
||||||
'mpdev_filepath': '/dev/mapper/disconnect'},
|
|
||||||
{'name': 'volume-00000002',
|
|
||||||
'location': '10.0.2.15:3260',
|
|
||||||
'iqn': 'iqn.2010-10.org.openstack:volume-00000002',
|
|
||||||
'mpdev_filepath': '/dev/mapper/donotdisconnect'}]
|
|
||||||
iscsi_devs = ['ip-%s-iscsi-%s-lun-1' % (volumes[0]['location'],
|
|
||||||
volumes[0]['iqn']),
|
|
||||||
'ip-%s-iscsi-%s-lun-1' % (volumes[1]['location'],
|
|
||||||
volumes[1]['iqn'])]
|
|
||||||
|
|
||||||
def _get_multipath_device_name(path):
|
|
||||||
if '%s-lun-1' % volumes[0]['iqn'] in path:
|
|
||||||
return volumes[0]['mpdev_filepath']
|
|
||||||
else:
|
|
||||||
return volumes[1]['mpdev_filepath']
|
|
||||||
|
|
||||||
def _get_multipath_iqn(mpdev):
|
|
||||||
if volumes[0]['mpdev_filepath'] == mpdev:
|
|
||||||
return volumes[0]['iqn']
|
|
||||||
else:
|
|
||||||
return volumes[1]['iqn']
|
|
||||||
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch.object(os.path, 'exists', return_value=True),
|
|
||||||
mock.patch.object(self.fake_conn, '_get_all_block_devices',
|
|
||||||
retrun_value=[volumes[1]['mpdev_filepath']]),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_multipath_device_name',
|
|
||||||
_get_multipath_device_name),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_multipath_iqn',
|
|
||||||
_get_multipath_iqn),
|
|
||||||
mock.patch.object(libvirt_driver, '_get_iscsi_devices',
|
|
||||||
return_value=iscsi_devs),
|
|
||||||
mock.patch.object(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
return_value=[[volumes[0]['location'],
|
|
||||||
volumes[0]['iqn']],
|
|
||||||
[volumes[1]['location'],
|
|
||||||
volumes[1]['iqn']]]),
|
|
||||||
mock.patch.object(libvirt_driver, '_disconnect_mpath')
|
|
||||||
) as (mock_exists, mock_devices, mock_device_name, mock_get_iqn,
|
|
||||||
mock_iscsi_devices, mock_get_portals, mock_disconnect_mpath):
|
|
||||||
vol = {'id': 1, 'name': volumes[0]['name']}
|
|
||||||
connection_info = self.iscsi_connection(vol,
|
|
||||||
volumes[0]['location'],
|
|
||||||
volumes[0]['iqn'])
|
|
||||||
connection_info['data']['device_path'] =\
|
|
||||||
volumes[0]['mpdev_filepath']
|
|
||||||
libvirt_driver.use_multipath = True
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
# Ensure that the mpath device is disconnected.
|
|
||||||
ips_iqns = []
|
|
||||||
ips_iqns.append([volumes[0]['location'], volumes[0]['iqn']])
|
|
||||||
mock_disconnect_mpath.assert_called_once_with(
|
|
||||||
connection_info['data'], ips_iqns)
|
|
||||||
|
|
||||||
def test_libvirt_kvm_volume_with_multipath_getmpdev(self):
|
|
||||||
self.flags(iscsi_use_multipath=True, group='libvirt')
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
|
||||||
name0 = 'volume-00000000'
|
|
||||||
iqn0 = 'iqn.2010-10.org.openstack:%s' % name0
|
|
||||||
dev0 = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-0' % (self.location, iqn0)
|
|
||||||
dev = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (self.location,
|
|
||||||
self.iqn)
|
|
||||||
devs = [dev0, dev]
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
|
||||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
|
||||||
self.iqn)
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
|
|
||||||
self.stubs.Set(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
lambda x: [['fake_portal1', 'fake_iqn1']])
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
|
|
||||||
def test_libvirt_kvm_iser_volume_with_multipath(self):
|
|
||||||
self.flags(iser_use_multipath=True, group='libvirt')
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
self.stubs.Set(time, 'sleep', lambda x: eventlet.sleep(0.1))
|
|
||||||
devs = ['/dev/mapper/sda', '/dev/mapper/sdb']
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
|
||||||
libvirt_driver = volume.LibvirtISERVolumeDriver(self.fake_conn)
|
|
||||||
name = 'volume-00000001'
|
|
||||||
location = '10.0.2.15:3260'
|
|
||||||
iqn = 'iqn.2010-10.org.iser.openstack:%s' % name
|
|
||||||
vol = {'id': 1, 'name': name}
|
|
||||||
connection_info = self.iser_connection(vol, location, iqn)
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
connection_info['data']['device_path'] = mpdev_filepath
|
|
||||||
disk_info = {
|
|
||||||
"bus": "virtio",
|
|
||||||
"dev": "vde",
|
|
||||||
"type": "disk",
|
|
||||||
}
|
|
||||||
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
|
|
||||||
iscsi_devs = ['ip-%s-iscsi-%s-lun-0' % (location, iqn)]
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_iscsi_devices',
|
|
||||||
lambda: iscsi_devs)
|
|
||||||
self.stubs.Set(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
lambda x: [[location, iqn]])
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
|
||||||
lambda x: self.generate_device('iser', 0, False))
|
|
||||||
|
|
||||||
libvirt_driver.connect_volume(connection_info, disk_info)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
|
|
||||||
libvirt_driver._get_multipath_iqn = lambda x: iqn
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
expected_multipath_cmd = ('multipath', '-f', 'foo')
|
|
||||||
self.assertIn(expected_multipath_cmd, self.executes)
|
|
||||||
|
|
||||||
def test_libvirt_kvm_iser_volume_with_multipath_getmpdev(self):
|
|
||||||
self.flags(iser_use_multipath=True, group='libvirt')
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
self.stubs.Set(time, 'sleep', lambda x: eventlet.sleep(0.1))
|
|
||||||
libvirt_driver = volume.LibvirtISERVolumeDriver(self.fake_conn)
|
|
||||||
name0 = 'volume-00000000'
|
|
||||||
location0 = '10.0.2.15:3260'
|
|
||||||
iqn0 = 'iqn.2010-10.org.iser.openstack:%s' % name0
|
|
||||||
dev0 = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-0' % (location0, iqn0)
|
|
||||||
name = 'volume-00000001'
|
|
||||||
location = '10.0.2.15:3260'
|
|
||||||
iqn = 'iqn.2010-10.org.iser.openstack:%s' % name
|
|
||||||
vol = {'id': 1, 'name': name}
|
|
||||||
dev = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location, iqn)
|
|
||||||
devs = [dev0, dev]
|
|
||||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_iscsi_devices', lambda: [])
|
|
||||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
|
||||||
lambda x: self.generate_device('iser', 1, False))
|
|
||||||
connection_info = self.iser_connection(vol, location, iqn)
|
|
||||||
mpdev_filepath = '/dev/mapper/foo'
|
|
||||||
disk_info = {
|
|
||||||
"bus": "virtio",
|
|
||||||
"dev": "vde",
|
|
||||||
"type": "disk",
|
|
||||||
}
|
|
||||||
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
|
|
||||||
self.stubs.Set(libvirt_driver,
|
|
||||||
'_get_target_portals_from_iscsiadm_output',
|
|
||||||
lambda x: [['fake_portal1', 'fake_iqn1']])
|
|
||||||
libvirt_driver.connect_volume(connection_info, disk_info)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, 'vde')
|
|
||||||
|
|
||||||
def test_libvirt_nfs_driver(self):
|
def test_libvirt_nfs_driver(self):
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
# NOTE(vish) exists is to make driver assume connecting worked
|
||||||
mnt_base = '/mnt'
|
mnt_base = '/mnt'
|
||||||
@ -1176,44 +645,11 @@ Setting up iSCSI targets: unused
|
|||||||
]
|
]
|
||||||
self.assertEqual(expected_commands, self.executes)
|
self.assertEqual(expected_commands, self.executes)
|
||||||
|
|
||||||
def aoe_connection(self, shelf, lun):
|
|
||||||
aoedev = 'e%s.%s' % (shelf, lun)
|
|
||||||
aoedevpath = '/dev/etherd/%s' % (aoedev)
|
|
||||||
return {
|
|
||||||
'driver_volume_type': 'aoe',
|
|
||||||
'data': {
|
|
||||||
'target_shelf': shelf,
|
|
||||||
'target_lun': lun,
|
|
||||||
'device_path': aoedevpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mock.patch('os.path.exists', return_value=True)
|
@mock.patch('os.path.exists', return_value=True)
|
||||||
def test_libvirt_aoe_driver(self, exists):
|
def test_libvirt_aoe_driver(self, exists):
|
||||||
libvirt_driver = volume.LibvirtAOEVolumeDriver(self.fake_conn)
|
libvirt_driver = volume.LibvirtAOEVolumeDriver(self.fake_conn)
|
||||||
shelf = '100'
|
self.assertIsInstance(libvirt_driver.connector,
|
||||||
lun = '1'
|
connector.AoEConnector)
|
||||||
connection_info = self.aoe_connection(shelf, lun)
|
|
||||||
aoedev = 'e%s.%s' % (shelf, lun)
|
|
||||||
aoedevpath = '/dev/etherd/%s' % (aoedev)
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
exists.assert_called_with(aoedevpath)
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
self.assertEqual(aoedevpath, connection_info['data']['device_path'])
|
|
||||||
expected_commands = [('aoe-revalidate', aoedev)]
|
|
||||||
self.assertEqual(expected_commands, self.executes)
|
|
||||||
|
|
||||||
def test_libvirt_aoe_driver_get_config(self):
|
|
||||||
libvirt_driver = volume.LibvirtAOEVolumeDriver(self.fake_conn)
|
|
||||||
shelf = '100'
|
|
||||||
lun = '1'
|
|
||||||
connection_info = self.aoe_connection(shelf, lun)
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
aoedevpath = '/dev/etherd/e%s.%s' % (shelf, lun)
|
|
||||||
self.assertEqual('block', tree.get('type'))
|
|
||||||
self.assertEqual(aoedevpath, tree.find('./source').get('dev'))
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
|
|
||||||
def test_libvirt_glusterfs_driver(self):
|
def test_libvirt_glusterfs_driver(self):
|
||||||
mnt_base = '/mnt'
|
mnt_base = '/mnt'
|
||||||
@ -1343,164 +779,15 @@ Setting up iSCSI targets: unused
|
|||||||
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
libvirt_driver.disconnect_volume(connection_info, "vde")
|
||||||
|
|
||||||
def fibrechan_connection(self, volume, location, wwn):
|
|
||||||
return {
|
|
||||||
'driver_volume_type': 'fibrechan',
|
|
||||||
'data': {
|
|
||||||
'volume_id': volume['id'],
|
|
||||||
'target_portal': location,
|
|
||||||
'target_wwn': wwn,
|
|
||||||
'target_lun': 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_libvirt_fibrechan_driver(self):
|
def test_libvirt_fibrechan_driver(self):
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas',
|
|
||||||
fake_libvirt_utils.get_fc_hbas)
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas_info',
|
|
||||||
fake_libvirt_utils.get_fc_hbas_info)
|
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
self.stubs.Set(os.path, 'realpath', lambda x: '/dev/sdb')
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
||||||
multipath_devname = '/dev/md-1'
|
self.assertIsInstance(libvirt_driver.connector,
|
||||||
devices = {"device": multipath_devname,
|
connector.FibreChannelConnector)
|
||||||
"id": "1234567890",
|
|
||||||
"devices": [{'device': '/dev/sdb',
|
|
||||||
'address': '1:0:0:1',
|
|
||||||
'host': 1, 'channel': 0,
|
|
||||||
'id': 0, 'lun': 1}]}
|
|
||||||
self.stubs.Set(linuxscsi, 'find_multipath_device', lambda x: devices)
|
|
||||||
self.stubs.Set(linuxscsi, 'remove_device', lambda x: None)
|
|
||||||
# Should work for string, unicode, and list
|
|
||||||
wwns = ['1234567890123456', six.text_type('1234567890123456'),
|
|
||||||
['1234567890123456', '1234567890123457']]
|
|
||||||
for wwn in wwns:
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location, wwn)
|
|
||||||
mount_device = "vde"
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
|
|
||||||
# Test the scenario where multipath_id is returned
|
def _test_libvirt_fibrechan_driver_s390(self):
|
||||||
libvirt_driver.disconnect_volume(connection_info, mount_device)
|
|
||||||
self.assertEqual(multipath_devname,
|
|
||||||
connection_info['data']['device_path'])
|
|
||||||
expected_commands = []
|
|
||||||
self.assertEqual(expected_commands, self.executes)
|
|
||||||
# Test the scenario where multipath_id is not returned
|
|
||||||
connection_info["data"]["devices"] = devices["devices"]
|
|
||||||
del connection_info["data"]["multipath_id"]
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, mount_device)
|
|
||||||
expected_commands = []
|
|
||||||
self.assertEqual(expected_commands, self.executes)
|
|
||||||
|
|
||||||
# Should not work for anything other than string, unicode, and list
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location, 123)
|
|
||||||
self.assertRaises(exception.NovaException,
|
|
||||||
libvirt_driver.connect_volume,
|
|
||||||
connection_info, self.disk_info)
|
|
||||||
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas', lambda: [])
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas_info', lambda: [])
|
|
||||||
self.assertRaises(exception.NovaException,
|
|
||||||
libvirt_driver.connect_volume,
|
|
||||||
connection_info, self.disk_info)
|
|
||||||
|
|
||||||
@mock.patch.object(libvirt_utils, 'get_fc_hbas',
|
|
||||||
side_effect=fake_libvirt_utils.get_fc_hbas)
|
|
||||||
@mock.patch.object(libvirt_utils, 'get_fc_hbas_info',
|
|
||||||
side_effect=fake_libvirt_utils.get_fc_hbas_info)
|
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
||||||
@mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
|
|
||||||
@mock.patch.object(linuxscsi, 'remove_device', return_value=None)
|
|
||||||
@mock.patch.object(linuxscsi, 'find_multipath_device')
|
|
||||||
def test_libvirt_fc_find_multipath_device_none(self, mock_find,
|
|
||||||
mock_remove, mock_realpath, mock_exists, mock_hbasinfo, mock_hbas):
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
||||||
multipath_devname = '/dev/md-1'
|
self.assertIsInstance(libvirt_driver.connector,
|
||||||
devices = {"device": multipath_devname,
|
connector.FibreChannelConnectorS390X)
|
||||||
"id": "1234567890",
|
|
||||||
"devices": [{'device': '/dev/sdb',
|
|
||||||
'address': '1:0:0:1',
|
|
||||||
'host': 1, 'channel': 0,
|
|
||||||
'id': 0, 'lun': 1}]}
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location,
|
|
||||||
'1234567890123456')
|
|
||||||
# Test the scenario where multipath_id is returned during
|
|
||||||
# connect volume
|
|
||||||
mock_find.return_value = devices
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
self.assertEqual(0, mock_remove.call_count)
|
|
||||||
|
|
||||||
self.assertIn('multipath_id', connection_info['data'])
|
|
||||||
|
|
||||||
# But NOT during disconnect_volume
|
|
||||||
mock_find.return_value = None
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
|
||||||
self.assertEqual(0, mock_remove.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(volume.LibvirtFibreChannelVolumeDriver,
|
|
||||||
'_remove_lun_from_s390')
|
|
||||||
def _test_libvirt_fibrechan_driver_s390(self, mock_remove_lun):
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas',
|
|
||||||
fake_libvirt_utils.get_fc_hbas)
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas_info',
|
|
||||||
fake_libvirt_utils.get_fc_hbas_info)
|
|
||||||
# NOTE(vish) exists is to make driver assume connecting worked
|
|
||||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
|
||||||
self.stubs.Set(os.path, 'realpath', lambda x: '/dev/sdb')
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
|
||||||
multipath_devname = '/dev/md-1'
|
|
||||||
devices = {"device": multipath_devname,
|
|
||||||
"id": "1234567890",
|
|
||||||
"devices": [{'device': '/dev/sdb',
|
|
||||||
'address': '1:0:0:1',
|
|
||||||
'host': 1, 'channel': 0,
|
|
||||||
'id': 0, 'lun': 1}]}
|
|
||||||
self.stubs.Set(linuxscsi, 'find_multipath_device', lambda x: devices)
|
|
||||||
self.stubs.Set(linuxscsi, 'remove_device', lambda x: None)
|
|
||||||
# Should work for string, unicode, and list
|
|
||||||
wwns = ['1234567890123456', six.text_type('1234567890123456'),
|
|
||||||
['1234567890123456']]
|
|
||||||
expected_remove_calls = []
|
|
||||||
for wwn in wwns:
|
|
||||||
self.executes = []
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location, wwn)
|
|
||||||
expected_remove_calls.append(mock.call(connection_info))
|
|
||||||
mount_device = "vde"
|
|
||||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
|
||||||
|
|
||||||
# Test the scenario where multipath_id is returned
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, mount_device)
|
|
||||||
self.assertEqual(multipath_devname,
|
|
||||||
connection_info['data']['device_path'])
|
|
||||||
expected_commands = [('tee', '-a',
|
|
||||||
'/sys/bus/ccw/drivers/zfcp/0000:05:00.2/'
|
|
||||||
'0x1234567890123456/unit_add')]
|
|
||||||
self.assertEqual(expected_commands, self.executes)
|
|
||||||
# Test the scenario where multipath_id is not returned
|
|
||||||
connection_info["data"]["devices"] = devices["devices"]
|
|
||||||
del connection_info["data"]["multipath_id"]
|
|
||||||
libvirt_driver.disconnect_volume(connection_info, mount_device)
|
|
||||||
|
|
||||||
mock_remove_lun.assert_has_calls(expected_remove_calls)
|
|
||||||
|
|
||||||
# Should not work for anything other than string, unicode, and list
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location, 123)
|
|
||||||
self.assertRaises(exception.NovaException,
|
|
||||||
libvirt_driver.connect_volume,
|
|
||||||
connection_info, self.disk_info)
|
|
||||||
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas', lambda: [])
|
|
||||||
self.stubs.Set(libvirt_utils, 'get_fc_hbas_info', lambda: [])
|
|
||||||
self.assertRaises(exception.NovaException,
|
|
||||||
libvirt_driver.connect_volume,
|
|
||||||
connection_info, self.disk_info)
|
|
||||||
|
|
||||||
@mock.patch.object(platform, 'machine', return_value=arch.S390)
|
@mock.patch.object(platform, 'machine', return_value=arch.S390)
|
||||||
def test_libvirt_fibrechan_driver_s390(self, mock_machine):
|
def test_libvirt_fibrechan_driver_s390(self, mock_machine):
|
||||||
@ -1510,65 +797,6 @@ Setting up iSCSI targets: unused
|
|||||||
def test_libvirt_fibrechan_driver_s390x(self, mock_machine):
|
def test_libvirt_fibrechan_driver_s390x(self, mock_machine):
|
||||||
self._test_libvirt_fibrechan_driver_s390()
|
self._test_libvirt_fibrechan_driver_s390()
|
||||||
|
|
||||||
def test_libvirt_fibrechan_driver_get_config(self):
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
|
||||||
connection_info = self.fibrechan_connection(self.vol,
|
|
||||||
self.location, 123)
|
|
||||||
connection_info['data']['device_path'] = ("/sys/devices/pci0000:00"
|
|
||||||
"/0000:00:03.0/0000:05:00.3/host2/fc_host/host2")
|
|
||||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
|
||||||
tree = conf.format_dom()
|
|
||||||
self.assertEqual('block', tree.get('type'))
|
|
||||||
self.assertEqual(connection_info['data']['device_path'],
|
|
||||||
tree.find('./source').get('dev'))
|
|
||||||
|
|
||||||
def test_libvirt_fibrechan_getpci_num(self):
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
|
||||||
hba = {'device_path': "/sys/devices/pci0000:00/0000:00:03.0"
|
|
||||||
"/0000:05:00.3/host2/fc_host/host2"}
|
|
||||||
pci_num = libvirt_driver._get_pci_num(hba)
|
|
||||||
self.assertEqual("0000:05:00.3", pci_num)
|
|
||||||
|
|
||||||
hba = {'device_path': "/sys/devices/pci0000:00/0000:00:03.0"
|
|
||||||
"/0000:05:00.3/0000:06:00.6/host2/fc_host/host2"}
|
|
||||||
pci_num = libvirt_driver._get_pci_num(hba)
|
|
||||||
self.assertEqual("0000:06:00.6", pci_num)
|
|
||||||
|
|
||||||
def test_libvirt_fibrechan_get_device_file_path_s390(self):
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
|
||||||
pci_num = "2310"
|
|
||||||
wwn = '1234567890123456'
|
|
||||||
lun = 1
|
|
||||||
file_path = libvirt_driver._get_device_file_path_s390(pci_num,
|
|
||||||
wwn, lun)
|
|
||||||
expected_path = ("/dev/disk/by-path/ccw-2310-zfcp-"
|
|
||||||
"1234567890123456:1")
|
|
||||||
self.assertEqual(expected_path, file_path)
|
|
||||||
|
|
||||||
@mock.patch.object(libvirt_utils, 'get_fc_hbas_info',
|
|
||||||
fake_libvirt_utils.get_fc_hbas_info)
|
|
||||||
@mock.patch.object(libvirt_utils, 'perform_unit_remove_for_s390')
|
|
||||||
def test_libvirt_fibrechan_remove_lun_from_s390(self, mock_unit_remove):
|
|
||||||
libvirt_driver = volume.LibvirtFibreChannelVolumeDriver(self.fake_conn)
|
|
||||||
connection_info = {
|
|
||||||
'driver_volume_type': 'fibrechan',
|
|
||||||
'data': {
|
|
||||||
'target_wwn': ['50014380242b9751',
|
|
||||||
'50014380242b9752'],
|
|
||||||
'target_lun': 1,
|
|
||||||
}}
|
|
||||||
libvirt_driver._remove_lun_from_s390(connection_info)
|
|
||||||
|
|
||||||
expected_calls = []
|
|
||||||
for target_wwn in connection_info['data']['target_wwn']:
|
|
||||||
# NOTE(mriedem): The device_num value comes from ClassDevicePath
|
|
||||||
# in fake_libvirt_utils.fake_libvirt_utils.
|
|
||||||
expected_calls.append(mock.call('0000:05:00.2',
|
|
||||||
'0x' + target_wwn,
|
|
||||||
'0x0001000000000000'))
|
|
||||||
|
|
||||||
mock_unit_remove.assert_has_calls(expected_calls)
|
|
||||||
|
|
||||||
def test_libvirt_scality_driver(self):
|
def test_libvirt_scality_driver(self):
|
||||||
tempdir = self.useFixture(fixtures.TempDir()).path
|
tempdir = self.useFixture(fixtures.TempDir()).path
|
||||||
TEST_MOUNT = os.path.join(tempdir, 'fake_mount')
|
TEST_MOUNT = os.path.join(tempdir, 'fake_mount')
|
||||||
|
@ -84,6 +84,9 @@ class _FakeDriverBackendTestCase(object):
|
|||||||
fake_libvirt_utils
|
fake_libvirt_utils
|
||||||
import nova.tests.unit.virt.libvirt.fakelibvirt as fakelibvirt
|
import nova.tests.unit.virt.libvirt.fakelibvirt as fakelibvirt
|
||||||
|
|
||||||
|
import nova.tests.unit.virt.libvirt.fake_os_brick_connector as \
|
||||||
|
fake_os_brick_connector
|
||||||
|
|
||||||
sys.modules['libvirt'] = fakelibvirt
|
sys.modules['libvirt'] = fakelibvirt
|
||||||
import nova.virt.libvirt.driver
|
import nova.virt.libvirt.driver
|
||||||
import nova.virt.libvirt.firewall
|
import nova.virt.libvirt.firewall
|
||||||
@ -108,6 +111,10 @@ class _FakeDriverBackendTestCase(object):
|
|||||||
'nova.virt.libvirt.firewall.libvirt',
|
'nova.virt.libvirt.firewall.libvirt',
|
||||||
fakelibvirt))
|
fakelibvirt))
|
||||||
|
|
||||||
|
self.useFixture(fixtures.MonkeyPatch(
|
||||||
|
'nova.virt.libvirt.driver.connector',
|
||||||
|
fake_os_brick_connector))
|
||||||
|
|
||||||
fakelibvirt.disable_event_thread(self)
|
fakelibvirt.disable_event_thread(self)
|
||||||
|
|
||||||
self.flags(rescue_image_id="2",
|
self.flags(rescue_image_id="2",
|
||||||
|
@ -14,34 +14,27 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
"""
|
"""
|
||||||
Tests fot virt volumeutils.
|
Tests for virt volumeutils.
|
||||||
"""
|
"""
|
||||||
|
import mock
|
||||||
|
from os_brick.initiator import connector
|
||||||
|
|
||||||
from nova import exception
|
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova import utils
|
|
||||||
from nova.virt import volumeutils
|
from nova.virt import volumeutils
|
||||||
|
|
||||||
|
|
||||||
class VolumeUtilsTestCase(test.NoDBTestCase):
|
class VolumeUtilsTestCase(test.NoDBTestCase):
|
||||||
def test_get_iscsi_initiator(self):
|
|
||||||
self.mox.StubOutWithMock(utils, 'execute')
|
@mock.patch.object(connector.ISCSIConnector, 'get_initiator',
|
||||||
|
return_value='fake.initiator.iqn')
|
||||||
|
def test_get_iscsi_initiator(self, fake_initiator):
|
||||||
initiator = 'fake.initiator.iqn'
|
initiator = 'fake.initiator.iqn'
|
||||||
rval = ("junk\nInitiatorName=%s\njunk\n" % initiator, None)
|
|
||||||
utils.execute('cat', '/etc/iscsi/initiatorname.iscsi',
|
|
||||||
run_as_root=True).AndReturn(rval)
|
|
||||||
# Start test
|
# Start test
|
||||||
self.mox.ReplayAll()
|
|
||||||
result = volumeutils.get_iscsi_initiator()
|
result = volumeutils.get_iscsi_initiator()
|
||||||
self.assertEqual(initiator, result)
|
self.assertEqual(initiator, result)
|
||||||
|
|
||||||
def test_get_missing_iscsi_initiator(self):
|
@mock.patch.object(connector.ISCSIConnector, 'get_initiator',
|
||||||
self.mox.StubOutWithMock(utils, 'execute')
|
return_value=None)
|
||||||
file_path = '/etc/iscsi/initiatorname.iscsi'
|
def test_get_missing_iscsi_initiator(self, fake_initiator):
|
||||||
utils.execute('cat', file_path, run_as_root=True).AndRaise(
|
|
||||||
exception.FileNotFound(file_path=file_path)
|
|
||||||
)
|
|
||||||
# Start test
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
result = volumeutils.get_iscsi_initiator()
|
result = volumeutils.get_iscsi_initiator()
|
||||||
self.assertEqual('', result)
|
self.assertIsNone(result)
|
||||||
|
@ -43,6 +43,7 @@ import eventlet
|
|||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
from eventlet import tpool
|
from eventlet import tpool
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
from os_brick.initiator import connector
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -250,6 +251,8 @@ CONF.import_opt('proxyclient_address', 'nova.console.serial',
|
|||||||
CONF.import_opt('hw_disk_discard', 'nova.virt.libvirt.imagebackend',
|
CONF.import_opt('hw_disk_discard', 'nova.virt.libvirt.imagebackend',
|
||||||
group='libvirt')
|
group='libvirt')
|
||||||
CONF.import_group('workarounds', 'nova.utils')
|
CONF.import_group('workarounds', 'nova.utils')
|
||||||
|
CONF.import_opt('iscsi_use_multipath', 'nova.virt.libvirt.volume',
|
||||||
|
group='libvirt')
|
||||||
|
|
||||||
DEFAULT_FIREWALL_DRIVER = "%s.%s" % (
|
DEFAULT_FIREWALL_DRIVER = "%s.%s" % (
|
||||||
libvirt_firewall.__name__,
|
libvirt_firewall.__name__,
|
||||||
@ -959,37 +962,12 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def get_volume_connector(self, instance):
|
def get_volume_connector(self, instance):
|
||||||
if self._initiator is None:
|
root_helper = utils._get_root_helper()
|
||||||
self._initiator = libvirt_utils.get_iscsi_initiator()
|
return connector.get_connector_properties(
|
||||||
if not self._initiator:
|
root_helper, CONF.my_block_storage_ip,
|
||||||
LOG.debug('Could not determine iscsi initiator name',
|
CONF.libvirt.iscsi_use_multipath,
|
||||||
instance=instance)
|
enforce_multipath=True,
|
||||||
|
host=CONF.host)
|
||||||
if self._fc_wwnns is None:
|
|
||||||
self._fc_wwnns = libvirt_utils.get_fc_wwnns()
|
|
||||||
if not self._fc_wwnns or len(self._fc_wwnns) == 0:
|
|
||||||
LOG.debug('Could not determine fibre channel '
|
|
||||||
'world wide node names',
|
|
||||||
instance=instance)
|
|
||||||
|
|
||||||
if self._fc_wwpns is None:
|
|
||||||
self._fc_wwpns = libvirt_utils.get_fc_wwpns()
|
|
||||||
if not self._fc_wwpns or len(self._fc_wwpns) == 0:
|
|
||||||
LOG.debug('Could not determine fibre channel '
|
|
||||||
'world wide port names',
|
|
||||||
instance=instance)
|
|
||||||
|
|
||||||
connector = {'ip': CONF.my_block_storage_ip,
|
|
||||||
'host': CONF.host}
|
|
||||||
|
|
||||||
if self._initiator:
|
|
||||||
connector['initiator'] = self._initiator
|
|
||||||
|
|
||||||
if self._fc_wwnns and self._fc_wwpns:
|
|
||||||
connector["wwnns"] = self._fc_wwnns
|
|
||||||
connector["wwpns"] = self._fc_wwpns
|
|
||||||
|
|
||||||
return connector
|
|
||||||
|
|
||||||
def _cleanup_resize(self, instance, network_info):
|
def _cleanup_resize(self, instance, network_info):
|
||||||
# NOTE(wangpan): we get the pre-grizzly instance path firstly,
|
# NOTE(wangpan): we get the pre-grizzly instance path firstly,
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@ -31,8 +30,6 @@ from oslo_log import log as logging
|
|||||||
from nova.compute import arch
|
from nova.compute import arch
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova.i18n import _LI
|
from nova.i18n import _LI
|
||||||
from nova.i18n import _LW
|
|
||||||
from nova.storage import linuxscsi
|
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova.virt import images
|
from nova.virt import images
|
||||||
from nova.virt.libvirt import config as vconfig
|
from nova.virt.libvirt import config as vconfig
|
||||||
@ -59,110 +56,6 @@ def get_iscsi_initiator():
|
|||||||
return volumeutils.get_iscsi_initiator()
|
return volumeutils.get_iscsi_initiator()
|
||||||
|
|
||||||
|
|
||||||
def get_fc_hbas():
|
|
||||||
"""Get the Fibre Channel HBA information."""
|
|
||||||
out = None
|
|
||||||
try:
|
|
||||||
out, err = execute('systool', '-c', 'fc_host', '-v',
|
|
||||||
run_as_root=True)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
# This handles the case where rootwrap is used
|
|
||||||
# and systool is not installed
|
|
||||||
# 96 = nova.cmd.rootwrap.RC_NOEXECFOUND:
|
|
||||||
if exc.exit_code == 96:
|
|
||||||
LOG.warn(_LW("systool is not installed"))
|
|
||||||
return []
|
|
||||||
except OSError as exc:
|
|
||||||
# This handles the case where rootwrap is NOT used
|
|
||||||
# and systool is not installed
|
|
||||||
if exc.errno == errno.ENOENT:
|
|
||||||
LOG.warn(_LW("systool is not installed"))
|
|
||||||
return []
|
|
||||||
|
|
||||||
if out is None:
|
|
||||||
raise RuntimeError(_("Cannot find any Fibre Channel HBAs"))
|
|
||||||
|
|
||||||
lines = out.split('\n')
|
|
||||||
# ignore the first 2 lines
|
|
||||||
lines = lines[2:]
|
|
||||||
hbas = []
|
|
||||||
hba = {}
|
|
||||||
lastline = None
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
# 2 newlines denotes a new hba port
|
|
||||||
if line == '' and lastline == '':
|
|
||||||
if len(hba) > 0:
|
|
||||||
hbas.append(hba)
|
|
||||||
hba = {}
|
|
||||||
else:
|
|
||||||
val = line.split('=')
|
|
||||||
if len(val) == 2:
|
|
||||||
key = val[0].strip().replace(" ", "")
|
|
||||||
value = val[1].strip()
|
|
||||||
hba[key] = value.replace('"', '')
|
|
||||||
lastline = line
|
|
||||||
|
|
||||||
return hbas
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_hbas_info():
|
|
||||||
"""Get Fibre Channel WWNs and device paths from the system, if any."""
|
|
||||||
# Note modern linux kernels contain the FC HBA's in /sys
|
|
||||||
# and are obtainable via the systool app
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
hbas_info = []
|
|
||||||
for hba in hbas:
|
|
||||||
# Systems implementing the S390 architecture support virtual HBAs
|
|
||||||
# may be online, or offline. This function should only return
|
|
||||||
# virtual HBAs in the online state
|
|
||||||
if (platform.machine() in (arch.S390, arch.S390X) and
|
|
||||||
hba['port_state'].lower() != 'online'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
wwpn = hba['port_name'].replace('0x', '')
|
|
||||||
wwnn = hba['node_name'].replace('0x', '')
|
|
||||||
device_path = hba['ClassDevicepath']
|
|
||||||
device = hba['ClassDevice']
|
|
||||||
hbas_info.append({'port_name': wwpn,
|
|
||||||
'node_name': wwnn,
|
|
||||||
'host_device': device,
|
|
||||||
'device_path': device_path})
|
|
||||||
return hbas_info
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_wwpns():
|
|
||||||
"""Get Fibre Channel WWPNs from the system, if any."""
|
|
||||||
# Note modern linux kernels contain the FC HBA's in /sys
|
|
||||||
# and are obtainable via the systool app
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
|
|
||||||
wwpns = []
|
|
||||||
if hbas:
|
|
||||||
for hba in hbas:
|
|
||||||
if hba['port_state'] == 'Online':
|
|
||||||
wwpn = hba['port_name'].replace('0x', '')
|
|
||||||
wwpns.append(wwpn)
|
|
||||||
|
|
||||||
return wwpns
|
|
||||||
|
|
||||||
|
|
||||||
def get_fc_wwnns():
|
|
||||||
"""Get Fibre Channel WWNNs from the system, if any."""
|
|
||||||
# Note modern linux kernels contain the FC HBA's in /sys
|
|
||||||
# and are obtainable via the systool app
|
|
||||||
hbas = get_fc_hbas()
|
|
||||||
|
|
||||||
wwnns = []
|
|
||||||
if hbas:
|
|
||||||
for hba in hbas:
|
|
||||||
if hba['port_state'] == 'Online':
|
|
||||||
wwnn = hba['node_name'].replace('0x', '')
|
|
||||||
wwnns.append(wwnn)
|
|
||||||
|
|
||||||
return wwnns
|
|
||||||
|
|
||||||
|
|
||||||
def create_image(disk_format, path, size):
|
def create_image(disk_format, path, size):
|
||||||
"""Create a disk image
|
"""Create a disk image
|
||||||
|
|
||||||
@ -588,51 +481,3 @@ def is_mounted(mount_path, source=None):
|
|||||||
|
|
||||||
def is_valid_hostname(hostname):
|
def is_valid_hostname(hostname):
|
||||||
return re.match(r"^[\w\-\.:]+$", hostname)
|
return re.match(r"^[\w\-\.:]+$", hostname)
|
||||||
|
|
||||||
|
|
||||||
def perform_unit_add_for_s390(device_number, target_wwn, lun):
|
|
||||||
"""Write the LUN to the port's unit_add attribute."""
|
|
||||||
# NOTE If LUN scanning is turned off on systems following the s390,
|
|
||||||
# or s390x architecture LUNs need to be added to the configuration
|
|
||||||
# using the unit_add call. The unit_add call may fail if a target_wwn
|
|
||||||
# is not accessible for the HBA specified by the device_number.
|
|
||||||
# This can be an expected situation in multipath configurations.
|
|
||||||
# This method will thus only log a warning message in case the
|
|
||||||
# unit_add call fails.
|
|
||||||
LOG.debug("perform unit_add for s390: device_number=(%(device_num)s) "
|
|
||||||
"target_wwn=(%(target_wwn)s) target_lun=(%(target_lun)s)",
|
|
||||||
{'device_num': device_number,
|
|
||||||
'target_wwn': target_wwn,
|
|
||||||
'target_lun': lun})
|
|
||||||
zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_add" %
|
|
||||||
(device_number, target_wwn))
|
|
||||||
try:
|
|
||||||
linuxscsi.echo_scsi_command(zfcp_device_command, lun)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
LOG.warn(_LW("unit_add call failed; exit code (%(code)s), "
|
|
||||||
"stderr (%(stderr)s)"),
|
|
||||||
{'code': exc.exit_code, 'stderr': exc.stderr})
|
|
||||||
|
|
||||||
|
|
||||||
def perform_unit_remove_for_s390(device_number, target_wwn, lun):
|
|
||||||
"""Write the LUN to the port's unit_remove attribute."""
|
|
||||||
# If LUN scanning is turned off on systems following the s390, or s390x
|
|
||||||
# architecture LUNs need to be removed from the configuration using the
|
|
||||||
# unit_remove call. The unit_remove call may fail if the LUN is not
|
|
||||||
# part of the configuration anymore. This may be an expected situation.
|
|
||||||
# For exmple, if LUN scanning is turned on.
|
|
||||||
# This method will thus only log a warning message in case the
|
|
||||||
# unit_remove call fails.
|
|
||||||
LOG.debug("perform unit_remove for s390: device_number=(%(device_num)s) "
|
|
||||||
"target_wwn=(%(target_wwn)s) target_lun=(%(target_lun)s)",
|
|
||||||
{'device_num': device_number,
|
|
||||||
'target_wwn': target_wwn,
|
|
||||||
'target_lun': lun})
|
|
||||||
zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_remove" %
|
|
||||||
(device_number, target_wwn))
|
|
||||||
try:
|
|
||||||
linuxscsi.echo_scsi_command(zfcp_device_command, lun)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
LOG.warn(_LW("unit_remove call failed; exit code (%(code)s), "
|
|
||||||
"stderr (%(stderr)s)"),
|
|
||||||
{'code': exc.exit_code, 'stderr': exc.stderr})
|
|
||||||
|
@ -17,29 +17,23 @@
|
|||||||
"""Volume drivers for libvirt."""
|
"""Volume drivers for libvirt."""
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import glob
|
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
|
|
||||||
|
from os_brick.initiator import connector
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_service import loopingcall
|
|
||||||
from oslo_utils import strutils
|
|
||||||
import six
|
import six
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
import six.moves.urllib.parse as urlparse
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
from nova.compute import arch
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova.i18n import _LE
|
from nova.i18n import _LE
|
||||||
from nova.i18n import _LI
|
from nova.i18n import _LI
|
||||||
from nova.i18n import _LW
|
from nova.i18n import _LW
|
||||||
from nova import paths
|
from nova import paths
|
||||||
from nova.storage import linuxscsi
|
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova.virt.libvirt import config as vconfig
|
from nova.virt.libvirt import config as vconfig
|
||||||
from nova.virt.libvirt import quobyte
|
from nova.virt.libvirt import quobyte
|
||||||
@ -302,89 +296,26 @@ class LibvirtNetVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
|
|
||||||
class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
||||||
"""Driver to attach Network volumes to libvirt."""
|
"""Driver to attach Network volumes to libvirt."""
|
||||||
supported_transports = ['be2iscsi', 'bnx2i', 'cxgb3i',
|
|
||||||
'cxgb4i', 'qla4xxx', 'ocs']
|
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
super(LibvirtISCSIVolumeDriver, self).__init__(connection,
|
super(LibvirtISCSIVolumeDriver, self).__init__(connection,
|
||||||
is_block_dev=True)
|
is_block_dev=True)
|
||||||
self.num_scan_tries = CONF.libvirt.num_iscsi_scan_tries
|
|
||||||
self.use_multipath = CONF.libvirt.iscsi_use_multipath
|
# Call the factory here so we can support
|
||||||
if CONF.libvirt.iscsi_iface:
|
# more than x86 architectures.
|
||||||
self.transport = CONF.libvirt.iscsi_iface
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
else:
|
'ISCSI', utils._get_root_helper(),
|
||||||
self.transport = 'default'
|
use_multipath=CONF.libvirt.iscsi_use_multipath,
|
||||||
|
device_scan_attempts=CONF.libvirt.num_iscsi_scan_tries,
|
||||||
|
transport=self._get_transport())
|
||||||
|
|
||||||
def _get_transport(self):
|
def _get_transport(self):
|
||||||
if self._validate_transport(self.transport):
|
if CONF.libvirt.iscsi_iface:
|
||||||
return self.transport
|
transport = CONF.libvirt.iscsi_iface
|
||||||
else:
|
else:
|
||||||
return 'default'
|
transport = 'default'
|
||||||
|
|
||||||
def _validate_transport(self, transport_iface):
|
return transport
|
||||||
"""Check that given iscsi_iface uses only supported transports
|
|
||||||
|
|
||||||
Accepted transport names for provided iface param are
|
|
||||||
be2iscsi, bnx2i, cxgb3i, cxgb4i, qla4xxx and ocs. iSER uses it's
|
|
||||||
own separate driver. Note the difference between transport and
|
|
||||||
iface; unlike iscsi_tcp/iser, this is not one and the same for
|
|
||||||
offloaded transports, where the default format is
|
|
||||||
transport_name.hwaddress
|
|
||||||
"""
|
|
||||||
# We can support iser here as well, but currently reject it as the
|
|
||||||
# separate iser driver has not yet been deprecated.
|
|
||||||
if transport_iface == 'default':
|
|
||||||
return True
|
|
||||||
# Will return (6) if iscsi_iface file was not found, or (2) if iscsid
|
|
||||||
# could not be contacted
|
|
||||||
out = self._run_iscsiadm_bare(['-m',
|
|
||||||
'iface',
|
|
||||||
'-I',
|
|
||||||
transport_iface],
|
|
||||||
check_exit_code=[0, 2, 6])[0] or ""
|
|
||||||
LOG.debug("iscsiadm %(iface)s configuration: stdout=%(out)s",
|
|
||||||
{'iface': transport_iface, 'out': out})
|
|
||||||
for data in [line.split() for line in out.splitlines()]:
|
|
||||||
if data[0] == 'iface.transport_name':
|
|
||||||
if data[2] in self.supported_transports:
|
|
||||||
return True
|
|
||||||
|
|
||||||
LOG.warn(_LW("No useable transport found for iscsi iface %s. "
|
|
||||||
"Falling back to default transport"),
|
|
||||||
transport_iface)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs):
|
|
||||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
|
||||||
(out, err) = utils.execute('iscsiadm', '-m', 'node', '-T',
|
|
||||||
iscsi_properties['target_iqn'],
|
|
||||||
'-p', iscsi_properties['target_portal'],
|
|
||||||
*iscsi_command, run_as_root=True,
|
|
||||||
check_exit_code=check_exit_code)
|
|
||||||
msg = ('iscsiadm %(command)s: stdout=%(out)s stderr=%(err)s' %
|
|
||||||
{'command': iscsi_command, 'out': out, 'err': err})
|
|
||||||
# NOTE(bpokorny): iscsi_command can contain passwords so we need to
|
|
||||||
# sanitize the password in the message.
|
|
||||||
LOG.debug(strutils.mask_password(msg))
|
|
||||||
return (out, err)
|
|
||||||
|
|
||||||
def _iscsiadm_update(self, iscsi_properties, property_key, property_value,
|
|
||||||
**kwargs):
|
|
||||||
iscsi_command = ('--op', 'update', '-n', property_key,
|
|
||||||
'-v', property_value)
|
|
||||||
return self._run_iscsiadm(iscsi_properties, iscsi_command, **kwargs)
|
|
||||||
|
|
||||||
def _get_target_portals_from_iscsiadm_output(self, output):
|
|
||||||
# return both portals and iqns
|
|
||||||
#
|
|
||||||
# as we are parsing a command line utility, allow for the
|
|
||||||
# possibility that additional debug data is spewed in the
|
|
||||||
# stream, and only grab actual ip / iqn lines.
|
|
||||||
targets = []
|
|
||||||
for data in [line.split() for line in output.splitlines()]:
|
|
||||||
if len(data) == 2 and data[1].startswith('iqn.'):
|
|
||||||
targets.append(data)
|
|
||||||
return targets
|
|
||||||
|
|
||||||
def get_config(self, connection_info, disk_info):
|
def get_config(self, connection_info, disk_info):
|
||||||
"""Returns xml for libvirt."""
|
"""Returns xml for libvirt."""
|
||||||
@ -394,486 +325,43 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
conf.source_path = connection_info['data']['device_path']
|
conf.source_path = connection_info['data']['device_path']
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
@utils.synchronized('connect_volume')
|
|
||||||
def connect_volume(self, connection_info, disk_info):
|
def connect_volume(self, connection_info, disk_info):
|
||||||
"""Attach the volume to instance_name."""
|
"""Attach the volume to instance_name."""
|
||||||
iscsi_properties = connection_info['data']
|
|
||||||
|
|
||||||
# multipath installed, discovering other targets if available
|
LOG.debug("Calling os-brick to attach iSCSI Volume")
|
||||||
# multipath should be configured on the nova-compute node,
|
device_info = self.connector.connect_volume(connection_info['data'])
|
||||||
# in order to fit storage vendor
|
LOG.debug("Attached iSCSI volume %s", device_info)
|
||||||
if self.use_multipath:
|
|
||||||
out = self._run_iscsiadm_discover(iscsi_properties)
|
|
||||||
|
|
||||||
# There are two types of iSCSI multipath devices. One which shares
|
connection_info['data']['device_path'] = device_info['path']
|
||||||
# the same iqn between multiple portals, and the other which use
|
|
||||||
# different iqns on different portals. Try to identify the type by
|
|
||||||
# checking the iscsiadm output if the iqn is used by multiple
|
|
||||||
# portals. If it is, it's the former, so use the supplied iqn.
|
|
||||||
# Otherwise, it's the latter, so try the ip,iqn combinations to
|
|
||||||
# find the targets which constitutes the multipath device.
|
|
||||||
ips_iqns = self._get_target_portals_from_iscsiadm_output(out)
|
|
||||||
same_portal = False
|
|
||||||
all_portals = set()
|
|
||||||
match_portals = set()
|
|
||||||
for ip, iqn in ips_iqns:
|
|
||||||
all_portals.add(ip)
|
|
||||||
if iqn == iscsi_properties['target_iqn']:
|
|
||||||
match_portals.add(ip)
|
|
||||||
if len(all_portals) == len(match_portals):
|
|
||||||
same_portal = True
|
|
||||||
|
|
||||||
for ip, iqn in ips_iqns:
|
|
||||||
props = iscsi_properties.copy()
|
|
||||||
props['target_portal'] = ip.split(",")[0]
|
|
||||||
if not same_portal:
|
|
||||||
props['target_iqn'] = iqn
|
|
||||||
self._connect_to_iscsi_portal(props)
|
|
||||||
|
|
||||||
self._rescan_iscsi()
|
|
||||||
else:
|
|
||||||
self._connect_to_iscsi_portal(iscsi_properties)
|
|
||||||
|
|
||||||
# Detect new/resized LUNs for existing sessions
|
|
||||||
self._run_iscsiadm(iscsi_properties, ("--rescan",))
|
|
||||||
|
|
||||||
host_device = self._get_host_device(iscsi_properties)
|
|
||||||
|
|
||||||
# The /dev/disk/by-path/... node is not always present immediately
|
|
||||||
# TODO(justinsb): This retry-with-delay is a pattern, move to utils?
|
|
||||||
tries = 0
|
|
||||||
disk_dev = disk_info['dev']
|
|
||||||
|
|
||||||
# Check host_device only when transport is used, since otherwise it is
|
|
||||||
# directly derived from properties. Only needed for unit tests
|
|
||||||
while ((self._get_transport() != "default" and not host_device)
|
|
||||||
or not os.path.exists(host_device)):
|
|
||||||
if tries >= self.num_scan_tries:
|
|
||||||
raise exception.NovaException(_("iSCSI device not found at %s")
|
|
||||||
% (host_device))
|
|
||||||
|
|
||||||
LOG.warn(_LW("ISCSI volume not yet found at: %(disk_dev)s. "
|
|
||||||
"Will rescan & retry. Try number: %(tries)s"),
|
|
||||||
{'disk_dev': disk_dev, 'tries': tries})
|
|
||||||
|
|
||||||
# The rescan isn't documented as being necessary(?), but it helps
|
|
||||||
self._run_iscsiadm(iscsi_properties, ("--rescan",))
|
|
||||||
|
|
||||||
# For offloaded open-iscsi transports, host_device cannot be
|
|
||||||
# guessed unlike iscsi_tcp where it can be obtained from
|
|
||||||
# properties, so try and get it again.
|
|
||||||
if not host_device and self._get_transport() != "default":
|
|
||||||
host_device = self._get_host_device(iscsi_properties)
|
|
||||||
|
|
||||||
tries = tries + 1
|
|
||||||
if not host_device or not os.path.exists(host_device):
|
|
||||||
time.sleep(tries ** 2)
|
|
||||||
|
|
||||||
if tries != 0:
|
|
||||||
LOG.debug("Found iSCSI node %(disk_dev)s "
|
|
||||||
"(after %(tries)s rescans)",
|
|
||||||
{'disk_dev': disk_dev,
|
|
||||||
'tries': tries})
|
|
||||||
|
|
||||||
if self.use_multipath:
|
|
||||||
# we use the multipath device instead of the single path device
|
|
||||||
self._rescan_multipath()
|
|
||||||
|
|
||||||
multipath_device = self._get_multipath_device_name(host_device)
|
|
||||||
|
|
||||||
if multipath_device is not None:
|
|
||||||
host_device = multipath_device
|
|
||||||
connection_info['data']['multipath_id'] = \
|
|
||||||
multipath_device.split('/')[-1]
|
|
||||||
|
|
||||||
connection_info['data']['device_path'] = host_device
|
|
||||||
|
|
||||||
def _run_iscsiadm_discover(self, iscsi_properties):
|
|
||||||
def run_iscsiadm_update_discoverydb():
|
|
||||||
return utils.execute(
|
|
||||||
'iscsiadm',
|
|
||||||
'-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', iscsi_properties['target_portal'],
|
|
||||||
'--op', 'update',
|
|
||||||
'-n', "discovery.sendtargets.auth.authmethod",
|
|
||||||
'-v', iscsi_properties['discovery_auth_method'],
|
|
||||||
'-n', "discovery.sendtargets.auth.username",
|
|
||||||
'-v', iscsi_properties['discovery_auth_username'],
|
|
||||||
'-n', "discovery.sendtargets.auth.password",
|
|
||||||
'-v', iscsi_properties['discovery_auth_password'],
|
|
||||||
run_as_root=True)
|
|
||||||
|
|
||||||
out = None
|
|
||||||
if iscsi_properties.get('discovery_auth_method'):
|
|
||||||
try:
|
|
||||||
run_iscsiadm_update_discoverydb()
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
# iscsiadm returns 6 for "db record not found"
|
|
||||||
if exc.exit_code == 6:
|
|
||||||
(out, err) = utils.execute(
|
|
||||||
'iscsiadm',
|
|
||||||
'-m', 'discoverydb',
|
|
||||||
'-t', 'sendtargets',
|
|
||||||
'-p', iscsi_properties['target_portal'],
|
|
||||||
'--op', 'new',
|
|
||||||
run_as_root=True)
|
|
||||||
run_iscsiadm_update_discoverydb()
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
out = self._run_iscsiadm_bare(
|
|
||||||
['-m',
|
|
||||||
'discoverydb',
|
|
||||||
'-t',
|
|
||||||
'sendtargets',
|
|
||||||
'-p',
|
|
||||||
iscsi_properties['target_portal'],
|
|
||||||
'--discover'],
|
|
||||||
check_exit_code=[0, 255])[0] or ""
|
|
||||||
else:
|
|
||||||
out = self._run_iscsiadm_bare(
|
|
||||||
['-m',
|
|
||||||
'discovery',
|
|
||||||
'-t',
|
|
||||||
'sendtargets',
|
|
||||||
'-p',
|
|
||||||
iscsi_properties['target_portal']],
|
|
||||||
check_exit_code=[0, 255])[0] or ""
|
|
||||||
return out
|
|
||||||
|
|
||||||
@utils.synchronized('connect_volume')
|
|
||||||
def disconnect_volume(self, connection_info, disk_dev):
|
def disconnect_volume(self, connection_info, disk_dev):
|
||||||
"""Detach the volume from instance_name."""
|
"""Detach the volume from instance_name."""
|
||||||
iscsi_properties = connection_info['data']
|
|
||||||
host_device = self._get_host_device(iscsi_properties)
|
LOG.debug("calling os-brick to detach iSCSI Volume")
|
||||||
multipath_device = None
|
self.connector.disconnect_volume(connection_info['data'], None)
|
||||||
if self.use_multipath:
|
LOG.debug("Disconnected iSCSI Volume %s", disk_dev)
|
||||||
if 'multipath_id' in iscsi_properties:
|
|
||||||
multipath_device = ('/dev/mapper/%s' %
|
|
||||||
iscsi_properties['multipath_id'])
|
|
||||||
else:
|
|
||||||
multipath_device = self._get_multipath_device_name(host_device)
|
|
||||||
|
|
||||||
super(LibvirtISCSIVolumeDriver,
|
super(LibvirtISCSIVolumeDriver,
|
||||||
self).disconnect_volume(connection_info, disk_dev)
|
self).disconnect_volume(connection_info, disk_dev)
|
||||||
|
|
||||||
if self.use_multipath and multipath_device:
|
|
||||||
return self._disconnect_volume_multipath_iscsi(iscsi_properties,
|
|
||||||
multipath_device)
|
|
||||||
|
|
||||||
# NOTE(vish): Only disconnect from the target if no luns from the
|
|
||||||
# target are in use.
|
|
||||||
device_byname = ("ip-%s-iscsi-%s-lun-" %
|
|
||||||
(iscsi_properties['target_portal'],
|
|
||||||
iscsi_properties['target_iqn']))
|
|
||||||
devices = self.connection._get_all_block_devices()
|
|
||||||
devices = [dev for dev in devices if (device_byname in dev
|
|
||||||
and
|
|
||||||
dev.startswith(
|
|
||||||
'/dev/disk/by-path/'))]
|
|
||||||
if not devices:
|
|
||||||
self._disconnect_from_iscsi_portal(iscsi_properties)
|
|
||||||
elif host_device not in devices:
|
|
||||||
# Delete device if LUN is not in use by another instance
|
|
||||||
self._delete_device(host_device)
|
|
||||||
|
|
||||||
def _delete_device(self, device_path):
|
|
||||||
device_name = os.path.basename(os.path.realpath(device_path))
|
|
||||||
delete_control = '/sys/block/' + device_name + '/device/delete'
|
|
||||||
if os.path.exists(delete_control):
|
|
||||||
# Copy '1' from stdin to the device delete control file
|
|
||||||
utils.execute('cp', '/dev/stdin', delete_control,
|
|
||||||
process_input='1', run_as_root=True)
|
|
||||||
else:
|
|
||||||
LOG.warn(_LW("Unable to delete volume device %s"), device_name)
|
|
||||||
|
|
||||||
def _remove_multipath_device_descriptor(self, disk_descriptor):
|
|
||||||
disk_descriptor = disk_descriptor.replace('/dev/mapper/', '')
|
|
||||||
try:
|
|
||||||
self._run_multipath(['-f', disk_descriptor],
|
|
||||||
check_exit_code=[0, 1])
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
# Because not all cinder drivers need to remove the dev mapper,
|
|
||||||
# here just logs a warning to avoid affecting those drivers in
|
|
||||||
# exceptional cases.
|
|
||||||
LOG.warn(_LW('Failed to remove multipath device descriptor '
|
|
||||||
'%(dev_mapper)s. Exception message: %(msg)s')
|
|
||||||
% {'dev_mapper': disk_descriptor,
|
|
||||||
'msg': exc.message})
|
|
||||||
|
|
||||||
def _disconnect_volume_multipath_iscsi(self, iscsi_properties,
|
|
||||||
multipath_device):
|
|
||||||
self._rescan_multipath()
|
|
||||||
block_devices = self.connection._get_all_block_devices()
|
|
||||||
devices = []
|
|
||||||
for dev in block_devices:
|
|
||||||
if "/mapper/" in dev:
|
|
||||||
devices.append(dev)
|
|
||||||
else:
|
|
||||||
mpdev = self._get_multipath_device_name(dev)
|
|
||||||
if mpdev:
|
|
||||||
devices.append(mpdev)
|
|
||||||
|
|
||||||
# Do a discovery to find all targets.
|
|
||||||
# Targets for multiple paths for the same multipath device
|
|
||||||
# may not be the same.
|
|
||||||
out = self._run_iscsiadm_discover(iscsi_properties)
|
|
||||||
|
|
||||||
# Extract targets for the current multipath device.
|
|
||||||
ips_iqns = []
|
|
||||||
entries = self._get_iscsi_devices()
|
|
||||||
for ip, iqn in self._get_target_portals_from_iscsiadm_output(out):
|
|
||||||
ip_iqn = "%s-iscsi-%s" % (ip.split(",")[0], iqn)
|
|
||||||
for entry in entries:
|
|
||||||
entry_ip_iqn = entry.split("-lun-")[0]
|
|
||||||
if entry_ip_iqn[:3] == "ip-":
|
|
||||||
entry_ip_iqn = entry_ip_iqn[3:]
|
|
||||||
elif entry_ip_iqn[:4] == "pci-":
|
|
||||||
# Look at an offset of len('pci-0000:00:00.0')
|
|
||||||
offset = entry_ip_iqn.find("ip-", 16, 21)
|
|
||||||
entry_ip_iqn = entry_ip_iqn[(offset + 3):]
|
|
||||||
if (ip_iqn != entry_ip_iqn):
|
|
||||||
continue
|
|
||||||
entry_real_path = os.path.realpath("/dev/disk/by-path/%s" %
|
|
||||||
entry)
|
|
||||||
entry_mpdev = self._get_multipath_device_name(entry_real_path)
|
|
||||||
if entry_mpdev == multipath_device:
|
|
||||||
ips_iqns.append([ip, iqn])
|
|
||||||
break
|
|
||||||
|
|
||||||
if not devices:
|
|
||||||
# disconnect if no other multipath devices
|
|
||||||
self._disconnect_mpath(iscsi_properties, ips_iqns)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get a target for all other multipath devices
|
|
||||||
other_iqns = [self._get_multipath_iqn(device)
|
|
||||||
for device in devices]
|
|
||||||
# Get all the targets for the current multipath device
|
|
||||||
current_iqns = [iqn for ip, iqn in ips_iqns]
|
|
||||||
|
|
||||||
in_use = False
|
|
||||||
for current in current_iqns:
|
|
||||||
if current in other_iqns:
|
|
||||||
in_use = True
|
|
||||||
break
|
|
||||||
|
|
||||||
# If no other multipath device attached has the same iqn
|
|
||||||
# as the current device
|
|
||||||
if not in_use:
|
|
||||||
# disconnect if no other multipath devices with same iqn
|
|
||||||
self._disconnect_mpath(iscsi_properties, ips_iqns)
|
|
||||||
return
|
|
||||||
elif multipath_device not in devices:
|
|
||||||
# delete the devices associated w/ the unused multipath
|
|
||||||
self._delete_mpath(iscsi_properties, multipath_device, ips_iqns)
|
|
||||||
|
|
||||||
# else do not disconnect iscsi portals,
|
|
||||||
# as they are used for other luns,
|
|
||||||
# just remove multipath mapping device descriptor
|
|
||||||
self._remove_multipath_device_descriptor(multipath_device)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _connect_to_iscsi_portal(self, iscsi_properties):
|
|
||||||
# NOTE(vish): If we are on the same host as nova volume, the
|
|
||||||
# discovery makes the target so we don't need to
|
|
||||||
# run --op new. Therefore, we check to see if the
|
|
||||||
# target exists, and if we get 255 (Not Found), then
|
|
||||||
# we run --op new. This will also happen if another
|
|
||||||
# volume is using the same target.
|
|
||||||
try:
|
|
||||||
self._run_iscsiadm(iscsi_properties, ())
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
# iscsiadm returns 21 for "No records found" after version 2.0-871
|
|
||||||
if exc.exit_code in [21, 255]:
|
|
||||||
self._reconnect(iscsi_properties)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if iscsi_properties.get('auth_method'):
|
|
||||||
self._iscsiadm_update(iscsi_properties,
|
|
||||||
"node.session.auth.authmethod",
|
|
||||||
iscsi_properties['auth_method'])
|
|
||||||
self._iscsiadm_update(iscsi_properties,
|
|
||||||
"node.session.auth.username",
|
|
||||||
iscsi_properties['auth_username'])
|
|
||||||
self._iscsiadm_update(iscsi_properties,
|
|
||||||
"node.session.auth.password",
|
|
||||||
iscsi_properties['auth_password'])
|
|
||||||
|
|
||||||
# duplicate logins crash iscsiadm after load,
|
|
||||||
# so we scan active sessions to see if the node is logged in.
|
|
||||||
out = self._run_iscsiadm_bare(["-m", "session"],
|
|
||||||
run_as_root=True,
|
|
||||||
check_exit_code=[0, 1, 21])[0] or ""
|
|
||||||
|
|
||||||
portals = [{'portal': p.split(" ")[2], 'iqn': p.split(" ")[3]}
|
|
||||||
for p in out.splitlines() if p.startswith("tcp:")]
|
|
||||||
|
|
||||||
stripped_portal = iscsi_properties['target_portal'].split(",")[0]
|
|
||||||
if len(portals) == 0 or len([s for s in portals
|
|
||||||
if stripped_portal ==
|
|
||||||
s['portal'].split(",")[0]
|
|
||||||
and
|
|
||||||
s['iqn'] ==
|
|
||||||
iscsi_properties['target_iqn']]
|
|
||||||
) == 0:
|
|
||||||
try:
|
|
||||||
self._run_iscsiadm(iscsi_properties,
|
|
||||||
("--login",),
|
|
||||||
check_exit_code=[0, 255])
|
|
||||||
except processutils.ProcessExecutionError as err:
|
|
||||||
# as this might be one of many paths,
|
|
||||||
# only set successful logins to startup automatically
|
|
||||||
if err.exit_code in [15]:
|
|
||||||
self._iscsiadm_update(iscsi_properties,
|
|
||||||
"node.startup",
|
|
||||||
"automatic")
|
|
||||||
return
|
|
||||||
|
|
||||||
self._iscsiadm_update(iscsi_properties,
|
|
||||||
"node.startup",
|
|
||||||
"automatic")
|
|
||||||
|
|
||||||
def _disconnect_from_iscsi_portal(self, iscsi_properties):
|
|
||||||
self._iscsiadm_update(iscsi_properties, "node.startup", "manual",
|
|
||||||
check_exit_code=[0, 21, 255])
|
|
||||||
self._run_iscsiadm(iscsi_properties, ("--logout",),
|
|
||||||
check_exit_code=[0, 21, 255])
|
|
||||||
self._run_iscsiadm(iscsi_properties, ('--op', 'delete'),
|
|
||||||
check_exit_code=[0, 21, 255])
|
|
||||||
|
|
||||||
def _get_multipath_device_name(self, single_path_device):
|
|
||||||
device = os.path.realpath(single_path_device)
|
|
||||||
|
|
||||||
out = self._run_multipath(['-ll',
|
|
||||||
device],
|
|
||||||
check_exit_code=[0, 1])[0]
|
|
||||||
mpath_line = [line for line in out.splitlines()
|
|
||||||
if "scsi_id" not in line] # ignore udev errors
|
|
||||||
if len(mpath_line) > 0 and len(mpath_line[0]) > 0:
|
|
||||||
return "/dev/mapper/%s" % mpath_line[0].split(" ")[0]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get_iscsi_devices(self):
|
|
||||||
try:
|
|
||||||
devices = list(os.walk('/dev/disk/by-path'))[0][-1]
|
|
||||||
except IndexError:
|
|
||||||
return []
|
|
||||||
iscsi_devs = []
|
|
||||||
for entry in devices:
|
|
||||||
if (entry.startswith("ip-") or
|
|
||||||
(entry.startswith('pci-') and 'ip-' in entry)):
|
|
||||||
iscsi_devs.append(entry)
|
|
||||||
|
|
||||||
return iscsi_devs
|
|
||||||
|
|
||||||
def _delete_mpath(self, iscsi_properties, multipath_device, ips_iqns):
|
|
||||||
entries = self._get_iscsi_devices()
|
|
||||||
# Loop through ips_iqns to construct all paths
|
|
||||||
iqn_luns = []
|
|
||||||
for ip, iqn in ips_iqns:
|
|
||||||
iqn_lun = '%s-lun-%s' % (iqn,
|
|
||||||
iscsi_properties.get('target_lun', 0))
|
|
||||||
iqn_luns.append(iqn_lun)
|
|
||||||
for dev in ['/dev/disk/by-path/%s' % dev for dev in entries]:
|
|
||||||
for iqn_lun in iqn_luns:
|
|
||||||
if iqn_lun in dev:
|
|
||||||
self._delete_device(dev)
|
|
||||||
|
|
||||||
self._rescan_multipath()
|
|
||||||
|
|
||||||
def _disconnect_mpath(self, iscsi_properties, ips_iqns):
|
|
||||||
for ip, iqn in ips_iqns:
|
|
||||||
props = iscsi_properties.copy()
|
|
||||||
props['target_portal'] = ip
|
|
||||||
props['target_iqn'] = iqn
|
|
||||||
self._disconnect_from_iscsi_portal(props)
|
|
||||||
|
|
||||||
self._rescan_multipath()
|
|
||||||
|
|
||||||
def _get_multipath_iqn(self, multipath_device):
|
|
||||||
entries = self._get_iscsi_devices()
|
|
||||||
for entry in entries:
|
|
||||||
entry_real_path = os.path.realpath("/dev/disk/by-path/%s" % entry)
|
|
||||||
entry_multipath = self._get_multipath_device_name(entry_real_path)
|
|
||||||
if entry_multipath == multipath_device:
|
|
||||||
return entry.split("iscsi-")[1].split("-lun")[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _run_iscsiadm_bare(self, iscsi_command, **kwargs):
|
|
||||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
|
||||||
(out, err) = utils.execute('iscsiadm',
|
|
||||||
*iscsi_command,
|
|
||||||
run_as_root=True,
|
|
||||||
check_exit_code=check_exit_code)
|
|
||||||
LOG.debug("iscsiadm %(command)s: stdout=%(out)s stderr=%(err)s",
|
|
||||||
{'command': iscsi_command, 'out': out, 'err': err})
|
|
||||||
return (out, err)
|
|
||||||
|
|
||||||
def _run_multipath(self, multipath_command, **kwargs):
|
|
||||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
|
||||||
(out, err) = utils.execute('multipath',
|
|
||||||
*multipath_command,
|
|
||||||
run_as_root=True,
|
|
||||||
check_exit_code=check_exit_code)
|
|
||||||
LOG.debug("multipath %(command)s: stdout=%(out)s stderr=%(err)s",
|
|
||||||
{'command': multipath_command, 'out': out, 'err': err})
|
|
||||||
return (out, err)
|
|
||||||
|
|
||||||
def _rescan_iscsi(self):
|
|
||||||
self._run_iscsiadm_bare(('-m', 'node', '--rescan'),
|
|
||||||
check_exit_code=[0, 1, 21, 255])
|
|
||||||
self._run_iscsiadm_bare(('-m', 'session', '--rescan'),
|
|
||||||
check_exit_code=[0, 1, 21, 255])
|
|
||||||
|
|
||||||
def _rescan_multipath(self):
|
|
||||||
self._run_multipath(['-r'], check_exit_code=[0, 1, 21])
|
|
||||||
|
|
||||||
def _get_host_device(self, transport_properties):
|
|
||||||
"""Find device path in devtemfs."""
|
|
||||||
device = ("ip-%s-iscsi-%s-lun-%s" %
|
|
||||||
(transport_properties['target_portal'],
|
|
||||||
transport_properties['target_iqn'],
|
|
||||||
transport_properties.get('target_lun', 0)))
|
|
||||||
if self._get_transport() == "default":
|
|
||||||
return ("/dev/disk/by-path/%s" % device)
|
|
||||||
else:
|
|
||||||
host_device = None
|
|
||||||
look_for_device = glob.glob('/dev/disk/by-path/*%s' % device)
|
|
||||||
if look_for_device:
|
|
||||||
host_device = look_for_device[0]
|
|
||||||
return host_device
|
|
||||||
|
|
||||||
def _reconnect(self, iscsi_properties):
|
|
||||||
# Note: iscsiadm does not support changing iface.iscsi_ifacename
|
|
||||||
# via --op update, so we do this at creation time
|
|
||||||
self._run_iscsiadm(iscsi_properties,
|
|
||||||
('--interface', self._get_transport(),
|
|
||||||
'--op', 'new'))
|
|
||||||
|
|
||||||
|
|
||||||
class LibvirtISERVolumeDriver(LibvirtISCSIVolumeDriver):
|
class LibvirtISERVolumeDriver(LibvirtISCSIVolumeDriver):
|
||||||
"""Driver to attach Network volumes to libvirt."""
|
"""Driver to attach Network volumes to libvirt."""
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
super(LibvirtISERVolumeDriver, self).__init__(connection)
|
super(LibvirtISERVolumeDriver, self).__init__(connection)
|
||||||
self.num_scan_tries = CONF.libvirt.num_iser_scan_tries
|
|
||||||
self.use_multipath = CONF.libvirt.iser_use_multipath
|
# Call the factory here so we can support
|
||||||
|
# more than x86 architectures.
|
||||||
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
|
'ISER', utils._get_root_helper(),
|
||||||
|
use_multipath=CONF.libvirt.iser_use_multipath,
|
||||||
|
device_scan_attempts=CONF.libvirt.num_iser_scan_tries,
|
||||||
|
transport=self._get_transport())
|
||||||
|
|
||||||
def _get_transport(self):
|
def _get_transport(self):
|
||||||
return 'iser'
|
return 'iser'
|
||||||
|
|
||||||
def _get_multipath_iqn(self, multipath_device):
|
|
||||||
entries = self._get_iscsi_devices()
|
|
||||||
for entry in entries:
|
|
||||||
entry_real_path = os.path.realpath("/dev/disk/by-path/%s" % entry)
|
|
||||||
entry_multipath = self._get_multipath_device_name(entry_real_path)
|
|
||||||
if entry_multipath == multipath_device:
|
|
||||||
return entry.split("iser-")[1].split("-lun")[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class LibvirtNFSVolumeDriver(LibvirtBaseVolumeDriver):
|
class LibvirtNFSVolumeDriver(LibvirtBaseVolumeDriver):
|
||||||
"""Class implements libvirt part of volume driver for NFS."""
|
"""Class implements libvirt part of volume driver for NFS."""
|
||||||
@ -1024,24 +512,11 @@ class LibvirtAOEVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
super(LibvirtAOEVolumeDriver,
|
super(LibvirtAOEVolumeDriver,
|
||||||
self).__init__(connection, is_block_dev=True)
|
self).__init__(connection, is_block_dev=True)
|
||||||
|
|
||||||
def _aoe_discover(self):
|
# Call the factory here so we can support
|
||||||
"""Call aoe-discover (aoe-tools) AoE Discover."""
|
# more than x86 architectures.
|
||||||
(out, err) = utils.execute('aoe-discover',
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
run_as_root=True, check_exit_code=0)
|
'AOE', utils._get_root_helper(),
|
||||||
return (out, err)
|
device_scan_attempts=CONF.libvirt.num_aoe_discover_tries)
|
||||||
|
|
||||||
def _aoe_revalidate(self, aoedev):
|
|
||||||
"""Revalidate the LUN Geometry (When an AoE ID is reused)."""
|
|
||||||
(out, err) = utils.execute('aoe-revalidate', aoedev,
|
|
||||||
run_as_root=True, check_exit_code=0)
|
|
||||||
return (out, err)
|
|
||||||
|
|
||||||
def _get_device_path(self, connection_info):
|
|
||||||
shelf = connection_info['data']['target_shelf']
|
|
||||||
lun = connection_info['data']['target_lun']
|
|
||||||
aoedev = 'e%s.%s' % (shelf, lun)
|
|
||||||
aoedevpath = '/dev/etherd/%s' % (aoedev)
|
|
||||||
return aoedevpath
|
|
||||||
|
|
||||||
def get_config(self, connection_info, disk_info):
|
def get_config(self, connection_info, disk_info):
|
||||||
"""Returns xml for libvirt."""
|
"""Returns xml for libvirt."""
|
||||||
@ -1053,48 +528,22 @@ class LibvirtAOEVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
return conf
|
return conf
|
||||||
|
|
||||||
def connect_volume(self, connection_info, mount_device):
|
def connect_volume(self, connection_info, mount_device):
|
||||||
shelf = connection_info['data']['target_shelf']
|
LOG.debug("Calling os-brick to attach AoE Volume")
|
||||||
lun = connection_info['data']['target_lun']
|
device_info = self.connector.connect_volume(connection_info['data'])
|
||||||
aoedev = 'e%s.%s' % (shelf, lun)
|
LOG.debug("Attached AoE volume %s", device_info)
|
||||||
aoedevpath = '/dev/etherd/%s' % (aoedev)
|
|
||||||
|
|
||||||
if os.path.exists(aoedevpath):
|
connection_info['data']['device_path'] = device_info['path']
|
||||||
# NOTE(jbr_): If aoedevpath already exists, revalidate the LUN.
|
|
||||||
self._aoe_revalidate(aoedev)
|
|
||||||
else:
|
|
||||||
# NOTE(jbr_): If aoedevpath does not exist, do a discover.
|
|
||||||
self._aoe_discover()
|
|
||||||
|
|
||||||
# NOTE(jbr_): Device path is not always present immediately
|
def disconnect_volume(self, connection_info, disk_dev):
|
||||||
def _wait_for_device_discovery(aoedevpath, mount_device):
|
"""Detach the volume from instance_name."""
|
||||||
tries = self.tries
|
|
||||||
if os.path.exists(aoedevpath):
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
if self.tries >= CONF.libvirt.num_aoe_discover_tries:
|
LOG.debug("calling os-brick to detach AoE Volume %s",
|
||||||
raise exception.NovaException(_("AoE device not found at %s") %
|
connection_info)
|
||||||
(aoedevpath))
|
self.connector.disconnect_volume(connection_info['data'], None)
|
||||||
LOG.warn(_LW("AoE volume not yet found at: %(aoedevpath)s. "
|
LOG.debug("Disconnected AoE Volume %s", disk_dev)
|
||||||
"Try number: %(tries)s"),
|
|
||||||
{'aoedevpath': aoedevpath, 'tries': tries})
|
|
||||||
|
|
||||||
self._aoe_discover()
|
super(LibvirtAOEVolumeDriver,
|
||||||
self.tries = self.tries + 1
|
self).disconnect_volume(connection_info, disk_dev)
|
||||||
|
|
||||||
self.tries = 0
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(
|
|
||||||
_wait_for_device_discovery, aoedevpath, mount_device)
|
|
||||||
timer.start(interval=2).wait()
|
|
||||||
|
|
||||||
tries = self.tries
|
|
||||||
if tries != 0:
|
|
||||||
LOG.debug("Found AoE device %(aoedevpath)s "
|
|
||||||
"(after %(tries)s rediscover)",
|
|
||||||
{'aoedevpath': aoedevpath,
|
|
||||||
'tries': tries})
|
|
||||||
|
|
||||||
connection_info['data']['device_path'] = \
|
|
||||||
self._get_device_path(connection_info)
|
|
||||||
|
|
||||||
|
|
||||||
class LibvirtGlusterfsVolumeDriver(LibvirtBaseVolumeDriver):
|
class LibvirtGlusterfsVolumeDriver(LibvirtBaseVolumeDriver):
|
||||||
@ -1199,26 +648,12 @@ class LibvirtFibreChannelVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
super(LibvirtFibreChannelVolumeDriver,
|
super(LibvirtFibreChannelVolumeDriver,
|
||||||
self).__init__(connection, is_block_dev=False)
|
self).__init__(connection, is_block_dev=False)
|
||||||
|
|
||||||
def _get_pci_num(self, hba):
|
# Call the factory here so we can support
|
||||||
# NOTE(walter-boring)
|
# more than x86 architectures.
|
||||||
# device path is in format of
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
# /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.3/host2/fc_host/host2
|
'FIBRE_CHANNEL', utils._get_root_helper(),
|
||||||
# sometimes an extra entry exists before the host2 value
|
use_multipath=CONF.libvirt.iscsi_use_multipath,
|
||||||
# we always want the value prior to the host2 value
|
device_scan_attempts=CONF.libvirt.num_iscsi_scan_tries)
|
||||||
pci_num = None
|
|
||||||
if hba is not None:
|
|
||||||
if "device_path" in hba:
|
|
||||||
index = 0
|
|
||||||
device_path = hba['device_path'].split('/')
|
|
||||||
for value in device_path:
|
|
||||||
if value.startswith('host'):
|
|
||||||
break
|
|
||||||
index = index + 1
|
|
||||||
|
|
||||||
if index > 0:
|
|
||||||
pci_num = device_path[index - 1]
|
|
||||||
|
|
||||||
return pci_num
|
|
||||||
|
|
||||||
def get_config(self, connection_info, disk_info):
|
def get_config(self, connection_info, disk_info):
|
||||||
"""Returns xml for libvirt."""
|
"""Returns xml for libvirt."""
|
||||||
@ -1229,193 +664,33 @@ class LibvirtFibreChannelVolumeDriver(LibvirtBaseVolumeDriver):
|
|||||||
conf.source_path = connection_info['data']['device_path']
|
conf.source_path = connection_info['data']['device_path']
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
def _get_lun_string_for_s390(self, lun):
|
|
||||||
target_lun = 0
|
|
||||||
if lun < 256:
|
|
||||||
target_lun = "0x00%02x000000000000" % lun
|
|
||||||
elif lun <= 0xffffffff:
|
|
||||||
target_lun = "0x%08x00000000" % lun
|
|
||||||
return target_lun
|
|
||||||
|
|
||||||
def _get_device_file_path_s390(self, pci_num, target_wwn, lun):
|
|
||||||
"""Returns device file path"""
|
|
||||||
# NOTE the format of device file paths depends on the system
|
|
||||||
# architecture. Most architectures use a PCI based format.
|
|
||||||
# Systems following the S390, or S390x architecture use a format
|
|
||||||
# which is based upon the inherent channel architecture (ccw).
|
|
||||||
host_device = ("/dev/disk/by-path/ccw-%s-zfcp-%s:%s" %
|
|
||||||
(pci_num,
|
|
||||||
target_wwn,
|
|
||||||
lun))
|
|
||||||
return host_device
|
|
||||||
|
|
||||||
def _remove_lun_from_s390(self, connection_info):
|
|
||||||
"""Rempove lun from s390 configuration"""
|
|
||||||
# If LUN scanning is turned off on systems following the s390, or
|
|
||||||
# s390x architecture LUNs need to be removed from the configuration
|
|
||||||
# using the unit_remove call. The unit_remove call needs to be issued
|
|
||||||
# for each (virtual) HBA and target_port.
|
|
||||||
fc_properties = connection_info['data']
|
|
||||||
lun = int(fc_properties.get('target_lun', 0))
|
|
||||||
target_lun = self._get_lun_string_for_s390(lun)
|
|
||||||
ports = fc_properties['target_wwn']
|
|
||||||
|
|
||||||
for device_num, target_wwn in self._get_possible_devices(ports):
|
|
||||||
libvirt_utils.perform_unit_remove_for_s390(device_num,
|
|
||||||
target_wwn,
|
|
||||||
target_lun)
|
|
||||||
|
|
||||||
def _get_possible_devices(self, wwnports):
|
|
||||||
"""Compute the possible valid fiber channel device options.
|
|
||||||
|
|
||||||
:param wwnports: possible wwn addresses. Can either be string
|
|
||||||
or list of strings.
|
|
||||||
|
|
||||||
:returns: list of (pci_id, wwn) tuples
|
|
||||||
|
|
||||||
Given one or more wwn (mac addresses for fiber channel) ports
|
|
||||||
do the matrix math to figure out a set of pci device, wwn
|
|
||||||
tuples that are potentially valid (they won't all be). This
|
|
||||||
provides a search space for the device connection.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# the wwn (think mac addresses for fiber channel devices) can
|
|
||||||
# either be a single value or a list. Normalize it to a list
|
|
||||||
# for further operations.
|
|
||||||
wwns = []
|
|
||||||
if isinstance(wwnports, list):
|
|
||||||
for wwn in wwnports:
|
|
||||||
wwns.append(str(wwn))
|
|
||||||
elif isinstance(wwnports, six.string_types):
|
|
||||||
wwns.append(str(wwnports))
|
|
||||||
|
|
||||||
raw_devices = []
|
|
||||||
hbas = libvirt_utils.get_fc_hbas_info()
|
|
||||||
for hba in hbas:
|
|
||||||
pci_num = self._get_pci_num(hba)
|
|
||||||
if pci_num is not None:
|
|
||||||
for wwn in wwns:
|
|
||||||
target_wwn = "0x%s" % wwn.lower()
|
|
||||||
raw_devices.append((pci_num, target_wwn))
|
|
||||||
return raw_devices
|
|
||||||
|
|
||||||
@utils.synchronized('connect_volume')
|
|
||||||
def connect_volume(self, connection_info, disk_info):
|
def connect_volume(self, connection_info, disk_info):
|
||||||
"""Attach the volume to instance_name."""
|
"""Attach the volume to instance_name."""
|
||||||
fc_properties = connection_info['data']
|
|
||||||
mount_device = disk_info["dev"]
|
|
||||||
|
|
||||||
possible_devs = self._get_possible_devices(fc_properties['target_wwn'])
|
LOG.debug("Calling os-brick to attach FC Volume")
|
||||||
# map the raw device possibilities to possible host device paths
|
device_info = self.connector.connect_volume(connection_info['data'])
|
||||||
host_devices = []
|
LOG.debug("Attached FC volume %s", device_info)
|
||||||
for device in possible_devs:
|
|
||||||
pci_num, target_wwn = device
|
|
||||||
if platform.machine() in (arch.S390, arch.S390X):
|
|
||||||
target_lun = self._get_lun_string_for_s390(
|
|
||||||
fc_properties.get('target_lun', 0))
|
|
||||||
host_device = self._get_device_file_path_s390(
|
|
||||||
pci_num,
|
|
||||||
target_wwn,
|
|
||||||
target_lun)
|
|
||||||
libvirt_utils.perform_unit_add_for_s390(
|
|
||||||
pci_num, target_wwn, target_lun)
|
|
||||||
else:
|
|
||||||
host_device = ("/dev/disk/by-path/pci-%s-fc-%s-lun-%s" %
|
|
||||||
(pci_num,
|
|
||||||
target_wwn,
|
|
||||||
fc_properties.get('target_lun', 0)))
|
|
||||||
host_devices.append(host_device)
|
|
||||||
|
|
||||||
if len(host_devices) == 0:
|
connection_info['data']['device_path'] = device_info['path']
|
||||||
# this is empty because we don't have any FC HBAs
|
if 'multipath_id' in device_info:
|
||||||
msg = _("We are unable to locate any Fibre Channel devices")
|
connection_info['data']['multipath_id'] = \
|
||||||
raise exception.NovaException(msg)
|
device_info['multipath_id']
|
||||||
|
|
||||||
# The /dev/disk/by-path/... node is not always present immediately
|
def disconnect_volume(self, connection_info, disk_dev):
|
||||||
# We only need to find the first device. Once we see the first device
|
|
||||||
# multipath will have any others.
|
|
||||||
def _wait_for_device_discovery(host_devices, mount_device):
|
|
||||||
tries = self.tries
|
|
||||||
for device in host_devices:
|
|
||||||
LOG.debug("Looking for Fibre Channel dev %(device)s",
|
|
||||||
{'device': device})
|
|
||||||
if os.path.exists(device):
|
|
||||||
self.host_device = device
|
|
||||||
# get the /dev/sdX device. This is used
|
|
||||||
# to find the multipath device.
|
|
||||||
self.device_name = os.path.realpath(device)
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
if self.tries >= CONF.libvirt.num_iscsi_scan_tries:
|
|
||||||
msg = _("Fibre Channel device not found.")
|
|
||||||
raise exception.NovaException(msg)
|
|
||||||
|
|
||||||
LOG.warn(_LW("Fibre volume not yet found at: %(mount_device)s. "
|
|
||||||
"Will rescan & retry. Try number: %(tries)s"),
|
|
||||||
{'mount_device': mount_device, 'tries': tries})
|
|
||||||
|
|
||||||
linuxscsi.rescan_hosts(libvirt_utils.get_fc_hbas_info())
|
|
||||||
self.tries = self.tries + 1
|
|
||||||
|
|
||||||
self.host_device = None
|
|
||||||
self.device_name = None
|
|
||||||
self.tries = 0
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(
|
|
||||||
_wait_for_device_discovery, host_devices, mount_device)
|
|
||||||
timer.start(interval=2).wait()
|
|
||||||
|
|
||||||
tries = self.tries
|
|
||||||
if self.host_device is not None and self.device_name is not None:
|
|
||||||
LOG.debug("Found Fibre Channel volume %(mount_device)s "
|
|
||||||
"(after %(tries)s rescans)",
|
|
||||||
{'mount_device': mount_device,
|
|
||||||
'tries': tries})
|
|
||||||
|
|
||||||
# see if the new drive is part of a multipath
|
|
||||||
# device. If so, we'll use the multipath device.
|
|
||||||
mdev_info = linuxscsi.find_multipath_device(self.device_name)
|
|
||||||
if mdev_info is not None:
|
|
||||||
LOG.debug("Multipath device discovered %(device)s",
|
|
||||||
{'device': mdev_info['device']})
|
|
||||||
device_path = mdev_info['device']
|
|
||||||
connection_info['data']['device_path'] = device_path
|
|
||||||
connection_info['data']['devices'] = mdev_info['devices']
|
|
||||||
connection_info['data']['multipath_id'] = mdev_info['id']
|
|
||||||
else:
|
|
||||||
# we didn't find a multipath device.
|
|
||||||
# so we assume the kernel only sees 1 device
|
|
||||||
device_path = self.host_device
|
|
||||||
device_info = linuxscsi.get_device_info(self.device_name)
|
|
||||||
connection_info['data']['device_path'] = device_path
|
|
||||||
connection_info['data']['devices'] = [device_info]
|
|
||||||
|
|
||||||
@utils.synchronized('connect_volume')
|
|
||||||
def disconnect_volume(self, connection_info, mount_device):
|
|
||||||
"""Detach the volume from instance_name."""
|
"""Detach the volume from instance_name."""
|
||||||
|
|
||||||
|
LOG.debug("calling os-brick to detach FC Volume")
|
||||||
|
# TODO(walter-boring) eliminated the need for preserving
|
||||||
|
# multipath_id. Use scsi_id instead of multipath -ll
|
||||||
|
# This will then eliminate the need to pass anything in
|
||||||
|
# the 2nd param of disconnect_volume and be consistent
|
||||||
|
# with the rest of the connectors.
|
||||||
|
self.connector.disconnect_volume(connection_info['data'],
|
||||||
|
connection_info['data'])
|
||||||
|
LOG.debug("Disconnected FC Volume %s", disk_dev)
|
||||||
|
|
||||||
super(LibvirtFibreChannelVolumeDriver,
|
super(LibvirtFibreChannelVolumeDriver,
|
||||||
self).disconnect_volume(connection_info, mount_device)
|
self).disconnect_volume(connection_info, disk_dev)
|
||||||
|
|
||||||
# If this is a multipath device, we need to search again
|
|
||||||
# and make sure we remove all the devices. Some of them
|
|
||||||
# might not have shown up at attach time.
|
|
||||||
if 'multipath_id' in connection_info['data']:
|
|
||||||
multipath_id = connection_info['data']['multipath_id']
|
|
||||||
mdev_info = linuxscsi.find_multipath_device(multipath_id)
|
|
||||||
devices = mdev_info['devices'] if mdev_info else []
|
|
||||||
LOG.debug("devices to remove = %s", devices)
|
|
||||||
else:
|
|
||||||
# only needed when multipath-tools work improperly
|
|
||||||
devices = connection_info['data'].get('devices', [])
|
|
||||||
LOG.warn(_LW("multipath-tools probably work improperly. "
|
|
||||||
"devices to remove = %s.") % devices)
|
|
||||||
|
|
||||||
# There may have been more than 1 device mounted
|
|
||||||
# by the kernel for this volume. We have to remove
|
|
||||||
# all of them
|
|
||||||
for device in devices:
|
|
||||||
linuxscsi.remove_device(device)
|
|
||||||
if platform.machine() in (arch.S390, arch.S390X):
|
|
||||||
self._remove_lun_from_s390(connection_info)
|
|
||||||
|
|
||||||
|
|
||||||
class LibvirtScalityVolumeDriver(LibvirtBaseVolumeDriver):
|
class LibvirtScalityVolumeDriver(LibvirtBaseVolumeDriver):
|
||||||
|
@ -15,21 +15,20 @@
|
|||||||
"""
|
"""
|
||||||
Volume utilities for virt drivers.
|
Volume utilities for virt drivers.
|
||||||
"""
|
"""
|
||||||
|
from os_brick.initiator import connector
|
||||||
|
from oslo_concurrency import processutils as putils
|
||||||
|
|
||||||
from nova import exception
|
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
|
||||||
|
|
||||||
def get_iscsi_initiator():
|
def get_iscsi_initiator(execute=None):
|
||||||
"""Get iscsi initiator name for this machine."""
|
"""Get iscsi initiator name for this machine."""
|
||||||
# NOTE(vish) openiscsi stores initiator name in a file that
|
|
||||||
# needs root permission to read.
|
|
||||||
try:
|
|
||||||
contents = utils.read_file_as_root('/etc/iscsi/initiatorname.iscsi')
|
|
||||||
except exception.FileNotFound:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
for l in contents.split('\n'):
|
root_helper = utils._get_root_helper()
|
||||||
if l.startswith('InitiatorName='):
|
# so we can mock out the execute itself
|
||||||
return l[l.index('=') + 1:].strip()
|
# in unit tests.
|
||||||
return ''
|
if not execute:
|
||||||
|
execute = putils.execute
|
||||||
|
iscsi = connector.ISCSIConnector(root_helper=root_helper,
|
||||||
|
execute=execute)
|
||||||
|
return iscsi.get_initiator()
|
||||||
|
@ -51,3 +51,4 @@ oslo.middleware>=2.4.0 # Apache-2.0
|
|||||||
psutil<2.0.0,>=1.1.1
|
psutil<2.0.0,>=1.1.1
|
||||||
oslo.versionedobjects!=0.5.0,>=0.3.0
|
oslo.versionedobjects!=0.5.0,>=0.3.0
|
||||||
alembic>=0.7.2
|
alembic>=0.7.2
|
||||||
|
os-brick>=0.3.2 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user