summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/nova/rootwrap.d/compute.filters9
-rw-r--r--nova/storage/__init__.py15
-rw-r--r--nova/storage/linuxscsi.py139
-rw-r--r--nova/tests/fake_libvirt_utils.py54
-rw-r--r--nova/tests/test_libvirt.py6
-rw-r--r--nova/tests/test_libvirt_volume.py79
-rwxr-xr-xnova/virt/libvirt/driver.py45
-rwxr-xr-xnova/virt/libvirt/utils.py88
-rw-r--r--nova/virt/libvirt/volume.py140
9 files changed, 565 insertions, 10 deletions
diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters
index cce8504cb..00b73d831 100644
--- a/etc/nova/rootwrap.d/compute.filters
+++ b/etc/nova/rootwrap.d/compute.filters
@@ -186,4 +186,11 @@ read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localf
read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow
# nova/virt/libvirt/volume.py: 'multipath' '-R'
-multipath: CommandFilter, /sbin/multipath, root \ No newline at end of file
+multipath: CommandFilter, /sbin/multipath, root
+
+# nova/virt/libvirt/utils.py:
+systool: CommandFilter, /usr/bin/systool, root
+
+# nova/virt/libvirt/volume.py:
+sginfo: CommandFilter, /usr/bin/sginfo, root
+sg_scan: CommandFilter, /usr/bin/sg_scan, root
diff --git a/nova/storage/__init__.py b/nova/storage/__init__.py
new file mode 100644
index 000000000..931ad9875
--- /dev/null
+++ b/nova/storage/__init__.py
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Hewlett-Packard, Inc.
+#
+# 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.
diff --git a/nova/storage/linuxscsi.py b/nova/storage/linuxscsi.py
new file mode 100644
index 000000000..739092b2e
--- /dev/null
+++ b/nova/storage/linuxscsi.py
@@ -0,0 +1,139 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# (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 nova import exception
+from nova.openstack.common import log as logging
+from nova import utils
+
+LOG = logging.getLogger(__name__)
+
+
+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 utils.LoopingCallDone()
+
+
+def remove_device(device):
+ tries = 0
+ timer = utils.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 exception.ProcessExecutionError as exc:
+ LOG.warn(_("Multipath call failed exit (%(code)s)")
+ % {'code': exc.exit_code})
+ return None
+
+ if out:
+ lines = out.strip()
+ lines = lines.split("\n")
+ if lines:
+ line = lines[0]
+ info = line.split(" ")
+ # device line output is different depending
+ # on /etc/multipath.conf settings.
+ if info[1][:2] == "dm":
+ mdev = "/dev/%s" % info[1]
+ elif info[2][:2] == "dm":
+ mdev = "/dev/%s" % info[2]
+
+ if mdev is None:
+ LOG.warn(_("Couldn't find multipath device %(line)s")
+ % locals())
+ return None
+
+ LOG.debug(_("Found multipath device = %(mdev)s") % locals())
+ device_lines = lines[3:]
+ for dev_line in device_lines:
+ dev_line = dev_line.strip()
+ dev_line = dev_line[3:]
+ dev_info = dev_line.split(" ")
+ if dev_line.find("policy") != -1:
+ 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,
+ "devices": devices}
+ return info
+ return None
diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py
index 285a4b7e3..caa5e72d1 100644
--- a/nova/tests/fake_libvirt_utils.py
+++ b/nova/tests/fake_libvirt_utils.py
@@ -34,6 +34,60 @@ 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):
pass
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 6cff3f567..2c5b07428 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -338,6 +338,8 @@ class LibvirtConnTestCase(test.TestCase):
initiator = 'fake.initiator.iqn'
ip = 'fakeip'
host = 'fakehost'
+ wwpns = ['100010604b019419']
+ wwnns = ['200010604b019419']
self.flags(my_ip=ip)
self.flags(host=host)
@@ -345,7 +347,9 @@ class LibvirtConnTestCase(test.TestCase):
expected = {
'ip': ip,
'initiator': initiator,
- 'host': host
+ 'host': host,
+ 'wwpns': wwpns,
+ 'wwnns': wwnns
}
volume = {
'id': 'fake'
diff --git a/nova/tests/test_libvirt_volume.py b/nova/tests/test_libvirt_volume.py
index 1d157ba34..fa71782ee 100644
--- a/nova/tests/test_libvirt_volume.py
+++ b/nova/tests/test_libvirt_volume.py
@@ -17,10 +17,14 @@
import os
+from nova import exception
from nova.openstack.common import cfg
+from nova.storage import linuxscsi
from nova import test
+from nova.tests import fake_libvirt_utils
from nova import utils
from nova.virt import fake
+from nova.virt.libvirt import utils as libvirt_utils
from nova.virt.libvirt import volume
CONF = cfg.CONF
@@ -467,3 +471,78 @@ class LibvirtVolumeTestCase(test.TestCase):
('stat', export_mnt_base),
('mount', '-t', 'glusterfs', export_string, export_mnt_base)]
self.assertEqual(self.executes, expected_commands)
+
+ 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):
+ 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,
+ "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)
+ location = '10.0.2.15:3260'
+ name = 'volume-00000001'
+ wwn = '1234567890123456'
+ vol = {'id': 1, 'name': name}
+ connection_info = self.fibrechan_connection(vol, location, wwn)
+ mount_device = "vde"
+ disk_info = {
+ "bus": "virtio",
+ "dev": mount_device,
+ "type": "disk"
+ }
+ conf = libvirt_driver.connect_volume(connection_info, disk_info)
+ tree = conf.format_dom()
+ dev_str = '/dev/disk/by-path/pci-0000:05:00.2-fc-0x%s-lun-1' % wwn
+ self.assertEqual(tree.get('type'), 'block')
+ self.assertEqual(tree.find('./source').get('dev'), multipath_devname)
+ connection_info["data"]["devices"] = devices["devices"]
+ libvirt_driver.disconnect_volume(connection_info, mount_device)
+ expected_commands = []
+ self.assertEqual(self.executes, expected_commands)
+
+ 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, 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, disk_info)
+
+ 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)
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 386fe836c..e7d4d71a7 100755
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -6,6 +6,7 @@
# Copyright (c) 2010 Citrix Systems, Inc.
# Copyright (c) 2011 Piston Cloud Computing, Inc
# Copyright (c) 2012 University Of Minho
+# (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
@@ -154,7 +155,9 @@ libvirt_opts = [
'nfs=nova.virt.libvirt.volume.LibvirtNFSVolumeDriver',
'aoe=nova.virt.libvirt.volume.LibvirtAOEVolumeDriver',
'glusterfs='
- 'nova.virt.libvirt.volume.LibvirtGlusterfsVolumeDriver'
+ 'nova.virt.libvirt.volume.LibvirtGlusterfsVolumeDriver',
+ 'fibre_channel=nova.virt.libvirt.volume.'
+ 'LibvirtFibreChannelVolumeDriver'
],
help='Libvirt handlers for remote volumes.'),
cfg.StrOpt('libvirt_disk_prefix',
@@ -281,6 +284,8 @@ class LibvirtDriver(driver.ComputeDriver):
self._host_state = None
self._initiator = None
+ self._fc_wwnns = None
+ self._fc_wwpns = None
self._wrapped_conn = None
self._caps = None
self.read_only = read_only
@@ -644,13 +649,37 @@ class LibvirtDriver(driver.ComputeDriver):
if not self._initiator:
self._initiator = libvirt_utils.get_iscsi_initiator()
if not self._initiator:
- LOG.warn(_('Could not determine iscsi initiator name'),
- instance=instance)
- return {
- 'ip': CONF.my_ip,
- 'initiator': self._initiator,
- 'host': CONF.host
- }
+ LOG.debug(_('Could not determine iscsi initiator name'),
+ instance=instance)
+
+ if not self._fc_wwnns:
+ 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 not self._fc_wwpns:
+ 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)
+
+ if not self._initiator and not self._fc_wwnns and not self._fc_wwpns:
+ msg = _("No Volume Connector found.")
+ LOG.error(msg)
+ raise exception.NovaException(msg)
+
+ connector = {'ip': CONF.my_ip,
+ 'initiator': self._initiator,
+ 'host': CONF.host}
+
+ 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):
target = libvirt_utils.get_instance_path(instance) + "_resize"
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index b8e0cafec..d2e03321a 100755
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -6,6 +6,7 @@
# Copyright (c) 2010 Citrix Systems, Inc.
# Copyright (c) 2011 Piston Cloud Computing, Inc
# Copyright (c) 2011 OpenStack LLC
+# (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
@@ -56,6 +57,93 @@ def get_iscsi_initiator():
return l[l.index('=') + 1:].strip()
+def get_fc_hbas():
+ """Get the Fibre Channel HBA information."""
+ try:
+ out, err = execute('systool', '-c', 'fc_host', '-v',
+ run_as_root=True)
+ except exception.ProcessExecutionError as exc:
+ if exc.exit_code == 96:
+ LOG.warn(_("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:
+ 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):
"""Create a disk image
diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py
index 650292e29..088f19a9f 100644
--- a/nova/virt/libvirt/volume.py
+++ b/nova/virt/libvirt/volume.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -26,6 +27,7 @@ from nova.openstack.common import cfg
from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova import paths
+from nova.storage import linuxscsi
from nova import utils
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import utils as virtutils
@@ -608,3 +610,141 @@ class LibvirtGlusterfsVolumeDriver(LibvirtBaseVolumeDriver):
return utils.execute('stat', path, run_as_root=True)
except exception.ProcessExecutionError:
return False
+
+
+class LibvirtFibreChannelVolumeDriver(LibvirtBaseVolumeDriver):
+ """Driver to attach Fibre Channel Network volumes to libvirt."""
+
+ def __init__(self, connection):
+ super(LibvirtFibreChannelVolumeDriver,
+ self).__init__(connection, is_block_dev=False)
+
+ def _get_pci_num(self, hba):
+ # NOTE(walter-boring)
+ # device path is in format of
+ # /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.3/host2/fc_host/host2
+ # sometimes an extra entry exists before the host2 value
+ # we always want the value prior to the host2 value
+ 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
+
+ @lockutils.synchronized('connect_volume', 'nova-')
+ def connect_volume(self, connection_info, disk_info):
+ """Attach the volume to instance_name."""
+ fc_properties = connection_info['data']
+ mount_device = disk_info["dev"]
+
+ ports = fc_properties['target_wwn']
+ wwns = []
+ # we support a list of wwns or a single wwn
+ if isinstance(ports, list):
+ for wwn in ports:
+ wwns.append(wwn)
+ elif isinstance(ports, str):
+ wwns.append(ports)
+
+ # We need to look for wwns on every hba
+ # because we don't know ahead of time
+ # where they will show up.
+ hbas = virtutils.get_fc_hbas_info()
+ host_devices = []
+ 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()
+ 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:
+ # this is empty because we don't have any FC HBAs
+ msg = _("We are unable to locate any Fibre Channel devices")
+ raise exception.NovaException(msg)
+
+ # The /dev/disk/by-path/... node is not always present immediately
+ # 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")
+ % locals())
+ 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 utils.LoopingCallDone()
+
+ if self.tries >= CONF.num_iscsi_scan_tries:
+ msg = _("Fibre Channel device not found.")
+ raise exception.NovaException(msg)
+
+ LOG.warn(_("Fibre volume not yet found at: %(mount_device)s. "
+ "Will rescan & retry. Try number: %(tries)s") %
+ locals())
+
+ linuxscsi.rescan_hosts(hbas)
+ self.tries = self.tries + 1
+
+ self.host_device = None
+ self.device_name = None
+ self.tries = 0
+ timer = utils.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)") % locals())
+
+ # 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']['devices'] = mdev_info['devices']
+ 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']['devices'] = [device_info]
+
+ conf = super(LibvirtFibreChannelVolumeDriver,
+ self).connect_volume(connection_info, disk_info)
+
+ conf.source_type = "block"
+ conf.source_path = device_path
+ return conf
+
+ @lockutils.synchronized('connect_volume', 'nova-')
+ def disconnect_volume(self, connection_info, mount_device):
+ """Detach the volume from instance_name."""
+ super(LibvirtFibreChannelVolumeDriver,
+ self).disconnect_volume(connection_info, mount_device)
+ devices = connection_info['data']['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)