Merge "Remove duplication of ISCSIDriver in ISERDriver"
This commit is contained in:
commit
7a995b2e03
@ -91,6 +91,13 @@ class InitiatorConnector(executor.Executor):
|
||||
use_multipath=use_multipath,
|
||||
device_scan_attempts=device_scan_attempts,
|
||||
*args, **kwargs)
|
||||
elif protocol == "ISER":
|
||||
return ISERConnector(root_helper=root_helper,
|
||||
driver=driver,
|
||||
execute=execute,
|
||||
use_multipath=use_multipath,
|
||||
device_scan_attempts=device_scan_attempts,
|
||||
*args, **kwargs)
|
||||
elif protocol == "FIBRE_CHANNEL":
|
||||
return FibreChannelConnector(root_helper=root_helper,
|
||||
driver=driver,
|
||||
@ -501,6 +508,15 @@ class ISCSIConnector(InitiatorConnector):
|
||||
self._run_multipath('-r', check_exit_code=[0, 1, 21])
|
||||
|
||||
|
||||
class ISERConnector(ISCSIConnector):
|
||||
|
||||
def _get_device_path(self, iser_properties):
|
||||
return ("/dev/disk/by-path/ip-%s-iser-%s-lun-%s" %
|
||||
(iser_properties['target_portal'],
|
||||
iser_properties['target_iqn'],
|
||||
iser_properties.get('target_lun', 0)))
|
||||
|
||||
|
||||
class FibreChannelConnector(InitiatorConnector):
|
||||
"""Connector class to attach/detach Fibre Channel volumes."""
|
||||
|
||||
|
@ -2278,6 +2278,16 @@ class LVMVolumeDriverTestCase(DriverTestCase):
|
||||
class ISCSITestCase(DriverTestCase):
|
||||
"""Test Case for ISCSIDriver"""
|
||||
driver_name = "cinder.volume.drivers.lvm.LVMISCSIDriver"
|
||||
base_driver = driver.ISCSIDriver
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSITestCase, self).setUp()
|
||||
self.configuration = mox.MockObject(conf.Configuration)
|
||||
self.configuration.num_iscsi_scan_tries = 3
|
||||
self.configuration.iscsi_num_targets = 100
|
||||
self.configuration.iscsi_target_prefix = 'iqn.2010-10.org.openstack:'
|
||||
self.configuration.iscsi_ip_address = '0.0.0.0'
|
||||
self.configuration.iscsi_port = 3260
|
||||
|
||||
def _attach_volume(self):
|
||||
"""Attach volumes to an instance."""
|
||||
@ -2299,11 +2309,8 @@ class ISCSITestCase(DriverTestCase):
|
||||
return volume_id_list
|
||||
|
||||
def test_do_iscsi_discovery(self):
|
||||
configuration = mox.MockObject(conf.Configuration)
|
||||
configuration.iscsi_ip_address = '0.0.0.0'
|
||||
configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
iscsi_driver = driver.ISCSIDriver(configuration=configuration)
|
||||
self.configuration.append_config_values(mox.IgnoreArg())
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
iscsi_driver._execute = lambda *a, **kw: \
|
||||
("%s dummy" % CONF.iscsi_ip_address, '')
|
||||
volume = {"name": "dummy",
|
||||
@ -2315,7 +2322,7 @@ class ISCSITestCase(DriverTestCase):
|
||||
"id": "0",
|
||||
"provider_auth": "a b c",
|
||||
"attached_mode": "rw"}
|
||||
iscsi_driver = driver.ISCSIDriver()
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
iscsi_driver._do_iscsi_discovery = lambda v: "0.0.0.0:0000,0 iqn:iqn 0"
|
||||
result = iscsi_driver._get_iscsi_properties(volume)
|
||||
self.assertEqual(result["target_portal"], "0.0.0.0:0000")
|
||||
@ -2349,7 +2356,7 @@ class ISCSITestCase(DriverTestCase):
|
||||
self.assertEqual(stats['free_capacity_gb'], float('0.52'))
|
||||
|
||||
def test_validate_connector(self):
|
||||
iscsi_driver = driver.ISCSIDriver()
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
# Validate a valid connector
|
||||
connector = {'ip': '10.0.0.2',
|
||||
'host': 'fakehost',
|
||||
@ -2365,29 +2372,16 @@ class ISCSITestCase(DriverTestCase):
|
||||
class ISERTestCase(ISCSITestCase):
|
||||
"""Test Case for ISERDriver."""
|
||||
driver_name = "cinder.volume.drivers.lvm.LVMISERDriver"
|
||||
base_driver = driver.ISERDriver
|
||||
|
||||
def test_do_iscsi_discovery(self):
|
||||
configuration = mox.MockObject(conf.Configuration)
|
||||
configuration.iser_ip_address = '0.0.0.0'
|
||||
configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
iser_driver = driver.ISERDriver(configuration=configuration)
|
||||
iser_driver._execute = lambda *a, **kw: \
|
||||
("%s dummy" % CONF.iser_ip_address, '')
|
||||
volume = {"name": "dummy",
|
||||
"host": "0.0.0.0"}
|
||||
iser_driver._do_iser_discovery(volume)
|
||||
|
||||
def test_get_iscsi_properties(self):
|
||||
volume = {"provider_location": '',
|
||||
"id": "0",
|
||||
"provider_auth": "a b c"}
|
||||
iser_driver = driver.ISERDriver()
|
||||
iser_driver._do_iser_discovery = lambda v: "0.0.0.0:0000,0 iqn:iqn 0"
|
||||
result = iser_driver._get_iser_properties(volume)
|
||||
self.assertEqual(result["target_portal"], "0.0.0.0:0000")
|
||||
self.assertEqual(result["target_iqn"], "iqn:iqn")
|
||||
self.assertEqual(result["target_lun"], 0)
|
||||
def setUp(self):
|
||||
super(ISERTestCase, self).setUp()
|
||||
self.configuration = mox.MockObject(conf.Configuration)
|
||||
self.configuration.num_iser_scan_tries = 3
|
||||
self.configuration.iser_num_targets = 100
|
||||
self.configuration.iser_target_prefix = 'iqn.2010-10.org.openstack:'
|
||||
self.configuration.iser_ip_address = '0.0.0.0'
|
||||
self.configuration.iser_port = 3260
|
||||
|
||||
|
||||
class FibreChannelTestCase(DriverTestCase):
|
||||
|
@ -62,25 +62,6 @@ volume_opts = [
|
||||
default=3,
|
||||
help='The maximum number of times to rescan targets'
|
||||
' to find volume'),
|
||||
cfg.IntOpt('num_iser_scan_tries',
|
||||
default=3,
|
||||
help='The maximum number of times to rescan iSER target '
|
||||
'to find volume'),
|
||||
cfg.IntOpt('iser_num_targets',
|
||||
default=100,
|
||||
help='The maximum number of iser target ids per host'),
|
||||
cfg.StrOpt('iser_target_prefix',
|
||||
default='iqn.2010-10.org.iser.openstack:',
|
||||
help='prefix for iser volumes'),
|
||||
cfg.StrOpt('iser_ip_address',
|
||||
default='$my_ip',
|
||||
help='The IP address that the iSER daemon is listening on'),
|
||||
cfg.IntOpt('iser_port',
|
||||
default=3260,
|
||||
help='The port that the iSER daemon is listening on'),
|
||||
cfg.StrOpt('iser_helper',
|
||||
default='tgtadm',
|
||||
help='iser target user-land tool to use'),
|
||||
cfg.StrOpt('volume_backend_name',
|
||||
default=None,
|
||||
help='The backend name for a given driver implementation'),
|
||||
@ -117,9 +98,33 @@ volume_opts = [
|
||||
'optionally, auto can be set and Cinder '
|
||||
'will autodetect type of backing device'))]
|
||||
|
||||
# for backward compatibility
|
||||
iser_opts = [
|
||||
cfg.IntOpt('num_iser_scan_tries',
|
||||
default=3,
|
||||
help='The maximum number of times to rescan iSER target'
|
||||
'to find volume'),
|
||||
cfg.IntOpt('iser_num_targets',
|
||||
default=100,
|
||||
help='The maximum number of iser target ids per host'),
|
||||
cfg.StrOpt('iser_target_prefix',
|
||||
default='iqn.2010-10.org.iser.openstack:',
|
||||
help='prefix for iser volumes'),
|
||||
cfg.StrOpt('iser_ip_address',
|
||||
default='$my_ip',
|
||||
help='The IP address that the iSER daemon is listening on'),
|
||||
cfg.IntOpt('iser_port',
|
||||
default=3260,
|
||||
help='The port that the iSER daemon is listening on'),
|
||||
cfg.StrOpt('iser_helper',
|
||||
default='tgtadm',
|
||||
help='iser target user-land tool to use'),
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
CONF.register_opts(iser_opts)
|
||||
|
||||
|
||||
class VolumeDriver(object):
|
||||
@ -751,91 +756,18 @@ class ISERDriver(ISCSIDriver):
|
||||
'<auth method> <auth username> <auth password>'.
|
||||
`CHAP` is the only auth_method in use at the moment.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ISERDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def _do_iser_discovery(self, volume):
|
||||
LOG.warn(_("ISER provider_location not stored, using discovery"))
|
||||
|
||||
volume_name = volume['name']
|
||||
|
||||
(out, _err) = self._execute('iscsiadm', '-m', 'discovery',
|
||||
'-t', 'sendtargets', '-p', volume['host'],
|
||||
run_as_root=True)
|
||||
for target in out.splitlines():
|
||||
if (self.configuration.iser_ip_address in target
|
||||
and volume_name in target):
|
||||
return target
|
||||
return None
|
||||
|
||||
def _get_iser_properties(self, volume):
|
||||
"""Gets iser configuration
|
||||
|
||||
We ideally get saved information in the volume entity, but fall back
|
||||
to discovery if need be. Discovery may be completely removed in future
|
||||
The properties are:
|
||||
|
||||
:target_discovered: boolean indicating whether discovery was used
|
||||
|
||||
:target_iqn: the IQN of the iSER target
|
||||
|
||||
:target_portal: the portal of the iSER target
|
||||
|
||||
:target_lun: the lun of the iSER target
|
||||
|
||||
:volume_id: the id of the volume (currently used by xen)
|
||||
|
||||
:auth_method:, :auth_username:, :auth_password:
|
||||
|
||||
the authentication details. Right now, either auth_method is not
|
||||
present meaning no authentication, or auth_method == `CHAP`
|
||||
meaning use CHAP with the specified credentials.
|
||||
"""
|
||||
|
||||
properties = {}
|
||||
|
||||
location = volume['provider_location']
|
||||
|
||||
if location:
|
||||
# provider_location is the same format as iSER discovery output
|
||||
properties['target_discovered'] = False
|
||||
else:
|
||||
location = self._do_iser_discovery(volume)
|
||||
|
||||
if not location:
|
||||
msg = (_("Could not find iSER export for volume %s") %
|
||||
(volume['name']))
|
||||
raise exception.InvalidVolume(reason=msg)
|
||||
|
||||
LOG.debug(_("ISER Discovery: Found %s") % (location))
|
||||
properties['target_discovered'] = True
|
||||
|
||||
results = location.split(" ")
|
||||
properties['target_portal'] = results[0].split(",")[0]
|
||||
properties['target_iqn'] = results[1]
|
||||
try:
|
||||
properties['target_lun'] = int(results[2])
|
||||
except (IndexError, ValueError):
|
||||
if (self.configuration.volume_driver in
|
||||
['cinder.volume.drivers.lvm.LVMISERDriver',
|
||||
'cinder.volume.drivers.lvm.ThinLVMVolumeDriver'] and
|
||||
self.configuration.iser_helper == 'tgtadm'):
|
||||
properties['target_lun'] = 1
|
||||
else:
|
||||
properties['target_lun'] = 0
|
||||
|
||||
properties['volume_id'] = volume['id']
|
||||
|
||||
auth = volume['provider_auth']
|
||||
if auth:
|
||||
(auth_method, auth_username, auth_secret) = auth.split()
|
||||
|
||||
properties['auth_method'] = auth_method
|
||||
properties['auth_username'] = auth_username
|
||||
properties['auth_password'] = auth_secret
|
||||
|
||||
return properties
|
||||
# for backward compatibility
|
||||
self.configuration.num_iscsi_scan_tries = \
|
||||
self.configuration.num_iser_scan_tries
|
||||
self.configuration.iscsi_num_targets = \
|
||||
self.configuration.iser_num_targets
|
||||
self.configuration.iscsi_target_prefix = \
|
||||
self.configuration.iser_target_prefix
|
||||
self.configuration.iscsi_ip_address = \
|
||||
self.configuration.iser_ip_address
|
||||
self.configuration.iser_port = self.configuration.iser_port
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Initializes the connection and returns connection info.
|
||||
@ -857,123 +789,12 @@ class ISERDriver(ISCSIDriver):
|
||||
|
||||
"""
|
||||
|
||||
iser_properties = self._get_iser_properties(volume)
|
||||
iser_properties = self._get_iscsi_properties(volume)
|
||||
return {
|
||||
'driver_volume_type': 'iser',
|
||||
'data': iser_properties
|
||||
}
|
||||
|
||||
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 processutils.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 _attach_volume(self, context, volume, connector):
|
||||
"""Attach the volume."""
|
||||
iser_properties = None
|
||||
host_device = None
|
||||
init_conn = self.initialize_connection(volume, connector)
|
||||
iser_properties = init_conn['data']
|
||||
|
||||
# code "inspired by" nova/virt/libvirt/volume.py
|
||||
try:
|
||||
self._run_iscsiadm(iser_properties, ())
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
# iscsiadm returns 21 for "No records found" after version 2.0-871
|
||||
if exc.exit_code in [21, 255]:
|
||||
self._run_iscsiadm(iser_properties, ('--op', 'new'))
|
||||
else:
|
||||
raise
|
||||
|
||||
if iser_properties.get('auth_method'):
|
||||
self._iscsiadm_update(iser_properties,
|
||||
"node.session.auth.authmethod",
|
||||
iser_properties['auth_method'])
|
||||
self._iscsiadm_update(iser_properties,
|
||||
"node.session.auth.username",
|
||||
iser_properties['auth_username'])
|
||||
self._iscsiadm_update(iser_properties,
|
||||
"node.session.auth.password",
|
||||
iser_properties['auth_password'])
|
||||
|
||||
host_device = ("/dev/disk/by-path/ip-%s-iser-%s-lun-%s" %
|
||||
(iser_properties['target_portal'],
|
||||
iser_properties['target_iqn'],
|
||||
iser_properties.get('target_lun', 0)))
|
||||
|
||||
out = self._run_iscsiadm_bare(["-m", "session"],
|
||||
run_as_root=True,
|
||||
check_exit_code=[0, 1, 21])[0] or ""
|
||||
|
||||
portals = [{'portal': p.split(" ")[2], 'iqn': p.split(" ")[3]}
|
||||
for p in out.splitlines() if p.startswith("iser:")]
|
||||
|
||||
stripped_portal = iser_properties['target_portal'].split(",")[0]
|
||||
length_iqn = [s for s in portals
|
||||
if stripped_portal ==
|
||||
s['portal'].split(",")[0] and
|
||||
s['iqn'] == iser_properties['target_iqn']]
|
||||
if len(portals) == 0 or len(length_iqn) == 0:
|
||||
try:
|
||||
self._run_iscsiadm(iser_properties, ("--login",),
|
||||
check_exit_code=[0, 255])
|
||||
except processutils.ProcessExecutionError as err:
|
||||
if err.exit_code in [15]:
|
||||
self._iscsiadm_update(iser_properties,
|
||||
"node.startup",
|
||||
"automatic")
|
||||
return iser_properties, host_device
|
||||
else:
|
||||
raise
|
||||
|
||||
self._iscsiadm_update(iser_properties,
|
||||
"node.startup", "automatic")
|
||||
|
||||
tries = 0
|
||||
while not os.path.exists(host_device):
|
||||
if tries >= self.configuration.num_iser_scan_tries:
|
||||
raise exception.CinderException(_("iSER device "
|
||||
"not found "
|
||||
"at %s") % (host_device))
|
||||
|
||||
LOG.warn(_("ISER volume not yet found at: %(host_device)s. "
|
||||
"Will rescan & retry. Try number: %(tries)s.") %
|
||||
{'host_device': host_device, 'tries': tries})
|
||||
|
||||
# The rescan isn't documented as being necessary(?),
|
||||
# but it helps
|
||||
self._run_iscsiadm(iser_properties, ("--rescan",))
|
||||
|
||||
tries = tries + 1
|
||||
if not os.path.exists(host_device):
|
||||
time.sleep(tries ** 2)
|
||||
|
||||
if tries != 0:
|
||||
LOG.debug(_("Found iSER node %(host_device)s "
|
||||
"(after %(tries)s rescans).") %
|
||||
{'host_device': host_device,
|
||||
'tries': tries})
|
||||
|
||||
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}))
|
||||
return iser_properties, host_device
|
||||
|
||||
def _update_volume_status(self):
|
||||
"""Retrieve status info from volume group."""
|
||||
|
||||
|
@ -1011,6 +1011,28 @@
|
||||
# Options defined in cinder.volume.driver
|
||||
#
|
||||
|
||||
# The maximum number of times to rescan iSER targetto find
|
||||
# volume (integer value)
|
||||
#num_iser_scan_tries=3
|
||||
|
||||
# The maximum number of iser target ids per host (integer
|
||||
# value)
|
||||
#iser_num_targets=100
|
||||
|
||||
# prefix for iser volumes (string value)
|
||||
#iser_target_prefix=iqn.2010-10.org.iser.openstack:
|
||||
|
||||
# The IP address that the iSER daemon is listening on (string
|
||||
# value)
|
||||
#iser_ip_address=$my_ip
|
||||
|
||||
# The port that the iSER daemon is listening on (integer
|
||||
# value)
|
||||
#iser_port=3260
|
||||
|
||||
# iser target user-land tool to use (string value)
|
||||
#iser_helper=tgtadm
|
||||
|
||||
# number of times to attempt to run flakey shell commands
|
||||
# (integer value)
|
||||
#num_shell_tries=3
|
||||
@ -1038,28 +1060,6 @@
|
||||
# (integer value)
|
||||
#num_volume_device_scan_tries=3
|
||||
|
||||
# The maximum number of times to rescan iSER target to find
|
||||
# volume (integer value)
|
||||
#num_iser_scan_tries=3
|
||||
|
||||
# The maximum number of iser target ids per host (integer
|
||||
# value)
|
||||
#iser_num_targets=100
|
||||
|
||||
# prefix for iser volumes (string value)
|
||||
#iser_target_prefix=iqn.2010-10.org.iser.openstack:
|
||||
|
||||
# The IP address that the iSER daemon is listening on (string
|
||||
# value)
|
||||
#iser_ip_address=$my_ip
|
||||
|
||||
# The port that the iSER daemon is listening on (integer
|
||||
# value)
|
||||
#iser_port=3260
|
||||
|
||||
# iser target user-land tool to use (string value)
|
||||
#iser_helper=tgtadm
|
||||
|
||||
# The backend name for a given driver implementation (string
|
||||
# value)
|
||||
#volume_backend_name=<None>
|
||||
|
Loading…
Reference in New Issue
Block a user