components/openstack/cinder/files/solaris/zfs.py
changeset 1944 56ac2df1785b
parent 1912 b0c17ef05b75
child 2162 0fee3eccf153
child 4072 db0cec748ec0
equal deleted inserted replaced
1943:1a27f000029f 1944:56ac2df1785b
    23 import os
    23 import os
    24 
    24 
    25 from oslo.config import cfg
    25 from oslo.config import cfg
    26 
    26 
    27 from cinder import exception
    27 from cinder import exception
    28 from cinder import flags
       
    29 from cinder.image import image_utils
    28 from cinder.image import image_utils
    30 from cinder.openstack.common import log as logging
    29 from cinder.openstack.common import log as logging
       
    30 from cinder.openstack.common import processutils
    31 from cinder.volume import driver
    31 from cinder.volume import driver
    32 
    32 
    33 from solaris_install.target.size import Size
    33 from solaris_install.target.size import Size
    34 
    34 
    35 FLAGS = flags.FLAGS
    35 FLAGS = cfg.CONF
    36 LOG = logging.getLogger(__name__)
    36 LOG = logging.getLogger(__name__)
    37 
    37 
    38 solaris_zfs_opts = [
    38 solaris_zfs_opts = [
    39     cfg.StrOpt('zfs_volume_base',
    39     cfg.StrOpt('zfs_volume_base',
    40                default='rpool/cinder',
    40                default='rpool/cinder',
    71             exception_message = (_("Could not create volume '%s' because "
    71             exception_message = (_("Could not create volume '%s' because "
    72                                    "its volume size of '%s' is different "
    72                                    "its volume size of '%s' is different "
    73                                    "from that of the snapshot, '%s'.")
    73                                    "from that of the snapshot, '%s'.")
    74                                  % (volume['name'], volume['size'],
    74                                  % (volume['name'], volume['size'],
    75                                     snapshot['volume_size']))
    75                                     snapshot['volume_size']))
    76             raise exception.VolumeBackendAPIException(data=exception_message)
    76             raise exception.InvalidInput(reason=exception_message)
    77 
    77 
    78         # Create a ZFS clone
    78         # Create a ZFS clone
    79         zfs_snapshot = self._get_zfs_snap_name(snapshot)
    79         zfs_snapshot = self._get_zfs_snap_name(snapshot)
    80         zfs_volume = self._get_zfs_volume_name(volume)
    80         zfs_volume = self._get_zfs_volume_name(volume)
    81         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
    81         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
   170         """Initialize the connection and returns connection info."""
   170         """Initialize the connection and returns connection info."""
   171         volume_path = '%s/volume-%s' % (self.configuration.zfs_volume_base,
   171         volume_path = '%s/volume-%s' % (self.configuration.zfs_volume_base,
   172                                         volume['id'])
   172                                         volume['id'])
   173         return {
   173         return {
   174             'driver_volume_type': 'local',
   174             'driver_volume_type': 'local',
   175             'volume_path': volume_path
   175             'volume_path': volume_path,
       
   176             'data': {}
   176         }
   177         }
   177 
   178 
   178     def terminate_connection(self, volume, connector, **kwargs):
   179     def terminate_connection(self, volume, connector, **kwargs):
   179         """Disconnection from the connector."""
   180         """Disconnection from the connector."""
   180         pass
   181         pass
   181 
   182 
   182     def attach_volume(self, context, volume_id, instance_uuid, mountpoint):
   183     def attach_volume(self, context, volume, instance_uuid, host_name,
   183         """ Callback for volume attached to instance."""
   184                       mountpoint):
       
   185         """Callback for volume attached to instance or host."""
   184         pass
   186         pass
   185 
   187 
   186     def detach_volume(self, context, volume_id):
   188     def detach_volume(self, context, volume):
   187         """ Callback for volume detached."""
   189         """ Callback for volume detached."""
   188         pass
   190         pass
   189 
   191 
   190     def get_volume_stats(self, refresh=False):
   192     def get_volume_stats(self, refresh=False):
   191         """Get volume status."""
   193         """Get volume status."""
   249         stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units)
   251         stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units)
   250         stats['reserved_percentage'] = self.configuration.reserved_percentage
   252         stats['reserved_percentage'] = self.configuration.reserved_percentage
   251 
   253 
   252         self._stats = stats
   254         self._stats = stats
   253 
   255 
       
   256     def extend_volume(self, volume, new_size):
       
   257         """Extend an existing volume's size."""
       
   258         volsize_str = 'volsize=%sg' % new_size
       
   259         zfs_volume = self._get_zfs_volume_name(volume)
       
   260         try:
       
   261             self._execute('/usr/sbin/zfs', 'set', volsize_str, zfs_volume)
       
   262         except Exception:
       
   263             msg = (_("Failed to extend volume size to %(new_size)s GB.")
       
   264                    % {'new_size': new_size})
       
   265             raise exception.VolumeBackendAPIException(data=msg)
       
   266 
   254 
   267 
   255 class STMFDriver(ZFSVolumeDriver):
   268 class STMFDriver(ZFSVolumeDriver):
   256     """Abstract base class for common COMSTAR operations."""
   269     """Abstract base class for common COMSTAR operations."""
   257     __metaclass__ = abc.ABCMeta
   270     __metaclass__ = abc.ABCMeta
   258 
   271 
   325         return luid
   338         return luid
   326 
   339 
   327     def _get_view_and_lun(self, lu):
   340     def _get_view_and_lun(self, lu):
   328         """Check the view entry of the LU and then get the lun and view."""
   341         """Check the view entry of the LU and then get the lun and view."""
   329         view_and_lun = {}
   342         view_and_lun = {}
   330         view_and_lun['valid_value'] = False
   343         view_and_lun['view'] = view_and_lun['lun'] = None
   331         try:
   344         try:
   332             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-view',
   345             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-view',
   333                                         '-l', lu)
   346                                         '-l', lu, '-v')
   334         except exception.ProcessExecutionError as error:
   347         except processutils.ProcessExecutionError as error:
   335             if 'no views found' in error.stderr:
   348             if 'no views found' in error.stderr:
   336                 LOG.debug(_("No view is found for LU '%s'") % lu)
   349                 LOG.debug(_("No view is found for LU '%s'") % lu)
   337                 return view_and_lun
   350                 return view_and_lun
   338             else:
   351             else:
   339                 raise
   352                 raise
   340 
   353 
   341         for line in [l.strip() for l in out.splitlines()]:
   354         for line in [l.strip() for l in out.splitlines()]:
   342             if line.startswith("View Entry:"):
   355             if line.startswith("View Entry:"):
   343                 view_and_lun['view'] = line.split()[-1]
   356                 view_and_lun['view'] = line.split()[-1]
   344                 view_and_lun['valid_value'] = True
   357             if line.startswith("LUN") and 'Auto' not in line.split()[-1]:
   345             if line.startswith("LUN"):
   358                 view_and_lun['lun'] = int(line.split()[-1])
   346                 view_and_lun['lun'] = line.split()[-1]
   359                 break
   347 
   360             if line.startswith("Lun"):
   348         if view_and_lun['lun'] == 'Auto':
   361                 view_and_lun['lun'] = int(line.split()[2])
   349             view_and_lun['lun'] = 0
   362 
   350 
   363         if view_and_lun['view'] is None or view_and_lun['lun'] is None:
   351         LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%s'.")
   364             LOG.error(_("Failed to get the view_entry or LUN of the LU '%s'.")
   352                   % (lu, view_and_lun['view'], view_and_lun['lun']))
   365                       % lu)
       
   366             raise
       
   367         else:
       
   368             LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%d'.")
       
   369                       % (lu, view_and_lun['view'], view_and_lun['lun']))
   353 
   370 
   354         return view_and_lun
   371         return view_and_lun
   355 
   372 
   356 
   373 
   357 class ZFSISCSIDriver(STMFDriver, driver.ISCSIDriver):
   374 class ZFSISCSIDriver(STMFDriver, driver.ISCSIDriver):
   383                       target_group, target_name)
   400                       target_group, target_name)
   384 
   401 
   385         self._execute('/usr/sbin/itadm', 'create-target', '-n', target_name)
   402         self._execute('/usr/sbin/itadm', 'create-target', '-n', target_name)
   386         assert self._check_target(target_name, 'iSCSI')
   403         assert self._check_target(target_name, 'iSCSI')
   387 
   404 
   388         # Add a logical unit view entry
   405         # Add a view entry to the logical unit with the specified LUN, 8776
   389         if luid is not None:
   406         if luid is not None:
   390             self._execute('/usr/sbin/stmfadm', 'add-view', '-t',
   407             self._execute('/usr/sbin/stmfadm', 'add-view', '-n', 8776, '-t',
   391                           target_group, luid)
   408                           target_group, luid)
   392 
   409 
   393     def remove_export(self, context, volume):
   410     def remove_export(self, context, volume):
   394         """Remove an export for a volume.
   411         """Remove an export for a volume.
   395 
   412 
   402                                 volume['name'])
   419                                 volume['name'])
   403 
   420 
   404         # Remove the view entry
   421         # Remove the view entry
   405         if luid is not None:
   422         if luid is not None:
   406             view_lun = self._get_view_and_lun(luid)
   423             view_lun = self._get_view_and_lun(luid)
   407             if view_lun['valid_value']:
   424             if view_lun['view']:
   408                 self._execute('/usr/sbin/stmfadm', 'remove-view', '-l',
   425                 self._execute('/usr/sbin/stmfadm', 'remove-view', '-l',
   409                               luid, view_lun['view'])
   426                               luid, view_lun['view'])
   410 
   427 
   411         # Remove the target and its target group
   428         # Remove the target and its target group
   412         if self._check_target(target_name, 'iSCSI'):
   429         if self._check_target(target_name, 'iSCSI'):
   450 
   467 
   451         properties['target_discovered'] = True
   468         properties['target_discovered'] = True
   452         properties['target_iqn'] = target_name
   469         properties['target_iqn'] = target_name
   453         properties['target_portal'] = ('%s:%d' %
   470         properties['target_portal'] = ('%s:%d' %
   454                                        (self.configuration.iscsi_ip_address,
   471                                        (self.configuration.iscsi_ip_address,
   455                                        self.configuration.iscsi_port))
   472                                         self.configuration.iscsi_port))
   456         view_lun = self._get_view_and_lun(luid)
   473         view_lun = self._get_view_and_lun(luid)
   457         if view_lun['valid_value']:
   474         if view_lun['lun']:
   458             properties['target_lun'] = view_lun['lun']
   475             properties['target_lun'] = view_lun['lun']
   459         properties['volume_id'] = volume['id']
   476         properties['volume_id'] = volume['id']
   460 
   477 
   461         auth = volume['provider_auth']
   478         auth = volume['provider_auth']
   462         if auth:
   479         if auth:
   506 
   523 
   507         LOG.debug(_('Disconnecting the initiator %(initiator_name)s '
   524         LOG.debug(_('Disconnecting the initiator %(initiator_name)s '
   508                     'for volume %(volume_name)s')
   525                     'for volume %(volume_name)s')
   509                   % {'initiator_name': initiator_name,
   526                   % {'initiator_name': initiator_name,
   510                      'volume_name': volume_name})
   527                      'volume_name': volume_name})
       
   528 
       
   529 
       
   530 class ZFSFCDriver(STMFDriver, driver.FibreChannelDriver):
       
   531     """ZFS volume operations in FC mode."""
       
   532     protocol = 'FC'
       
   533 
       
   534     def __init__(self, *args, **kwargs):
       
   535         super(ZFSFCDriver, self).__init__(*args, **kwargs)
       
   536 
       
   537     def check_for_setup_error(self):
       
   538         """Check the setup error."""
       
   539         wwns = self._get_wwns()
       
   540         if not wwns:
       
   541             msg = (_("Could not determine fibre channel world wide "
       
   542                      "node names."))
       
   543             raise exception.VolumeBackendAPIException(data=msg)
       
   544 
       
   545     def _get_wwns(self):
       
   546         """Get the FC port WWNs of the host."""
       
   547         (out, _err) = self._execute('/usr/sbin/fcinfo', 'hba-port', '-t')
       
   548 
       
   549         wwns = []
       
   550         for line in [l.strip() for l in out.splitlines()]:
       
   551             if line.startswith("HBA Port WWN:"):
       
   552                 wwn = line.split()[-1]
       
   553                 LOG.debug(_("Got the FC port WWN '%s'") % wwn)
       
   554                 wwns.append(wwn)
       
   555 
       
   556         return wwns
       
   557 
       
   558     def _check_wwn_tg(self, wwn):
       
   559         """Check if the target group 'tg-wwn-xxx' exists."""
       
   560         (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg')
       
   561 
       
   562         for line in [l.strip() for l in out.splitlines()]:
       
   563             if line.startswith("Target Group:") and wwn in line:
       
   564                 tg = line.split()[-1]
       
   565                 break
       
   566         else:
       
   567             LOG.debug(_("The target group 'tg-wwn-%s' doesn't exist.") % wwn)
       
   568             tg = None
       
   569 
       
   570         return tg
       
   571 
       
   572     def _only_lu(self, lu):
       
   573         """Check if the LU is the only one."""
       
   574         (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-lu', '-v')
       
   575         linecount = 0
       
   576 
       
   577         for line in [l.strip() for l in out.splitlines()]:
       
   578             if line.startswith("LU Name:"):
       
   579                 luid = line.split()[-1]
       
   580                 linecount += 1
       
   581 
       
   582         if linecount == 1 and luid == lu:
       
   583             LOG.debug(_("The LU '%s' is the only one.") % lu)
       
   584             return True
       
   585         else:
       
   586             return False
       
   587 
       
   588     def _target_in_tg(self, wwn, tg):
       
   589         """Check if the target has been added into a target group."""
       
   590         target = 'wwn.%s' % wwn.upper()
       
   591 
       
   592         if tg is not None:
       
   593             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg',
       
   594                                         '-v', tg)
       
   595         else:
       
   596             (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg', '-v')
       
   597 
       
   598         for line in [l.strip() for l in out.splitlines()]:
       
   599             if line.startswith("Member:") and target in line:
       
   600                 return True
       
   601         LOG.debug(_("The target '%s' is not in any target group.") % target)
       
   602         return False
       
   603 
       
   604     def create_export(self, context, volume):
       
   605         """Export the volume."""
       
   606         zvol = self._get_zvol_path(volume)
       
   607 
       
   608         # Create a Logical Unit (LU)
       
   609         self._execute('/usr/sbin/stmfadm', 'create-lu', zvol)
       
   610         luid = self._get_luid(volume)
       
   611         if not luid:
       
   612             msg = (_("Failed to create logic unit for volume '%s'")
       
   613                    % volume['name'])
       
   614             raise exception.VolumeBackendAPIException(data=msg)
       
   615 
       
   616         wwns = self._get_wwns()
       
   617         wwn = wwns[0]
       
   618         target_group = self._check_wwn_tg(wwn)
       
   619         if target_group is None:
       
   620             target_group = 'tg-wwn-%s' % wwn
       
   621             if self._target_in_tg(wwn, None):
       
   622                 msg = (_("Target WWN '%s' has been found in another"
       
   623                          "target group, so it will not be added "
       
   624                          "into the expected target group '%s'.") %
       
   625                        (wwn, target_group))
       
   626                 raise exception.VolumeBackendAPIException(data=msg)
       
   627 
       
   628             # Create a target group for the wwn
       
   629             self._execute('/usr/sbin/stmfadm', 'create-tg', target_group)
       
   630 
       
   631             # Enable the target and add it to the 'tg-wwn-xxx' group
       
   632             self._execute('/usr/sbin/stmfadm', 'offline-target',
       
   633                           'wwn.%s' % wwn)
       
   634             self._execute('/usr/sbin/stmfadm', 'add-tg-member', '-g',
       
   635                           target_group, 'wwn.%s' % wwn)
       
   636             self._execute('/usr/sbin/stmfadm', 'online-target', 'wwn.%s' % wwn)
       
   637         assert self._target_in_tg(wwn, target_group)
       
   638 
       
   639         # Add a logical unit view entry
       
   640         # TODO(Strony): replace the auto assigned LUN with '-n' option
       
   641         if luid is not None:
       
   642             self._execute('/usr/sbin/stmfadm', 'add-view', '-t',
       
   643                           target_group, luid)
       
   644 
       
   645     def remove_export(self, context, volume):
       
   646         """Remove an export for a volume."""
       
   647         luid = self._get_luid(volume)
       
   648 
       
   649         if luid is not None:
       
   650             wwns = self._get_wwns()
       
   651             wwn = wwns[0]
       
   652             target_wwn = 'wwn.%s' % wwn
       
   653             target_group = 'tg-wwn-%s' % wwn
       
   654             view_lun = self._get_view_and_lun(luid)
       
   655             if view_lun['view']:
       
   656                 self._execute('/usr/sbin/stmfadm', 'remove-view', '-l',
       
   657                               luid, view_lun['view'])
       
   658 
       
   659             # Remove the target group when only one LU exists.
       
   660             if self._only_lu(luid):
       
   661                 if self._check_target(target_wwn, 'Channel'):
       
   662                     self._execute('/usr/sbin/stmfadm', 'offline-target',
       
   663                                   target_wwn)
       
   664                 if self._check_tg(target_group):
       
   665                     self._execute('/usr/sbin/stmfadm', 'delete-tg',
       
   666                                   target_group)
       
   667 
       
   668             # Remove the LU
       
   669             self._execute('/usr/sbin/stmfadm', 'delete-lu', luid)
       
   670 
       
   671     def _get_fc_properties(self, volume):
       
   672         """Get Fibre Channel configuration.
       
   673 
       
   674         :target_discovered:    boolean indicating whether discovery was used
       
   675         :target_wwn:           the world wide name of the FC port target
       
   676         :target_lun:           the lun assigned to the LU for the view entry
       
   677 
       
   678         """
       
   679         wwns = self._get_wwns()
       
   680         if not wwns:
       
   681             msg = (_("Could not determine fibre channel world wide "
       
   682                      "node names."))
       
   683             raise exception.VolumeBackendAPIException(data=msg)
       
   684 
       
   685         luid = self._get_luid(volume)
       
   686         if not luid:
       
   687             msg = (_("Failed to get logic unit for volume '%s'")
       
   688                    % volume['name'])
       
   689             raise exception.VolumeBackendAPIException(data=msg)
       
   690 
       
   691         properties = {}
       
   692 
       
   693         properties['target_discovered'] = True
       
   694         properties['target_wwn'] = wwns
       
   695         view_lun = self._get_view_and_lun(luid)
       
   696         if view_lun['lun']:
       
   697             properties['target_lun'] = view_lun['lun']
       
   698         return properties
       
   699 
       
   700     def initialize_connection(self, volume, connector):
       
   701         """Initializes the connection and returns connection info.
       
   702 
       
   703         The  driver returns a driver_volume_type of 'fibre_channel'.
       
   704         The target_wwn can be a single entry or a list of wwns that
       
   705         correspond to the list of remote wwn(s) that will export the volume.
       
   706         Example return values:
       
   707 
       
   708             {
       
   709                 'driver_volume_type': 'fibre_channel'
       
   710                 'data': {
       
   711                     'target_discovered': True,
       
   712                     'target_lun': 1,
       
   713                     'target_wwn': '1234567890123',
       
   714                 }
       
   715             }
       
   716 
       
   717             or
       
   718 
       
   719              {
       
   720                 'driver_volume_type': 'fibre_channel'
       
   721                 'data': {
       
   722                     'target_discovered': True,
       
   723                     'target_lun': 1,
       
   724                     'target_wwn': ['1234567890123', '0987654321321'],
       
   725                 }
       
   726             }
       
   727 
       
   728         """
       
   729         fc_properties = self._get_fc_properties(volume)
       
   730 
       
   731         return {
       
   732             'driver_volume_type': 'fibre_channel',
       
   733             'data': fc_properties
       
   734         }