diff options
| -rw-r--r-- | etc/nova/rootwrap.d/compute.filters | 9 | ||||
| -rw-r--r-- | nova/storage/__init__.py | 15 | ||||
| -rw-r--r-- | nova/storage/linuxscsi.py | 139 | ||||
| -rw-r--r-- | nova/tests/fake_libvirt_utils.py | 54 | ||||
| -rw-r--r-- | nova/tests/test_libvirt.py | 6 | ||||
| -rw-r--r-- | nova/tests/test_libvirt_volume.py | 79 | ||||
| -rwxr-xr-x | nova/virt/libvirt/driver.py | 45 | ||||
| -rwxr-xr-x | nova/virt/libvirt/utils.py | 88 | ||||
| -rw-r--r-- | nova/virt/libvirt/volume.py | 140 |
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) |
