23294485 Cinder ZFSFCDriver fails to find the right target wwn s11u3-sru
authorsaurabh.vyas@oracle.com
Wed, 25 May 2016 14:31:44 -0700
branchs11u3-sru
changeset 7787 26bcbec7afce
parent 7784 531789214c7c
child 7788 61e5a13d7162
23294485 Cinder ZFSFCDriver fails to find the right target wwn 23072471 nova driver need to accept multiple target_wwn entries in connection_info
components/openstack/cinder/files/solaris/solarisfc.py
components/openstack/cinder/files/solaris/zfs.py
components/openstack/nova/files/nova.exec_attr
components/openstack/nova/files/solariszones/driver.py
--- a/components/openstack/cinder/files/solaris/solarisfc.py	Tue Mar 21 13:15:18 2017 -0700
+++ b/components/openstack/cinder/files/solaris/solarisfc.py	Wed May 25 14:31:44 2016 -0700
@@ -1,6 +1,6 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
-# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
@@ -109,23 +109,45 @@
         for wwpn in wwpns:
             self.execute('/usr/sbin/fcadm', 'force-lip', wwpn)
 
-    def _get_device_path(self, wwn):
-        """Get the Device Name of the WWN"""
+    def _get_device_path(self, wwn, target_lun):
+        """Get the Device path for the specified LUN.
+
+        The output of CMD below is like this:
+
+        OS Device Name: /dev/rdsk/c0t600C0FF0000000000036223AE73EB705d0s2
+               HBA Port WWN: 210100e08b27a8a1
+                       Remote Port WWN: 256000c0ffc03622
+                               LUN: 0
+                       Remote Port WWN: 216000c0ff803622
+                               LUN: 0
+               HBA Port WWN: 210000e08b07a8a1
+                       Remote Port WWN: 256000c0ffc03622
+                               LUN: 0
+                       Remote Port WWN: 216000c0ff803622
+                               LUN: 0
+               Vendor: SUN
+               Product: StorEdge 3510
+               Device Type: Disk device
+        ......
+        """
         try:
             out, err = self.execute('/usr/sbin/fcinfo', 'logical-unit', '-v')
         except putils.ProcessExecutionError as err:
             return None
 
-        host_dev = None
+        host_device = None
         remote_port = None
         if out is not None:
             for line in [l.strip() for l in out.splitlines()]:
                 if line.startswith("OS Device Name:"):
-                    host_dev = line.split()[-1]
+                    host_device = line.split()[-1]
                 if line.startswith("Remote Port WWN:"):
                     remote_port = line.split()[-1]
-                if remote_port == wwn:
-                    return host_dev
+                if line.startswith("LUN:"):
+                    lun = line.split()[-1]
+                    if remote_port.upper() == wwn and \
+                       int(lun) == int(target_lun):
+                        return host_device
 
         return None
 
@@ -133,28 +155,30 @@
         """Attach the volume to instance_name.
 
         connection_properties for Fibre Channel must include:
-        target_portal - ip and optional port
-        target_iqn - iSCSI Qualified Name
+        target_wwn - Specified port WWNs
         target_lun - LUN id of the volume
         """
         device_info = {'type': 'block'}
         target_wwn = connection_properties['target_wwn']
-        # Check for multiple target_wwn values in a list
+        target_lun = connection_properties['target_lun']
+        wwns = []
         if isinstance(target_wwn, list):
-            wwn = target_wwn[0]
+            wwns = target_wwn
+        else:
+            wwns.append(target_wwn)
 
         # The scsi_vhci disk node is not always present immediately.
         # Sometimes we need to reinitialize the connection to trigger
         # a refresh.
         for i in range(1, scan_tries):
-            LOG.debug("Looking for Fibre Channel device")
-            host_dev = self._get_device_path(wwn)
+            # initiator needs time to refresh the LU list
+            time.sleep(i * 2)
+            host_device = self._get_device_path(wwns[0], target_lun)
 
-            if host_dev is not None and os.path.exists(host_dev):
+            if host_device is not None and os.path.exists(host_device):
                 break
             else:
                 self._refresh_connection()
