components/openstack/cinder/files/solaris/zfs.py
branchs11-update
changeset 3178 77584387a894
parent 3147 891ab4caf1bc
child 2162 0fee3eccf153
child 4072 db0cec748ec0
--- a/components/openstack/cinder/files/solaris/zfs.py	Wed Jun 11 05:34:04 2014 -0700
+++ b/components/openstack/cinder/files/solaris/zfs.py	Fri Jun 13 09:10:23 2014 -0700
@@ -25,14 +25,14 @@
 from oslo.config import cfg
 
 from cinder import exception
-from cinder import flags
 from cinder.image import image_utils
 from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
 from cinder.volume import driver
 
 from solaris_install.target.size import Size
 
-FLAGS = flags.FLAGS
+FLAGS = cfg.CONF
 LOG = logging.getLogger(__name__)
 
 solaris_zfs_opts = [
@@ -73,7 +73,7 @@
                                    "from that of the snapshot, '%s'.")
                                  % (volume['name'], volume['size'],
                                     snapshot['volume_size']))
-            raise exception.VolumeBackendAPIException(data=exception_message)
+            raise exception.InvalidInput(reason=exception_message)
 
         # Create a ZFS clone
         zfs_snapshot = self._get_zfs_snap_name(snapshot)
@@ -172,18 +172,20 @@
                                         volume['id'])
         return {
             'driver_volume_type': 'local',
-            'volume_path': volume_path
+            'volume_path': volume_path,
+            'data': {}
         }
 
     def terminate_connection(self, volume, connector, **kwargs):
         """Disconnection from the connector."""
         pass
 
-    def attach_volume(self, context, volume_id, instance_uuid, mountpoint):
-        """ Callback for volume attached to instance."""
+    def attach_volume(self, context, volume, instance_uuid, host_name,
+                      mountpoint):
+        """Callback for volume attached to instance or host."""
         pass
 
-    def detach_volume(self, context, volume_id):
+    def detach_volume(self, context, volume):
         """ Callback for volume detached."""
         pass
 
@@ -251,6 +253,17 @@
 
         self._stats = stats
 
+    def extend_volume(self, volume, new_size):
+        """Extend an existing volume's size."""
+        volsize_str = 'volsize=%sg' % new_size
+        zfs_volume = self._get_zfs_volume_name(volume)
+        try:
+            self._execute('/usr/sbin/zfs', 'set', volsize_str, zfs_volume)
+        except Exception:
+            msg = (_("Failed to extend volume size to %(new_size)s GB.")
+                   % {'new_size': new_size})
+            raise exception.VolumeBackendAPIException(data=msg)
+
 
 class STMFDriver(ZFSVolumeDriver):
     """Abstract base class for common COMSTAR operations."""
@@ -327,11 +340,11 @@
     def _get_view_and_lun(self, lu):
         """Check the view entry of the LU and then get the lun and view."""
         view_and_lun = {}
-        view_and_lun['valid_value'] = False
+        view_and_lun['view'] = view_and_lun['lun'] = None
         try:
             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-view',
-                                        '-l', lu)
-        except exception.ProcessExecutionError as error:
+                                        '-l', lu, '-v')
+        except processutils.ProcessExecutionError as error:
             if 'no views found' in error.stderr:
                 LOG.debug(_("No view is found for LU '%s'") % lu)
                 return view_and_lun
@@ -341,15 +354,19 @@
         for line in [l.strip() for l in out.splitlines()]:
             if line.startswith("View Entry:"):
                 view_and_lun['view'] = line.split()[-1]
-                view_and_lun['valid_value'] = True
-            if line.startswith("LUN"):
-                view_and_lun['lun'] = line.split()[-1]
+            if line.startswith("LUN") and 'Auto' not in line.split()[-1]:
+                view_and_lun['lun'] = int(line.split()[-1])
+                break
+            if line.startswith("Lun"):
+                view_and_lun['lun'] = int(line.split()[2])
 
-        if view_and_lun['lun'] == 'Auto':
-            view_and_lun['lun'] = 0
-
-        LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%s'.")
-                  % (lu, view_and_lun['view'], view_and_lun['lun']))
+        if view_and_lun['view'] is None or view_and_lun['lun'] is None:
+            LOG.error(_("Failed to get the view_entry or LUN of the LU '%s'.")
+                      % lu)
+            raise
+        else:
+            LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%d'.")
+                      % (lu, view_and_lun['view'], view_and_lun['lun']))
 
         return view_and_lun
 
@@ -385,9 +402,9 @@
         self._execute('/usr/sbin/itadm', 'create-target', '-n', target_name)
         assert self._check_target(target_name, 'iSCSI')
 
-        # Add a logical unit view entry
+        # Add a view entry to the logical unit with the specified LUN, 8776
         if luid is not None:
-            self._execute('/usr/sbin/stmfadm', 'add-view', '-t',
+            self._execute('/usr/sbin/stmfadm', 'add-view', '-n', 8776, '-t',
                           target_group, luid)
 
     def remove_export(self, context, volume):
@@ -404,7 +421,7 @@
         # Remove the view entry
         if luid is not None:
             view_lun = self._get_view_and_lun(luid)
-            if view_lun['valid_value']:
+            if view_lun['view']:
                 self._execute('/usr/sbin/stmfadm', 'remove-view', '-l',
                               luid, view_lun['view'])
 
@@ -452,9 +469,9 @@
         properties['target_iqn'] = target_name
         properties['target_portal'] = ('%s:%d' %
                                        (self.configuration.iscsi_ip_address,
-                                       self.configuration.iscsi_port))
+                                        self.configuration.iscsi_port))
         view_lun = self._get_view_and_lun(luid)
