Merge "Remove duplication of ISCSIDriver in ISERDriver"

This commit is contained in:
Jenkins 2013-11-13 09:06:29 +00:00 committed by Gerrit Code Review
commit 7a995b2e03
4 changed files with 96 additions and 265 deletions

View File

@ -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."""

View File

@ -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):

View File

@ -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."""

View File

@ -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>