From f66de80e38bcfbc29228ea19ffc96fbf34aad0ba Mon Sep 17 00:00:00 2001 From: Ann Kamyshnikova Date: Fri, 6 Sep 2013 12:04:20 +0400 Subject: [PATCH] Remove duplication of ISCSIDriver in ISERDriver ISERDriver is almost copy of ISCSIDriver. Change-Id: I22394c7a3b26b140b19f76e052c98d11ec59145c --- cinder/brick/initiator/connector.py | 16 ++ cinder/tests/test_volume.py | 52 +++--- cinder/volume/driver.py | 249 ++++------------------------ etc/cinder/cinder.conf.sample | 44 ++--- 4 files changed, 96 insertions(+), 265 deletions(-) diff --git a/cinder/brick/initiator/connector.py b/cinder/brick/initiator/connector.py index 0778f946bba..8accbb1078a 100644 --- a/cinder/brick/initiator/connector.py +++ b/cinder/brick/initiator/connector.py @@ -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.""" diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py index 6c6d0a7c2cc..abc74937545 100644 --- a/cinder/tests/test_volume.py +++ b/cinder/tests/test_volume.py @@ -2233,6 +2233,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.""" @@ -2254,11 +2264,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", @@ -2270,7 +2277,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") @@ -2304,7 +2311,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', @@ -2320,29 +2327,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): diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index a286b4b3b7d..8b5380b1ee4 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -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): @@ -750,91 +755,18 @@ class ISERDriver(ISCSIDriver): ' '. `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. @@ -856,123 +788,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.""" diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 8ab46e8e021..8e3cdf4e970 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -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=