Add Brick Fibre Channel attach/detach support.
This patch adds the required code to do Fibre Channel attach and detaches of volumes. This code has been pulled over from Nova's implementation of FC attach/detach. Also adds a new driver config entry to enable multipath support for iSCSI and FC attaches during volume to image and image to volume transfers. DocImpact blueprint cinder-refactor-attach Change-Id: I436592f958a6c14cd2a0b5d7e53362dd1a7c1a48
This commit is contained in:
@@ -17,8 +17,10 @@
|
||||
|
||||
import executor
|
||||
import host_driver
|
||||
import linuxfc
|
||||
import linuxscsi
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
@@ -27,6 +29,7 @@ from cinder import exception
|
||||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import lockutils
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder.openstack.common import processutils as putils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -34,6 +37,21 @@ CONF = cfg.CONF
|
||||
synchronized = lockutils.synchronized_with_prefix('brick-')
|
||||
|
||||
|
||||
def get_connector_properties():
|
||||
"""Get the connection properties for all protocols."""
|
||||
|
||||
iscsi = ISCSIConnector()
|
||||
fc = linuxfc.LinuxFibreChannel()
|
||||
|
||||
props = {}
|
||||
props['ip'] = CONF.my_ip
|
||||
props['host'] = socket.gethostname()
|
||||
props['initiator'] = iscsi.get_initiator()
|
||||
props['wwpns'] = fc.get_fc_wwpns()
|
||||
|
||||
return props
|
||||
|
||||
|
||||
class InitiatorConnector(executor.Executor):
|
||||
def __init__(self, driver=None, execute=putils.execute,
|
||||
root_helper="sudo", *args, **kwargs):
|
||||
@@ -43,17 +61,61 @@ class InitiatorConnector(executor.Executor):
|
||||
driver = host_driver.HostDriver()
|
||||
self.set_driver(driver)
|
||||
|
||||
self._linuxscsi = linuxscsi.LinuxSCSI(execute, root_helper)
|
||||
|
||||
def set_driver(self, driver):
|
||||
"""The driver used to find used LUNs."""
|
||||
"""The driver is used to find used LUNs."""
|
||||
|
||||
self.driver = driver
|
||||
|
||||
@staticmethod
|
||||
def factory(protocol, execute=putils.execute,
|
||||
root_helper="sudo", use_multipath=False):
|
||||
"""Build a Connector object based upon protocol."""
|
||||
LOG.debug("Factory for %s" % protocol)
|
||||
protocol = protocol.upper()
|
||||
if protocol == "ISCSI":
|
||||
return ISCSIConnector(execute=execute,
|
||||
root_helper=root_helper,
|
||||
use_multipath=use_multipath)
|
||||
elif protocol == "FIBRE_CHANNEL":
|
||||
return FibreChannelConnector(execute=execute,
|
||||
root_helper=root_helper,
|
||||
use_multipath=use_multipath)
|
||||
else:
|
||||
msg = (_("Invalid InitiatorConnector protocol "
|
||||
"specified %(protocol)s") %
|
||||
dict(protocol=protocol))
|
||||
raise ValueError(msg)
|
||||
|
||||
def check_valid_device(self, path):
|
||||
cmd = ('dd', 'if=%(path)s' % {"path": path},
|
||||
'of=/dev/null', 'count=1')
|
||||
out, info = None, None
|
||||
try:
|
||||
out, info = self._execute(*cmd, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.error(_("Failed to access the device on the path "
|
||||
"%(path)s: %(error)s %(info)s.") %
|
||||
{"path": path, "error": e.stderr,
|
||||
"info": info})
|
||||
return False
|
||||
# If the info is none, the path does not exist.
|
||||
if info is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def connect_volume(self, connection_properties):
|
||||
"""Connect to a volume. The connection_properties
|
||||
describes the information needed by the specific
|
||||
protocol to use to make the connection.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def disconnect_volume(self, connection_properties):
|
||||
def disconnect_volume(self, connection_properties, device_info):
|
||||
"""Disconnect a volume from the local host.
|
||||
The connection_properties are the same as from connect_volume.
|
||||
The device_info is returned from connect_volume.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -66,6 +128,7 @@ class ISCSIConnector(InitiatorConnector):
|
||||
super(ISCSIConnector, self).__init__(driver, execute, root_helper,
|
||||
*args, **kwargs)
|
||||
self.use_multipath = use_multipath
|
||||
self._linuxscsi = linuxscsi.LinuxSCSI(execute, root_helper)
|
||||
|
||||
@synchronized('connect_volume')
|
||||
def connect_volume(self, connection_properties):
|
||||
@@ -138,7 +201,7 @@ class ISCSIConnector(InitiatorConnector):
|
||||
return device_info
|
||||
|
||||
@synchronized('connect_volume')
|
||||
def disconnect_volume(self, connection_properties):
|
||||
def disconnect_volume(self, connection_properties, device_info):
|
||||
"""Detach the volume from instance_name.
|
||||
|
||||
connection_properties for iSCSI must include:
|
||||
@@ -179,6 +242,19 @@ class ISCSIConnector(InitiatorConnector):
|
||||
'lun': connection_properties.get('target_lun', 0)})
|
||||
return path
|
||||
|
||||
def get_initiator(self):
|
||||
"""Secure helper to read file as root."""
|
||||
try:
|
||||
file_path = '/etc/iscsi/initiatorname.iscsi'
|
||||
lines, _err = self._execute('cat', file_path, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
|
||||
for l in lines.split('\n'):
|
||||
if l.startswith('InitiatorName='):
|
||||
return l[l.index('=') + 1:].strip()
|
||||
except exception.ProcessExecutionError:
|
||||
raise exception.FileNotFound(file_path=file_path)
|
||||
|
||||
def _run_iscsiadm(self, connection_properties, iscsi_command, **kwargs):
|
||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
||||
(out, err) = self._execute('iscsiadm', '-m', 'node', '-T',
|
||||
@@ -373,3 +449,169 @@ class ISCSIConnector(InitiatorConnector):
|
||||
|
||||
def _rescan_multipath(self):
|
||||
self._run_multipath('-r', check_exit_code=[0, 1, 21])
|
||||
|
||||
|
||||
class FibreChannelConnector(InitiatorConnector):
|
||||
""""Connector class to attach/detach Fibre Channel volumes."""
|
||||
|
||||
def __init__(self, driver=None, execute=putils.execute,
|
||||
root_helper="sudo", use_multipath=False,
|
||||
*args, **kwargs):
|
||||
super(FibreChannelConnector, self).__init__(driver, execute,
|
||||
root_helper,
|
||||
*args, **kwargs)
|
||||
self.use_multipath = use_multipath
|
||||
self._linuxscsi = linuxscsi.LinuxSCSI(execute, root_helper)
|
||||
self._linuxfc = linuxfc.LinuxFibreChannel(execute, root_helper)
|
||||
|
||||
@synchronized('connect_volume')
|
||||
def connect_volume(self, connection_properties):
|
||||
"""Attach the volume to instance_name.
|
||||
|
||||
connection_properties for Fibre Channel must include:
|
||||
target_portal - ip and optional port
|
||||
target_iqn - iSCSI Qualified Name
|
||||
target_lun - LUN id of the volume
|
||||
"""
|
||||
LOG.debug("execute = %s" % self._execute)
|
||||
device_info = {'type': 'block'}
|
||||
|
||||
ports = connection_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 = self._linuxfc.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,
|
||||
connection_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.CinderException(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):
|
||||
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.num_iscsi_scan_tries:
|
||||
msg = _("Fibre Channel device not found.")
|
||||
raise exception.CinderException(msg)
|
||||
|
||||
LOG.warn(_("Fibre volume not yet found. "
|
||||
"Will rescan & retry. Try number: %(tries)s"),
|
||||
{'tries': tries})
|
||||
|
||||
self._linuxfc.rescan_hosts(hbas)
|
||||
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)
|
||||
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 %(name)s "
|
||||
"(after %(tries)s rescans)"),
|
||||
{'name': self.device_name, 'tries': tries})
|
||||
|
||||
# see if the new drive is part of a multipath
|
||||
# device. If so, we'll use the multipath device.
|
||||
if self.use_multipath:
|
||||
mdev_info = self._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']
|
||||
devices = mdev_info['devices']
|
||||
device_info['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
|
||||
dev_info = self._linuxscsi.get_device_info(self.device_name)
|
||||
devices = [dev_info]
|
||||
else:
|
||||
device_path = self.host_device
|
||||
dev_info = self._linuxscsi.get_device_info(self.device_name)
|
||||
devices = [dev_info]
|
||||
|
||||
device_info['path'] = device_path
|
||||
device_info['devices'] = devices
|
||||
return device_info
|
||||
|
||||
@synchronized('connect_volume')
|
||||
def disconnect_volume(self, connection_properties, device_info):
|
||||
"""Detach the volume from instance_name.
|
||||
|
||||
connection_properties for Fibre Channel must include:
|
||||
target_wwn - iSCSI Qualified Name
|
||||
target_lun - LUN id of the volume
|
||||
"""
|
||||
devices = device_info['devices']
|
||||
|
||||
# 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 self.use_multipath and 'multipath_id' in device_info:
|
||||
multipath_id = device_info['multipath_id']
|
||||
mdev_info = self._linuxscsi.find_multipath_device(multipath_id)
|
||||
devices = mdev_info['devices']
|
||||
LOG.debug("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:
|
||||
self._linuxscsi.remove_scsi_device(device["device"])
|
||||
|
||||
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
|
||||
|
||||
136
cinder/brick/initiator/linuxfc.py
Normal file
136
cinder/brick/initiator/linuxfc.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# 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 Fibre Channel utilities."""
|
||||
|
||||
import errno
|
||||
import executor
|
||||
import linuxscsi
|
||||
|
||||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import processutils as putils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinuxFibreChannel(linuxscsi.LinuxSCSI):
|
||||
def __init__(self, execute=putils.execute, root_helper="sudo",
|
||||
*args, **kwargs):
|
||||
super(LinuxFibreChannel, self).__init__(execute, root_helper,
|
||||
*args, **kwargs)
|
||||
|
||||
def rescan_hosts(self, hbas):
|
||||
for hba in hbas:
|
||||
self.echo_scsi_command("/sys/class/scsi_host/%s/scan"
|
||||
% hba['host_device'], "- - -")
|
||||
|
||||
def get_fc_hbas(self):
|
||||
"""Get the Fibre Channel HBA information."""
|
||||
out = None
|
||||
try:
|
||||
out, err = self._execute('systool', '-c', 'fc_host', '-v',
|
||||
run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
except putils.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(_("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(_("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(self):
|
||||
"""Get Fibre Channel WWNs and device paths from the system, if any."""
|
||||
|
||||
# Note(walter-boring) modern linux kernels contain the FC HBA's in /sys
|
||||
# and are obtainable via the systool app
|
||||
hbas = self.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(self):
|
||||
"""Get Fibre Channel WWPNs from the system, if any."""
|
||||
|
||||
# Note(walter-boring) modern linux kernels contain the FC HBA's in /sys
|
||||
# and are obtainable via the systool app
|
||||
hbas = self.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(self):
|
||||
"""Get Fibre Channel WWNNs from the system, if any."""
|
||||
|
||||
# Note(walter-boring) modern linux kernels contain the FC HBA's in /sys
|
||||
# and are obtainable via the systool app
|
||||
hbas = self.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
|
||||
@@ -24,6 +24,7 @@ import os
|
||||
|
||||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder.openstack.common import processutils as putils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -61,6 +62,25 @@ class LinuxSCSI(executor.Executor):
|
||||
LOG.debug("Remove SCSI device(%s) with %s" % (device, path))
|
||||
self.echo_scsi_command(path, "1")
|
||||
|
||||
def get_device_info(self, device):
|
||||
(out, err) = self._execute('sg_scan', device, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
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 remove_multipath_device(self, multipath_name):
|
||||
"""This removes LUNs associated with a multipath device
|
||||
and the multipath device itself.
|
||||
@@ -104,7 +124,6 @@ class LinuxSCSI(executor.Executor):
|
||||
(out, err) = self._execute('multipath', '-l', device,
|
||||
run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
LOG.error("PISS = %s" % out)
|
||||
except putils.ProcessExecutionError as exc:
|
||||
LOG.warn(_("multipath call failed exit (%(code)s)")
|
||||
% {'code': exc.exit_code})
|
||||
|
||||
231
cinder/tests/brick/test_brick_connector.py
Normal file
231
cinder/tests/brick/test_brick_connector.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# 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.
|
||||
|
||||
import os.path
|
||||
import string
|
||||
|
||||
from cinder.brick.initiator import connector
|
||||
from cinder.brick.initiator import host_driver
|
||||
from cinder.brick.initiator import linuxfc
|
||||
from cinder.brick.initiator import linuxscsi
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import test
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectorTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ConnectorTestCase, self).setUp()
|
||||
self.cmds = []
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def test_connect_volume(self):
|
||||
self.connector = connector.InitiatorConnector()
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.connector.connect_volume, None)
|
||||
|
||||
def test_disconnect_volume(self):
|
||||
self.connector = connector.InitiatorConnector()
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.connector.connect_volume, None)
|
||||
|
||||
def test_factory(self):
|
||||
obj = connector.InitiatorConnector.factory('iscsi')
|
||||
self.assertTrue(obj.__class__.__name__,
|
||||
"ISCSIConnector")
|
||||
|
||||
obj = connector.InitiatorConnector.factory('fibre_channel')
|
||||
self.assertTrue(obj.__class__.__name__,
|
||||
"FibreChannelConnector")
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
connector.InitiatorConnector.factory,
|
||||
"bogus")
|
||||
|
||||
|
||||
class HostDriverTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(HostDriverTestCase, self).setUp()
|
||||
self.devlist = ['device1', 'device2']
|
||||
self.stubs.Set(os, 'listdir', lambda x: self.devlist)
|
||||
|
||||
def test_host_driver(self):
|
||||
expected = ['/dev/disk/by-path/' + dev for dev in self.devlist]
|
||||
driver = host_driver.HostDriver()
|
||||
actual = driver.get_all_block_devices()
|
||||
self.assertEquals(expected, actual)
|
||||
|
||||
|
||||
class ISCSIConnectorTestCase(ConnectorTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSIConnectorTestCase, self).setUp()
|
||||
self.connector = connector.ISCSIConnector(execute=self.fake_execute,
|
||||
use_multipath=False)
|
||||
self.stubs.Set(self.connector._linuxscsi,
|
||||
'get_name_from_path', lambda x: "/dev/sdb")
|
||||
|
||||
def tearDown(self):
|
||||
super(ISCSIConnectorTestCase, self).tearDown()
|
||||
|
||||
def iscsi_connection(self, volume, location, iqn):
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': {
|
||||
'volume_id': volume['id'],
|
||||
'target_portal': location,
|
||||
'target_iqn': iqn,
|
||||
'target_lun': 1,
|
||||
}
|
||||
}
|
||||
|
||||
@test.testtools.skipUnless(os.path.exists('/dev/disk/by-path'),
|
||||
'Test requires /dev/disk/by-path')
|
||||
def test_connect_volume(self):
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
location = '10.0.2.15:3260'
|
||||
name = 'volume-00000001'
|
||||
iqn = 'iqn.2010-10.org.openstack:%s' % name
|
||||
vol = {'id': 1, 'name': name}
|
||||
connection_info = self.iscsi_connection(vol, location, iqn)
|
||||
device = self.connector.connect_volume(connection_info['data'])
|
||||
dev_str = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location, iqn)
|
||||
self.assertEquals(device['type'], 'block')
|
||||
self.assertEquals(device['path'], dev_str)
|
||||
|
||||
self.connector.disconnect_volume(connection_info['data'], device)
|
||||
expected_commands = [('iscsiadm -m node -T %s -p %s' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m session'),
|
||||
('iscsiadm -m node -T %s -p %s --login' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --op update'
|
||||
' -n node.startup -v automatic' % (iqn,
|
||||
location)),
|
||||
('tee -a /sys/block/sdb/device/delete'),
|
||||
('iscsiadm -m node -T %s -p %s --op update'
|
||||
' -n node.startup -v manual' % (iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --logout' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --op delete' %
|
||||
(iqn, location)), ]
|
||||
LOG.debug("self.cmds = %s" % self.cmds)
|
||||
LOG.debug("expected = %s" % expected_commands)
|
||||
|
||||
self.assertEqual(expected_commands, self.cmds)
|
||||
|
||||
|
||||
class FibreChannelConnectorTestCase(ConnectorTestCase):
|
||||
def setUp(self):
|
||||
super(FibreChannelConnectorTestCase, self).setUp()
|
||||
self.connector = connector.FibreChannelConnector(
|
||||
execute=self.fake_execute, use_multipath=False)
|
||||
self.assertIsNotNone(self.connector)
|
||||
self.assertIsNotNone(self.connector._linuxfc)
|
||||
self.assertIsNotNone(self.connector._linuxscsi)
|
||||
|
||||
def fake_get_fc_hbas(self):
|
||||
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 fake_get_fc_hbas_info(self):
|
||||
hbas = self.fake_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 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_connect_volume(self):
|
||||
self.stubs.Set(self.connector._linuxfc, "get_fc_hbas",
|
||||
self.fake_get_fc_hbas)
|
||||
self.stubs.Set(self.connector._linuxfc, "get_fc_hbas_info",
|
||||
self.fake_get_fc_hbas_info)
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
self.stubs.Set(os.path, 'realpath', lambda x: '/dev/sdb')
|
||||
|
||||
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(self.connector._linuxscsi, 'find_multipath_device',
|
||||
lambda x: devices)
|
||||
self.stubs.Set(self.connector._linuxscsi, 'remove_scsi_device',
|
||||
lambda x: None)
|
||||
self.stubs.Set(self.connector._linuxscsi, 'get_device_info',
|
||||
lambda x: devices['devices'][0])
|
||||
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"
|
||||
device_info = self.connector.connect_volume(connection_info['data'])
|
||||
dev_str = '/dev/disk/by-path/pci-0000:05:00.2-fc-0x%s-lun-1' % wwn
|
||||
self.assertEquals(device_info['type'], 'block')
|
||||
self.assertEquals(device_info['path'], dev_str)
|
||||
|
||||
self.connector.disconnect_volume(connection_info['data'], device_info)
|
||||
expected_commands = []
|
||||
self.assertEqual(expected_commands, self.cmds)
|
||||
|
||||
self.stubs.Set(self.connector._linuxfc, 'get_fc_hbas',
|
||||
lambda: [])
|
||||
self.stubs.Set(self.connector._linuxfc, 'get_fc_hbas_info',
|
||||
lambda: [])
|
||||
self.assertRaises(exception.CinderException,
|
||||
self.connector.connect_volume,
|
||||
connection_info['data'])
|
||||
158
cinder/tests/brick/test_brick_linuxfc.py
Normal file
158
cinder/tests/brick/test_brick_linuxfc.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# 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.
|
||||
|
||||
import os.path
|
||||
import string
|
||||
|
||||
from cinder.brick.initiator import linuxfc
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import test
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinuxFCTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LinuxFCTestCase, self).setUp()
|
||||
self.cmds = []
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
self.lfc = linuxfc.LinuxFibreChannel(execute=self.fake_execute)
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def test_rescan_hosts(self):
|
||||
hbas = [{'host_device': 'foo'},
|
||||
{'host_device': 'bar'}, ]
|
||||
self.lfc.rescan_hosts(hbas)
|
||||
expected_commands = ['tee -a /sys/class/scsi_host/foo/scan',
|
||||
'tee -a /sys/class/scsi_host/bar/scan']
|
||||
self.assertEquals(expected_commands, self.cmds)
|
||||
|
||||
def test_get_fc_hbas(self):
|
||||
def fake_exec(a, b, c, d, run_as_root=True, root_helper='sudo'):
|
||||
return SYSTOOL_FC, None
|
||||
self.stubs.Set(self.lfc, "_execute", fake_exec)
|
||||
hbas = self.lfc.get_fc_hbas()
|
||||
self.assertEquals(2, len(hbas))
|
||||
hba1 = hbas[0]
|
||||
self.assertEquals(hba1["ClassDevice"], "host0")
|
||||
hba2 = hbas[1]
|
||||
self.assertEquals(hba2["ClassDevice"], "host2")
|
||||
|
||||
def test_get_fc_hbas_info(self):
|
||||
def fake_exec(a, b, c, d, run_as_root=True, root_helper='sudo'):
|
||||
return SYSTOOL_FC, None
|
||||
self.stubs.Set(self.lfc, "_execute", fake_exec)
|
||||
hbas_info = self.lfc.get_fc_hbas_info()
|
||||
expected_info = [{'device_path': '/sys/devices/pci0000:20/'
|
||||
'0000:20:03.0/0000:21:00.0/'
|
||||
'host0/fc_host/host0',
|
||||
'host_device': 'host0',
|
||||
'node_name': '50014380242b9751',
|
||||
'port_name': '50014380242b9750'},
|
||||
{'device_path': '/sys/devices/pci0000:20/'
|
||||
'0000:20:03.0/0000:21:00.1/'
|
||||
'host2/fc_host/host2',
|
||||
'host_device': 'host2',
|
||||
'node_name': '50014380242b9753',
|
||||
'port_name': '50014380242b9752'}, ]
|
||||
self.assertEquals(expected_info, hbas_info)
|
||||
|
||||
def test_get_fc_wwpns(self):
|
||||
def fake_exec(a, b, c, d, run_as_root=True, root_helper='sudo'):
|
||||
return SYSTOOL_FC, None
|
||||
self.stubs.Set(self.lfc, "_execute", fake_exec)
|
||||
wwpns = self.lfc.get_fc_wwpns()
|
||||
expected_wwpns = ['50014380242b9750', '50014380242b9752']
|
||||
self.assertEquals(expected_wwpns, wwpns)
|
||||
|
||||
def test_get_fc_wwnns(self):
|
||||
def fake_exec(a, b, c, d, run_as_root=True, root_helper='sudo'):
|
||||
return SYSTOOL_FC, None
|
||||
self.stubs.Set(self.lfc, "_execute", fake_exec)
|
||||
wwnns = self.lfc.get_fc_wwpns()
|
||||
expected_wwnns = ['50014380242b9750', '50014380242b9752']
|
||||
self.assertEquals(expected_wwnns, wwnns)
|
||||
|
||||
SYSTOOL_FC = """
|
||||
Class = "fc_host"
|
||||
|
||||
Class Device = "host0"
|
||||
Class Device path = "/sys/devices/pci0000:20/0000:20:03.0/\
|
||||
0000:21:00.0/host0/fc_host/host0"
|
||||
dev_loss_tmo = "16"
|
||||
fabric_name = "0x100000051ea338b9"
|
||||
issue_lip = <store method only>
|
||||
max_npiv_vports = "0"
|
||||
node_name = "0x50014380242b9751"
|
||||
npiv_vports_inuse = "0"
|
||||
port_id = "0x960d0d"
|
||||
port_name = "0x50014380242b9750"
|
||||
port_state = "Online"
|
||||
port_type = "NPort (fabric via point-to-point)"
|
||||
speed = "8 Gbit"
|
||||
supported_classes = "Class 3"
|
||||
supported_speeds = "1 Gbit, 2 Gbit, 4 Gbit, 8 Gbit"
|
||||
symbolic_name = "QMH2572 FW:v4.04.04 DVR:v8.03.07.12-k"
|
||||
system_hostname = ""
|
||||
tgtid_bind_type = "wwpn (World Wide Port Name)"
|
||||
uevent =
|
||||
vport_create = <store method only>
|
||||
vport_delete = <store method only>
|
||||
|
||||
Device = "host0"
|
||||
Device path = "/sys/devices/pci0000:20/0000:20:03.0/0000:21:00.0/host0"
|
||||
edc = <store method only>
|
||||
optrom_ctl = <store method only>
|
||||
reset = <store method only>
|
||||
uevent = "DEVTYPE=scsi_host"
|
||||
|
||||
|
||||
Class Device = "host2"
|
||||
Class Device path = "/sys/devices/pci0000:20/0000:20:03.0/\
|
||||
0000:21:00.1/host2/fc_host/host2"
|
||||
dev_loss_tmo = "16"
|
||||
fabric_name = "0x100000051ea33b79"
|
||||
issue_lip = <store method only>
|
||||
max_npiv_vports = "0"
|
||||
node_name = "0x50014380242b9753"
|
||||
npiv_vports_inuse = "0"
|
||||
port_id = "0x970e09"
|
||||
port_name = "0x50014380242b9752"
|
||||
port_state = "Online"
|
||||
port_type = "NPort (fabric via point-to-point)"
|
||||
speed = "8 Gbit"
|
||||
supported_classes = "Class 3"
|
||||
supported_speeds = "1 Gbit, 2 Gbit, 4 Gbit, 8 Gbit"
|
||||
symbolic_name = "QMH2572 FW:v4.04.04 DVR:v8.03.07.12-k"
|
||||
system_hostname = ""
|
||||
tgtid_bind_type = "wwpn (World Wide Port Name)"
|
||||
uevent =
|
||||
vport_create = <store method only>
|
||||
vport_delete = <store method only>
|
||||
|
||||
Device = "host2"
|
||||
Device path = "/sys/devices/pci0000:20/0000:20:03.0/0000:21:00.1/host2"
|
||||
edc = <store method only>
|
||||
optrom_ctl = <store method only>
|
||||
reset = <store method only>
|
||||
uevent = "DEVTYPE=scsi_host"
|
||||
|
||||
|
||||
"""
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Red Hat, Inc.
|
||||
# (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
|
||||
@@ -17,8 +17,6 @@
|
||||
import os.path
|
||||
import string
|
||||
|
||||
from cinder.brick.initiator import connector
|
||||
from cinder.brick.initiator import host_driver
|
||||
from cinder.brick.initiator import linuxscsi
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import test
|
||||
@@ -26,45 +24,6 @@ from cinder import test
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectorTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ConnectorTestCase, self).setUp()
|
||||
self.cmds = []
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
|
||||
def fake_init(obj):
|
||||
return
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def test_connect_volume(self):
|
||||
self.connector = connector.InitiatorConnector()
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.connector.connect_volume, None)
|
||||
|
||||
def test_disconnect_volume(self):
|
||||
self.connector = connector.InitiatorConnector()
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.connector.connect_volume, None)
|
||||
|
||||
|
||||
class HostDriverTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(HostDriverTestCase, self).setUp()
|
||||
self.devlist = ['device1', 'device2']
|
||||
self.stubs.Set(os, 'listdir', lambda x: self.devlist)
|
||||
|
||||
def test_host_driver(self):
|
||||
expected = ['/dev/disk/by-path/' + dev for dev in self.devlist]
|
||||
driver = host_driver.HostDriver()
|
||||
actual = driver.get_all_block_devices()
|
||||
self.assertEquals(expected, actual)
|
||||
|
||||
|
||||
class LinuxSCSITestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(LinuxSCSITestCase, self).setUp()
|
||||
@@ -76,6 +35,11 @@ class LinuxSCSITestCase(test.TestCase):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def test_echo_scsi_command(self):
|
||||
self.linuxscsi.echo_scsi_command("/some/path", "1")
|
||||
expected_commands = ['tee -a /some/path']
|
||||
self.assertEquals(expected_commands, self.cmds)
|
||||
|
||||
def test_get_name_from_path(self):
|
||||
device_name = "/dev/sdc"
|
||||
self.stubs.Set(os.path, 'realpath', lambda x: device_name)
|
||||
@@ -223,62 +187,3 @@ class LinuxSCSITestCase(test.TestCase):
|
||||
self.assertEqual("1", info['devices'][1]['channel'])
|
||||
self.assertEqual("0", info['devices'][1]['id'])
|
||||
self.assertEqual("3", info['devices'][1]['lun'])
|
||||
|
||||
|
||||
class ISCSIConnectorTestCase(ConnectorTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSIConnectorTestCase, self).setUp()
|
||||
self.connector = connector.ISCSIConnector(execute=self.fake_execute)
|
||||
self.stubs.Set(self.connector._linuxscsi,
|
||||
'get_name_from_path',
|
||||
lambda x: "/dev/sdb")
|
||||
|
||||
def tearDown(self):
|
||||
super(ISCSIConnectorTestCase, self).tearDown()
|
||||
|
||||
def iscsi_connection(self, volume, location, iqn):
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': {
|
||||
'volume_id': volume['id'],
|
||||
'target_portal': location,
|
||||
'target_iqn': iqn,
|
||||
'target_lun': 1,
|
||||
}
|
||||
}
|
||||
|
||||
@test.testtools.skipUnless(os.path.exists('/dev/disk/by-path'),
|
||||
'Test requires /dev/disk/by-path')
|
||||
def test_connect_volume(self):
|
||||
self.stubs.Set(os.path, 'exists', lambda x: True)
|
||||
location = '10.0.2.15:3260'
|
||||
name = 'volume-00000001'
|
||||
iqn = 'iqn.2010-10.org.openstack:%s' % name
|
||||
vol = {'id': 1, 'name': name}
|
||||
connection_info = self.iscsi_connection(vol, location, iqn)
|
||||
conf = self.connector.connect_volume(connection_info['data'])
|
||||
dev_str = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location, iqn)
|
||||
self.assertEquals(conf['type'], 'block')
|
||||
self.assertEquals(conf['path'], dev_str)
|
||||
|
||||
self.connector.disconnect_volume(connection_info['data'])
|
||||
expected_commands = [('iscsiadm -m node -T %s -p %s' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m session'),
|
||||
('iscsiadm -m node -T %s -p %s --login' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --op update'
|
||||
' -n node.startup -v automatic' % (iqn,
|
||||
location)),
|
||||
('tee -a /sys/block/sdb/device/delete'),
|
||||
('iscsiadm -m node -T %s -p %s --op update'
|
||||
' -n node.startup -v manual' % (iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --logout' %
|
||||
(iqn, location)),
|
||||
('iscsiadm -m node -T %s -p %s --op delete' %
|
||||
(iqn, location)), ]
|
||||
LOG.debug("self.cmds = %s" % self.cmds)
|
||||
LOG.debug("expected = %s" % expected_commands)
|
||||
|
||||
self.assertEqual(expected_commands, self.cmds)
|
||||
@@ -58,7 +58,11 @@ volume_opts = [
|
||||
help='The port that the iSCSI daemon is listening on'),
|
||||
cfg.StrOpt('volume_backend_name',
|
||||
default=None,
|
||||
help='The backend name for a given driver implementation'), ]
|
||||
help='The backend name for a given driver implementation'),
|
||||
cfg.StrOpt('use_multipath_for_image_xfer',
|
||||
default=False,
|
||||
help='Do we attach/detach volumes in cinder using multipath '
|
||||
'for volume to image and image to volume transfers?'), ]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
@@ -188,11 +192,66 @@ class VolumeDriver(object):
|
||||
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
||||
"""Fetch the image from image_service and write it to the volume."""
|
||||
raise NotImplementedError()
|
||||
LOG.debug(_('copy_image_to_volume %s.') % volume['name'])
|
||||
|
||||
properties = initiator.get_connector_properties()
|
||||
connection, device, connector = self._attach_volume(context, volume,
|
||||
properties)
|
||||
|
||||
try:
|
||||
image_utils.fetch_to_raw(context,
|
||||
image_service,
|
||||
image_id,
|
||||
device['path'])
|
||||
finally:
|
||||
self._detach_volume(connection, device, connector)
|
||||
self.terminate_connection(volume, properties)
|
||||
|
||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||
"""Copy the volume to the specified image."""
|
||||
raise NotImplementedError()
|
||||
LOG.debug(_('copy_volume_to_image %s.') % volume['name'])
|
||||
|
||||
properties = initiator.get_connector_properties()
|
||||
connection, device, connector = self._attach_volume(context, volume,
|
||||
properties)
|
||||
|
||||
try:
|
||||
image_utils.upload_volume(context,
|
||||
image_service,
|
||||
image_meta,
|
||||
device['path'])
|
||||
finally:
|
||||
self._detach_volume(connection, device, connector)
|
||||
self.terminate_connection(volume, properties)
|
||||
|
||||
def _attach_volume(self, context, volume, properties):
|
||||
"""Attach the volume."""
|
||||
host_device = None
|
||||
conn = self.initialize_connection(volume, properties)
|
||||
|
||||
# Use Brick's code to do attach/detach
|
||||
use_multipath = self.configuration.use_multipath_for_image_xfer
|
||||
protocol = conn['driver_volume_type']
|
||||
connector = initiator.InitiatorConnector.factory(protocol,
|
||||
use_multipath=
|
||||
use_multipath)
|
||||
device = connector.connect_volume(conn['data'])
|
||||
host_device = device['path']
|
||||
|
||||
if not connector.check_valid_device(host_device):
|
||||
raise exception.DeviceUnavailable(path=host_device,
|
||||
reason=(_("Unable to access "
|
||||
"the backend storage "
|
||||
"via the path "
|
||||
"%(path)s.") %
|
||||
{'path': host_device}))
|
||||
return conn, device, connector
|
||||
|
||||
def _detach_volume(self, connection, device, connector):
|
||||
"""Disconnect the volume from the host."""
|
||||
protocol = connection['driver_volume_type']
|
||||
# Use Brick's code to do attach/detach
|
||||
connector.disconnect_volume(connection['data'], device)
|
||||
|
||||
def clone_image(self, volume, image_location):
|
||||
"""Create a volume efficiently from an existing image.
|
||||
@@ -397,22 +456,6 @@ class ISCSIDriver(VolumeDriver):
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
||||
|
||||
def _check_valid_device(self, path):
|
||||
cmd = ('dd', 'if=%(path)s' % {"path": path},
|
||||
'of=/dev/null', 'count=1')
|
||||
out, info = None, None
|
||||
try:
|
||||
out, info = self._execute(*cmd, run_as_root=True)
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.error(_("Failed to access the device on the path "
|
||||
"%(path)s: %(error)s.") %
|
||||
{"path": path, "error": e.stderr})
|
||||
return False
|
||||
# If the info is none, the path does not exist.
|
||||
if info is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_iscsi_initiator(self):
|
||||
"""Get iscsi initiator name for this machine"""
|
||||
# NOTE openiscsi stores initiator name in a file that
|
||||
@@ -422,74 +465,6 @@ class ISCSIDriver(VolumeDriver):
|
||||
if l.startswith('InitiatorName='):
|
||||
return l[l.index('=') + 1:].strip()
|
||||
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
||||
"""Fetch the image from image_service and write it to the volume."""
|
||||
LOG.debug(_('copy_image_to_volume %s.') % volume['name'])
|
||||
connector = {'initiator': self._get_iscsi_initiator(),
|
||||
'host': socket.gethostname()}
|
||||
|
||||
iscsi_properties, volume_path = self._attach_volume(
|
||||
context, volume, connector)
|
||||
|
||||
try:
|
||||
image_utils.fetch_to_raw(context,
|
||||
image_service,
|
||||
image_id,
|
||||
volume_path)
|
||||
finally:
|
||||
self._detach_volume(iscsi_properties)
|
||||
self.terminate_connection(volume, connector)
|
||||
|
||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||
"""Copy the volume to the specified image."""
|
||||
LOG.debug(_('copy_volume_to_image %s.') % volume['name'])
|
||||
connector = {'initiator': self._get_iscsi_initiator(),
|
||||
'host': socket.gethostname()}
|
||||
|
||||
iscsi_properties, volume_path = self._attach_volume(
|
||||
context, volume, connector)
|
||||
|
||||
try:
|
||||
image_utils.upload_volume(context,
|
||||
image_service,
|
||||
image_meta,
|
||||
volume_path)
|
||||
finally:
|
||||
self._detach_volume(iscsi_properties)
|
||||
self.terminate_connection(volume, connector)
|
||||
|
||||
def _attach_volume(self, context, volume, connector):
|
||||
"""Attach the volume."""
|
||||
iscsi_properties = None
|
||||
host_device = None
|
||||
init_conn = self.initialize_connection(volume, connector)
|
||||
iscsi_properties = init_conn['data']
|
||||
|
||||
# Use Brick's code to do attach/detach
|
||||
iscsi = initiator.ISCSIConnector()
|
||||
conf = iscsi.connect_volume(iscsi_properties)
|
||||
|
||||
host_device = conf['path']
|
||||
|
||||
if not self._check_valid_device(host_device):
|
||||
raise exception.DeviceUnavailable(path=host_device,
|
||||
reason=(_("Unable to access "
|
||||
"the backend storage "
|
||||
"via the path "
|
||||
"%(path)s.") %
|
||||
{'path': host_device}))
|
||||
LOG.debug("Volume attached %s" % host_device)
|
||||
return iscsi_properties, host_device
|
||||
|
||||
def _detach_volume(self, iscsi_properties):
|
||||
LOG.debug("Detach volume %s:%s:%s" %
|
||||
(iscsi_properties["target_portal"],
|
||||
iscsi_properties["target_iqn"],
|
||||
iscsi_properties["target_lun"]))
|
||||
# Use Brick's code to do attach/detach
|
||||
iscsi = initiator.ISCSIConnector()
|
||||
conf = iscsi.disconnect_volume(iscsi_properties)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume status.
|
||||
|
||||
@@ -586,9 +561,3 @@ class FibreChannelDriver(VolumeDriver):
|
||||
"""
|
||||
msg = _("Driver must implement initialize_connection")
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -855,6 +855,11 @@
|
||||
# value)
|
||||
#volume_backend_name=<None>
|
||||
|
||||
# Do we attach/detach volumes in cinder using multipath
|
||||
# for volume to image and image to volume transfers?
|
||||
# (boolean value)
|
||||
#use_multipath_for_image_xfer=False
|
||||
|
||||
|
||||
#
|
||||
# Options defined in cinder.volume.drivers.block_device
|
||||
|
||||
@@ -61,6 +61,7 @@ hus_cmd: CommandFilter, hus_cmd, root
|
||||
ls: CommandFilter, ls, root
|
||||
tee: CommandFilter, tee, root
|
||||
multipath: CommandFilter, multipath, root
|
||||
systool: CommandFilter, systool, root
|
||||
|
||||
# cinder/volume/drivers/block_device.py
|
||||
blockdev: CommandFilter, blockdev, root
|
||||
|
||||
Reference in New Issue
Block a user