-        if view_lun['valid_value']:
+        if view_lun['lun']:
             properties['target_lun'] = view_lun['lun']
         properties['volume_id'] = volume['id']
 
@@ -508,3 +525,210 @@
                     'for volume %(volume_name)s')
                   % {'initiator_name': initiator_name,
                      'volume_name': volume_name})
+
+
+class ZFSFCDriver(STMFDriver, driver.FibreChannelDriver):
+    """ZFS volume operations in FC mode."""
+    protocol = 'FC'
+
+    def __init__(self, *args, **kwargs):
+        super(ZFSFCDriver, self).__init__(*args, **kwargs)
+
+    def check_for_setup_error(self):
+        """Check the setup error."""
+        wwns = self._get_wwns()
+        if not wwns:
+            msg = (_("Could not determine fibre channel world wide "
+                     "node names."))
+            raise exception.VolumeBackendAPIException(data=msg)
+
+    def _get_wwns(self):
+        """Get the FC port WWNs of the host."""
+        (out, _err) = self._execute('/usr/sbin/fcinfo', 'hba-port', '-t')
+
+        wwns = []
+        for line in [l.strip() for l in out.splitlines()]:
+            if line.startswith("HBA Port WWN:"):
+                wwn = line.split()[-1]
+                LOG.debug(_("Got the FC port WWN '%s'") % wwn)
+                wwns.append(wwn)
+
+        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')
+
+        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
+
+        return tg
+
+    def _only_lu(self, lu):
+        """Check if the LU is the only one."""
+        (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-lu', '-v')
+        linecount = 0
+
+        for line in [l.strip() for l in out.splitlines()]:
+            if line.startswith("LU Name:"):
+                luid = line.split()[-1]
+                linecount += 1
+
+        if linecount == 1 and luid == lu:
+            LOG.debug(_("The LU '%s' is the only one.") % lu)
+            return True
+        else:
+            return False
+
+    def _target_in_tg(self, wwn, tg):
+        """Check if the target has been added into a target group."""
+        target = 'wwn.%s' % wwn.upper()
+
+        if tg is not None:
+            (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg',
+                                        '-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)
+        return False
+
+    def create_export(self, context, volume):
+        """Export the volume."""
+        zvol = self._get_zvol_path(volume)
+
+        # Create a Logical Unit (LU)
+        self._execute('/usr/sbin/stmfadm', 'create-lu', zvol)
+        luid = self._get_luid(volume)
+        if not luid:
+            msg = (_("Failed to create logic unit for volume '%s'")
+                   % 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._execute('/usr/sbin/stmfadm', 'create-tg', target_group)
+
+            # Enable the target and add it to the 'tg-wwn-xxx' group
+            self._execute('/usr/sbin/stmfadm', 'offline-target',
+                          'wwn.%s' % wwn)
+            self._execute('/usr/sbin/stmfadm', 'add-tg-member', '-g',
+                          target_group, 'wwn.%s' % wwn)
+            self._execute('/usr/sbin/stmfadm', 'online-target', 'wwn.%s' % wwn)
+        assert self._target_in_tg(wwn, target_group)
+
+        # Add a logical unit view entry
+        # TODO(Strony): replace the auto assigned LUN with '-n' option
+        if luid is not None:
+            self._execute('/usr/sbin/stmfadm', 'add-view', '-t',
+                          target_group, luid)
+
+    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
+            view_lun = self._get_view_and_lun(luid)
+            if view_lun['view']:
+                self._execute('/usr/sbin/stmfadm', 'remove-view', '-l',
+                              luid, view_lun['view'])
+
+            # Remove the target group when only one LU exists.
+            if self._only_lu(luid):
+                if self._check_target(target_wwn, 'Channel'):
+                    self._execute('/usr/sbin/stmfadm', 'offline-target',
+                                  target_wwn)
+                if self._check_tg(target_group):
+                    self._execute('/usr/sbin/stmfadm', 'delete-tg',
+                                  target_group)
+
+            # Remove the LU
+            self._execute('/usr/sbin/stmfadm', 'delete-lu', luid)
+
+    def _get_fc_properties(self, volume):
+        """Get Fibre Channel configuration.
+
+        :target_discovered:    boolean indicating whether discovery was used
+        :target_wwn:           the world wide name of the FC port target
+        :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'")
+                   % volume['name'])
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        properties = {}
+
+        properties['target_discovered'] = True
+        properties['target_wwn'] = wwns
+        view_lun = self._get_view_and_lun(luid)
+        if view_lun['lun']:
+            properties['target_lun'] = view_lun['lun']
+        return properties
+
+    def initialize_connection(self, volume, connector):
+        """Initializes the connection and returns connection info.
+
+        The  driver returns a driver_volume_type of 'fibre_channel'.
+        The target_wwn can be a single entry or a list of wwns that
+        correspond to the list of remote wwn(s) that will export the volume.
+        Example return values:
+
+            {
+                'driver_volume_type': 'fibre_channel'
+                'data': {
+                    'target_discovered': True,
+                    'target_lun': 1,
+                    'target_wwn': '1234567890123',
+                }
+            }
+
+            or
+
+             {
+                'driver_volume_type': 'fibre_channel'
+                'data': {
+                    'target_discovered': True,
+                    'target_lun': 1,
+                    'target_wwn': ['1234567890123', '0987654321321'],
+                }
+            }
+
+        """
+        fc_properties = self._get_fc_properties(volume)
+
+        return {
+            'driver_volume_type': 'fibre_channel',
+            'data': fc_properties
+        }