]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add the iscsi device check and exception processing.
authorVincent Hou <sbhou@cn.ibm.com>
Thu, 6 Jun 2013 08:46:52 +0000 (16:46 +0800)
committerVincent Hou <sbhou@cn.ibm.com>
Sun, 9 Jun 2013 08:15:21 +0000 (16:15 +0800)
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=<device> 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
cinder/volume/driver.py
cinder/volume/manager.py

index 6ccbfc9fc38d0bed340d054acd6200835aa4a7f3..058787f3a5d4b9a3cc0237e50b55de722f1932b3 100644 (file)
@@ -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.")
 
index ea3a92b1efe6f1e89c5f3a0a52d48c06dd7bb5f2..269622721e264b4086eba76ca838a064c451d2de 100644 (file)
@@ -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):
index 8effaf1b6a62d230d87b357371240cebda26d174..3ed0fb90e996d5e7fe1d85b203e2e6fe6128b52a 100644 (file)
@@ -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})