-                time.sleep(i ** 2)
         else:
             msg = _("Fibre Channel volume device not found.")
             LOG.error(msg)
@@ -170,5 +194,5 @@
                                        disk_name)
             host_device = '%ss0' % tmp_dev_name[0]
 
-        device_info['path'] = host_dev
+        device_info['path'] = host_device
         return device_info
--- a/components/openstack/cinder/files/solaris/zfs.py	Tue Mar 21 13:15:18 2017 -0700
+++ b/components/openstack/cinder/files/solaris/zfs.py	Wed May 25 14:31:44 2016 -0700
@@ -2,7 +2,7 @@
 # Copyright (c) 2012 OpenStack LLC.
 # All Rights Reserved.
 #
-# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
@@ -965,14 +965,18 @@
 
         return status
 
-    def check_for_setup_error(self):
-        """Check the setup error."""
-        wwns = self._get_wwns()
-        if not wwns:
+    def do_setup(self, context):
+        """Check wwns and setup the target group."""
+        self.wwns = self._get_wwns()
+        if not self.wwns:
             msg = (_("Could not determine fibre channel world wide "
                      "node names."))
             raise exception.VolumeBackendAPIException(data=msg)
 
+        self.tg = 'tg-wwn-%s' % self.wwns[0]
+        if not self._check_tg(self.tg):
+            self._setup_tg(self.tg)
+
     def _get_wwns(self):
         """Get the FC port WWNs of the host."""
         (out, _err) = self._execute('/usr/sbin/fcinfo', 'hba-port', '-t')
@@ -986,19 +990,45 @@
 
         return wwns
 
-    def _check_wwn_tg(self, wwn):
-        """Check if the target group 'tg-wwn-xxx' exists."""
-        (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg')
+    def _get_target_wwns(self, tg):
+        """Get the target members in the tg."""
+        (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg',
+                                    '-v', tg)
 
+        wwns = []
         for line in [l.strip() for l in out.splitlines()]:
-            if line.startswith("Target Group:") and wwn in line:
-                tg = line.split()[-1]
-                break
-        else:
-            LOG.debug(_("The target group 'tg-wwn-%s' doesn't exist.") % wwn)
-            tg = None
+            if line.startswith("Member:"):
+                wwn = line.split()[-1]
+                target_wwn = wwn.split('.')[-1]
+                wwns.append(target_wwn)
+        return wwns
+
+    def _setup_tg(self, tg):
+        """Setup the target group."""
+        self._stmf_execute('/usr/sbin/stmfadm', 'create-tg', tg)
 
-        return tg
+        # Add free target wwns into the target group
+        for wwn in self.wwns:
+            if not self._target_in_tg(wwn, None):
+                target_wwn = 'wwn.%s' % wwn
+                try:
+                    self._stmf_execute('/usr/sbin/stmfadm', 'offline-target',
+                                       target_wwn)
+                    self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member',
+                                       '-g', tg, target_wwn)
+                    self._stmf_execute('/usr/sbin/stmfadm', 'online-target',
+                                       target_wwn)
+                    assert self._check_target(wwn, 'Channel') == 'Online'
+
+                except:
+                    LOG.error(_LE("Failed to add and online the target '%s'.")
+                              % (target_wwn))
+
+        target_wwns = self._get_target_wwns(tg)
+        if not target_wwns:
+            msg = (_("No target members exist in the target group '%s'.")
+                   % tg)
+            raise exception.VolumeBackendAPIException(data=msg)
 
     def _only_lu(self, lu):
         """Check if the LU is the only one."""
@@ -1025,13 +1055,19 @@
                                         '-v', tg)
         else:
             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg', '-v')
-
         for line in [l.strip() for l in out.splitlines()]:
             if line.startswith("Member:") and target in line:
                 return True
-        LOG.debug(_("The target '%s' is not in any target group.") % target)
+        LOG.debug(_("The target '%s' is not in %s target group.") %
+                  (target, tg if tg else 'any'))
         return False
 
