From aa7fde57a37ae8e03f137e7b525cc77109cc31c1 Mon Sep 17 00:00:00 2001 From: Vincent Hou Date: Thu, 6 Jun 2013 16:46:52 +0800 Subject: [PATCH] Add the iscsi device check and exception processing. Before downloading the image and executing the command "qemu-img convert", check if the iSCSI device is still available via the command "sudo dd if= of=/dev/null count=1". This command will raise an exception with the message "Input/output error", if the back-end storage is disconnected to the cinder-volume node, so we use it to test the availability of the storage device. If it is unavailable, there is no need to download the image & "qemu-img convert" and an exception DeviceUnavailable will be raised. Fixed Bug1169290. Change-Id: I133b4cc1bac493df073d42e240092cf2e6300454 --- cinder/exception.py | 4 ++ cinder/volume/driver.py | 105 ++++++++++++++++++++++++++++++--------- cinder/volume/manager.py | 2 +- 3 files changed, 86 insertions(+), 25 deletions(-) diff --git a/cinder/exception.py b/cinder/exception.py index 6ccbfc9fc..058787f3a 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -194,6 +194,10 @@ class ImageUnacceptable(Invalid): message = _("Image %(image_id)s is unacceptable: %(reason)s") +class DeviceUnavailable(Invalid): + message = _("The device in the path %(path)s is unavailable: %(reason)s") + + class InvalidUUID(Invalid): message = _("Expected a uuid but received %(uuid)s.") diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index ea3a92b1e..269622721 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -310,6 +310,16 @@ class ISCSIDriver(VolumeDriver): (iscsi_command, out, err)) return (out, err) + def _run_iscsiadm_bare(self, iscsi_command, **kwargs): + check_exit_code = kwargs.pop('check_exit_code', 0) + (out, err) = utils.execute('iscsiadm', + *iscsi_command, + run_as_root=True, + check_exit_code=check_exit_code) + LOG.debug("iscsiadm %s: stdout=%s stderr=%s" % + (iscsi_command, out, err)) + return (out, err) + def _iscsiadm_update(self, iscsi_properties, property_key, property_value, **kwargs): iscsi_command = ('--op', 'update', '-n', property_key, @@ -347,6 +357,22 @@ 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 = utils.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 @@ -418,40 +444,71 @@ class ISCSIDriver(VolumeDriver): "node.session.auth.password", iscsi_properties['auth_password']) - # NOTE(vish): If we have another lun on the same target, we may - # have a duplicate login - self._run_iscsiadm(iscsi_properties, ("--login",), - check_exit_code=[0, 255]) - - self._iscsiadm_update(iscsi_properties, "node.startup", "automatic") - host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" % (iscsi_properties['target_portal'], iscsi_properties['target_iqn'], iscsi_properties.get('target_lun', 0))) - tries = 0 - while not os.path.exists(host_device): - if tries >= self.configuration.num_iscsi_scan_tries: - raise exception.CinderException( - _("iSCSI device not found at %s") % (host_device)) + out = self._run_iscsiadm_bare(["-m", "session"], + run_as_root=True, + check_exit_code=[0, 1, 21])[0] or "" - LOG.warn(_("ISCSI volume not yet found at: %(host_device)s. " - "Will rescan & retry. Try number: %(tries)s") % - locals()) + portals = [{'portal': p.split(" ")[2], 'iqn': p.split(" ")[3]} + for p in out.splitlines() if p.startswith("tcp:")] - # The rescan isn't documented as being necessary(?), but it helps - self._run_iscsiadm(iscsi_properties, ("--rescan",)) + stripped_portal = iscsi_properties['target_portal'].split(",")[0] + length_iqn = [s for s in portals + if stripped_portal == + s['portal'].split(",")[0] and + s['iqn'] == iscsi_properties['target_iqn']] + if len(portals) == 0 or len(length_iqn) == 0: + try: + self._run_iscsiadm(iscsi_properties, ("--login",), + check_exit_code=[0, 255]) + except exception.ProcessExecutionError as err: + if err.exit_code in [15]: + self._iscsiadm_update(iscsi_properties, + "node.startup", + "automatic") + return iscsi_properties, host_device + else: + raise - tries = tries + 1 - if not os.path.exists(host_device): - time.sleep(tries ** 2) + self._iscsiadm_update(iscsi_properties, + "node.startup", "automatic") + + tries = 0 + while not os.path.exists(host_device): + if tries >= self.configuration.num_iscsi_scan_tries: + raise exception.CinderException(_("iSCSI device " + "not found " + "at %s") % (host_device)) - if tries != 0: - LOG.debug(_("Found iSCSI node %(host_device)s " - "(after %(tries)s rescans)") % - locals()) + LOG.warn(_("ISCSI 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(iscsi_properties, ("--rescan",)) + + tries = tries + 1 + if not os.path.exists(host_device): + time.sleep(tries ** 2) + + if tries != 0: + LOG.debug(_("Found iSCSI 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 iscsi_properties, host_device def get_volume_stats(self, refresh=False): diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index 8effaf1b6..3ed0fb90e 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -615,7 +615,7 @@ class VolumeManager(manager.SchedulerDependentManager): "error: %(error)s") % {'volume_id': volume_id, 'error': ex.stderr}) raise exception.ImageCopyFailure(reason=ex.stderr) - except exception.ImageUnacceptable as ex: + except Exception as ex: LOG.error(_("Failed to copy image to volume: %(volume_id)s, " "error: %(error)s") % {'volume_id': volume_id, 'error': ex}) -- 2.45.2