+    def _force_lip_wwn(self):
+        """Force the link to reinitialize."""
+        target_wwns = self._get_target_wwns(self.tg)
+        for target_wwn in target_wwns:
+            self._stmf_execute('/usr/sbin/fcadm', 'force-lip', target_wwn)
+
     def create_export(self, context, volume):
         """Export the volume."""
         # If the volume is already exported there is nothing to do, as we
@@ -1047,7 +1083,6 @@
                 raise exception.VolumeBackendAPIException(data=msg)
 
         zvol = self._get_zvol_path(volume)
-
         # Create a Logical Unit (LU)
         self._stmf_execute('/usr/sbin/stmfadm', 'create-lu', zvol)
         luid = self._get_luid(volume)
@@ -1056,55 +1091,28 @@
                    % volume['name'])
             raise exception.VolumeBackendAPIException(data=msg)
 
-        wwns = self._get_wwns()
-        wwn = wwns[0]
-        target_group = self._check_wwn_tg(wwn)
-        if target_group is None:
-            target_group = 'tg-wwn-%s' % wwn
-            if self._target_in_tg(wwn, None):
-                msg = (_("Target WWN '%s' has been found in another"
-                         "target group, so it will not be added "
-                         "into the expected target group '%s'.") %
-                       (wwn, target_group))
-                raise exception.VolumeBackendAPIException(data=msg)
-
-            # Create a target group for the wwn
-            self._stmf_execute('/usr/sbin/stmfadm', 'create-tg', target_group)
-
-            # Enable the target and add it to the 'tg-wwn-xxx' group
-            self._stmf_execute('/usr/sbin/stmfadm', 'offline-target',
-                               'wwn.%s' % wwn)
-            self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member', '-g',
-                               target_group, 'wwn.%s' % wwn)
-
+        # setup the target group if it doesn't exist.
+        if not self._check_tg(self.tg):
+            self._setup_tg(self.tg)
         # Add a logical unit view entry
-        # TODO(Strony): replace the auto assigned LUN with '-n' option
-        if luid is not None:
-            self._stmf_execute('/usr/sbin/stmfadm', 'add-view', '-t',
-                               target_group, luid)
-            self._stmf_execute('/usr/sbin/stmfadm', 'online-target',
-                               'wwn.%s' % wwn)
-        assert self._target_in_tg(wwn, target_group)
+        self._stmf_execute('/usr/sbin/stmfadm', 'add-view', '-t',
+                           self.tg, luid)
+        self._force_lip_wwn()
 
     def remove_export(self, context, volume):
         """Remove an export for a volume."""
         luid = self._get_luid(volume)
 
         if luid is not None:
-            wwns = self._get_wwns()
-            wwn = wwns[0]
-            target_wwn = 'wwn.%s' % wwn
-            target_group = 'tg-wwn-%s' % wwn
+            target_group = self.tg
             view_lun = self._get_view_and_lun(luid)
             if view_lun['view']:
                 self._stmf_execute('/usr/sbin/stmfadm', 'remove-view', '-l',
                                    luid, view_lun['view'])
 
-            # Remove the target group when only one LU exists.
+            # Remove the target group when the LU to be deleted is last one
+            # exposed by the target group.
             if self._only_lu(luid):
-                if self._check_target(target_wwn, 'Channel') == 'Online':
-                    self._stmf_execute('/usr/sbin/stmfadm', 'offline-target',
-                                       target_wwn)
                 if self._check_tg(target_group):
                     self._stmf_execute('/usr/sbin/stmfadm', 'delete-tg',
                                        target_group)
@@ -1120,12 +1128,6 @@
         :target_lun:           the lun assigned to the LU for the view entry
 
         """
-        wwns = self._get_wwns()
-        if not wwns:
-            msg = (_("Could not determine fibre channel world wide "
-                     "node names."))
-            raise exception.VolumeBackendAPIException(data=msg)
-
         luid = self._get_luid(volume)
         if not luid:
             msg = (_("Failed to get logic unit for volume '%s'")
@@ -1135,7 +1137,7 @@
         properties = {}
 
         properties['target_discovered'] = True
-        properties['target_wwn'] = wwns
+        properties['target_wwn'] = self._get_target_wwns(self.tg)
         view_lun = self._get_view_and_lun(luid)
         if view_lun['lun'] is not None:
             properties['target_lun'] = view_lun['lun']
--- a/components/openstack/nova/files/nova.exec_attr	Tue Mar 21 13:15:18 2017 -0700
+++ b/components/openstack/nova/files/nova.exec_attr	Wed May 25 14:31:44 2016 -0700
@@ -13,6 +13,8 @@
 
 nova-compute:solaris:cmd:RO::/usr/sbin/iscsiadm:euid=0
 
+nova-compute:solaris:cmd:RO::/usr/sbin/suriadm:privs=file_dac_read
+
 nova-compute:solaris:cmd:RO::/usr/sbin/zfs:euid=0
 
 nova-compute:solaris:cmd:RO::/usr/sbin/zlogin:uid=0
--- a/components/openstack/nova/files/solariszones/driver.py	Tue Mar 21 13:15:18 2017 -0700
+++ b/components/openstack/nova/files/solariszones/driver.py	Wed May 25 14:31:44 2016 -0700
@@ -1121,15 +1121,11 @@
         elif driver_type == 'fibre_channel':
             data = connection_info['data']
             target_wwn = data['target_wwn']
-            # Check for multiple target_wwn values in a list
-            if isinstance(target_wwn, list):
-                target_wwn = target_wwn[0]
             # Ensure there's a fibre channel HBA.
             hbas = self._get_fc_hbas()
             if not hbas:
-                LOG.error(_("Cannot attach Fibre Channel volume '%s' because "
-                          "no Fibre Channel HBA initiators were found")
-                          % (target_wwn))
+                LOG.error(_("Cannot attach Fibre Channel volume because "
+                          "no Fibre Channel HBA initiators were found"))
                 raise exception.InvalidVolume(
                     reason="No host Fibre Channel initiator found")
 
@@ -1142,22 +1138,36 @@
                 utils.execute('/usr/sbin/fcinfo', 'remote-port',
                               '-p', wwpn)
 
-            # Use suriadm(1M) to generate a Fibre Channel storage URI.
-            try:
-                out, err = utils.execute('/usr/sbin/suriadm', 'lookup-uri',
-                                         '-p', 'target=naa.%s' % target_wwn,
-                                         '-p', 'lun=%s' % target_lun)
-            except processutils.ProcessExecutionError as ex:
-                reason = ex.stderr
-                LOG.error(_("Lookup failure of Fibre Channel volume '%s', lun "
-                          "%s: %s") % (target_wwn, target_lun, reason))
-                raise
-
-            lines = out.split('\n')
-            # Use the long form SURI on the second output line.
-            suri = lines[1].strip()
+            suri = self._lookup_fc_volume_suri(target_wwn, target_lun)
         return suri
 
+    def _lookup_fc_volume_suri(self, target_wwn, target_lun):
+        """Searching the LU based URI for the FC LU. """
+        wwns = []
+        if isinstance(target_wwn, list):
+            wwns = target_wwn
+        else:
+            wwns.append(target_wwn)
+
+        for _none in range(3):
+            for wwn in wwns:
+                try:
+                    out, err = utils.execute('/usr/sbin/suriadm', 'lookup-uri',
+                                             '-p', 'target=naa.%s' % wwn,
+                                             '-p', 'lun=%s' % target_lun)
+                    for line in [l.strip() for l in out.splitlines()]:
+                        if line.startswith("lu:luname.naa."):
+                            return line
+                except processutils.ProcessExecutionError as ex:
+                    reason = ex.stderr
+                    LOG.debug(_("Failed to lookup-uri for volume '%s', lun "
+                              "%s: %s") % (wwn, target_lun, reason))
+            greenthread.sleep(2)
+        else:
+            msg = _("Unable to lookup URI of Fibre Channel volume "
+                    "with lun '%s'." % target_lun)
+            raise exception.InvalidVolume(reason=msg)
+
     def _set_global_properties(self, name, extra_specs, brand):
         """Set Solaris Zone's global properties if supplied via flavor."""
         zone = self._get_zone_by_name(name)