components/openstack/cinder/files/solaris/zfs.py
branchs11u3-sru
changeset 4937 8f0976d7e40e
parent 4072 db0cec748ec0
child 5317 fa561e436e88
equal deleted inserted replaced
4936:79af241b4834 4937:8f0976d7e40e
    18 """
    18 """
    19 Drivers for Solaris ZFS operations in local and iSCSI modes
    19 Drivers for Solaris ZFS operations in local and iSCSI modes
    20 """
    20 """
    21 
    21 
    22 import abc
    22 import abc
       
    23 import fcntl
       
    24 import os
       
    25 import socket
       
    26 import subprocess
    23 import time
    27 import time
    24 
    28 
    25 from oslo.config import cfg
    29 from oslo.config import cfg
    26 
    30 
    27 from cinder import exception
    31 from cinder import exception
       
    32 from cinder.i18n import _
    28 from cinder.image import image_utils
    33 from cinder.image import image_utils
    29 from cinder.i18n import _
       
    30 from cinder.openstack.common import log as logging
    34 from cinder.openstack.common import log as logging
    31 from cinder.openstack.common import processutils
    35 from cinder.openstack.common import processutils
    32 from cinder.volume import driver
    36 from cinder.volume import driver
       
    37 from cinder.volume.drivers.san.san import SanDriver
    33 
    38 
    34 from solaris_install.target.size import Size
    39 from solaris_install.target.size import Size
    35 
    40 
    36 FLAGS = cfg.CONF
    41 FLAGS = cfg.CONF
    37 LOG = logging.getLogger(__name__)
    42 LOG = logging.getLogger(__name__)
    42                help='The base dataset for ZFS volumes.'), ]
    47                help='The base dataset for ZFS volumes.'), ]
    43 
    48 
    44 FLAGS.register_opts(solaris_zfs_opts)
    49 FLAGS.register_opts(solaris_zfs_opts)
    45 
    50 
    46 
    51 
    47 class ZFSVolumeDriver(driver.VolumeDriver):
    52 class ZFSVolumeDriver(SanDriver):
    48     """Local ZFS volume operations."""
    53     """Local ZFS volume operations."""
    49     protocol = 'local'
    54     protocol = 'local'
    50 
    55 
    51     def __init__(self, *args, **kwargs):
    56     def __init__(self, *args, **kwargs):
    52         super(ZFSVolumeDriver, self).__init__(*args, **kwargs)
    57         super(ZFSVolumeDriver, self).__init__(execute=self.solaris_execute,
       
    58                                               *args, **kwargs)
    53         self.configuration.append_config_values(solaris_zfs_opts)
    59         self.configuration.append_config_values(solaris_zfs_opts)
       
    60         self.run_local = self.configuration.san_is_local
       
    61         self.hostname = socket.gethostname()
       
    62 
       
    63     def solaris_execute(self, *cmd, **kwargs):
       
    64         """Execute the command locally or remotely."""
       
    65         if self.run_local:
       
    66             return processutils.execute(*cmd, **kwargs)
       
    67         else:
       
    68             return super(ZFSVolumeDriver, self)._run_ssh(cmd,
       
    69                                                          check_exit_code=True)
    54 
    70 
    55     def check_for_setup_error(self):
    71     def check_for_setup_error(self):
    56         """Check the setup error."""
    72         """Check the setup error."""
    57         pass
    73         pass
    58 
    74 
    59     def create_volume(self, volume):
    75     def create_volume(self, volume):
    60         """Create a volume."""
    76         """Create a volume."""
    61         size = '%sG' % volume['size']
    77         size = '%sG' % volume['size']
    62         zfs_volume = self._get_zfs_volume_name(volume)
    78         zfs_volume = self._get_zfs_volume_name(volume['name'])
    63 
    79 
    64         # Create a ZFS volume
    80         # Create a ZFS volume
    65         cmd = ['/usr/sbin/zfs', 'create', '-V', size, zfs_volume]
    81         cmd = ['/usr/sbin/zfs', 'create', '-V', size, zfs_volume]
    66         self._execute(*cmd)
    82         self._execute(*cmd)
    67         LOG.debug(_("Created ZFS volume '%s'") % volume['name'])
    83         LOG.debug(_("Created ZFS volume '%s'") % volume['name'])
    76                                     snapshot['volume_size']))
    92                                     snapshot['volume_size']))
    77             raise exception.InvalidInput(reason=exception_message)
    93             raise exception.InvalidInput(reason=exception_message)
    78 
    94 
    79         # Create a ZFS clone
    95         # Create a ZFS clone
    80         zfs_snapshot = self._get_zfs_snap_name(snapshot)
    96         zfs_snapshot = self._get_zfs_snap_name(snapshot)
    81         zfs_volume = self._get_zfs_volume_name(volume)
    97         zfs_volume = self._get_zfs_volume_name(volume['name'])
    82         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
    98         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
    83         self._execute(*cmd)
    99         self._execute(*cmd)
    84 
   100 
    85         LOG.debug(_("Created cloned volume '%s'") % volume['name'])
   101         LOG.debug(_("Created cloned volume '%s'") % volume['name'])
    86 
   102 
   100                         'name': 'tmp-snapshot-%s' % volume['id']}
   116                         'name': 'tmp-snapshot-%s' % volume['id']}
   101         self.create_snapshot(tmp_snapshot)
   117         self.create_snapshot(tmp_snapshot)
   102 
   118 
   103         # Create a ZFS clone
   119         # Create a ZFS clone
   104         zfs_snapshot = self._get_zfs_snap_name(tmp_snapshot)
   120         zfs_snapshot = self._get_zfs_snap_name(tmp_snapshot)
   105         zfs_volume = self._get_zfs_volume_name(volume)
   121         zfs_volume = self._get_zfs_volume_name(volume['name'])
   106         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
   122         cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
   107         self._execute(*cmd)
   123         self._execute(*cmd)
   108 
   124 
   109         LOG.debug(_("Created cloned volume '%s' from source volume '%s'")
   125         LOG.debug(_("Created cloned volume '%s' from source volume '%s'")
   110                   % (volume_name, src_volume_name))
   126                   % (volume_name, src_volume_name))
   121             (out, _err) = self._execute('/usr/bin/ls', zvol)
   137             (out, _err) = self._execute('/usr/bin/ls', zvol)
   122         except processutils.ProcessExecutionError:
   138         except processutils.ProcessExecutionError:
   123             LOG.debug(_("The volume path '%s' doesn't exist") % zvol)
   139             LOG.debug(_("The volume path '%s' doesn't exist") % zvol)
   124             return
   140             return
   125 
   141 
   126         zfs_volume = self._get_zfs_volume_name(volume)
   142         zfs_volume = self._get_zfs_volume_name(volume['name'])
   127         origin_snapshot = self._get_zfs_property('origin', zfs_volume)
   143         origin_snapshot = self._get_zfs_property('origin', zfs_volume)
   128         tmp_cloned_vol = False
   144         tmp_cloned_vol = False
   129 
   145 
   130         # Check if it is the temporary snapshot created for the cloned volume
   146         # Check if it is the temporary snapshot created for the cloned volume
   131         if origin_snapshot.startswith(self.configuration.zfs_volume_base):
   147         if origin_snapshot.startswith(self.configuration.zfs_volume_base):
   203 
   219 
   204         return self._stats
   220         return self._stats
   205 
   221 
   206     def copy_image_to_volume(self, context, volume, image_service, image_id):
   222     def copy_image_to_volume(self, context, volume, image_service, image_id):
   207         """Fetch the image from image_service and write it to the volume."""
   223         """Fetch the image from image_service and write it to the volume."""
   208         image_utils.fetch_to_raw(context,
   224         raise NotImplementedError()
   209                                  image_service,
       
   210                                  image_id,
       
   211                                  self.local_path(volume))
       
   212 
   225 
   213     def copy_volume_to_image(self, context, volume, image_service, image_meta):
   226     def copy_volume_to_image(self, context, volume, image_service, image_meta):
   214         """Copy the volume to the specified image."""
   227         """Copy the volume to the specified image."""
   215         image_utils.upload_volume(context,
   228         raise NotImplementedError()
   216                                   image_service,
       
   217                                   image_meta,
       
   218                                   self.local_path(volume))
       
   219 
   229 
   220     def _get_zfs_property(self, prop, dataset):
   230     def _get_zfs_property(self, prop, dataset):
   221         """Get the value of property for the dataset."""
   231         """Get the value of property for the dataset."""
   222         (out, _err) = self._execute('/usr/sbin/zfs', 'get', '-H', '-o',
   232         try:
   223                                     'value', prop, dataset)
   233             (out, _err) = self._execute('/usr/sbin/zfs', 'get', '-H', '-o',
   224         return out.rstrip()
   234                                         'value', prop, dataset)
       
   235             return out.rstrip()
       
   236         except processutils.ProcessExecutionError:
       
   237             LOG.info(_("Failed to get the property '%s' of the dataset '%s'") %
       
   238                      (prop, dataset))
       
   239             return None
   225 
   240 
   226     def _get_zfs_snap_name(self, snapshot):
   241     def _get_zfs_snap_name(self, snapshot):
   227         """Get the snapshot path."""
   242         """Get the snapshot path."""
   228         return "%s/%s@%s" % (self.configuration.zfs_volume_base,
   243         return "%s/%s@%s" % (self.configuration.zfs_volume_base,
   229                              snapshot['volume_name'], snapshot['name'])
   244                              snapshot['volume_name'], snapshot['name'])
   230 
   245 
   231     def _get_zfs_volume_name(self, volume):
   246     def _get_zfs_volume_name(self, volume_name):
   232         """Add the pool name to get the ZFS volume."""
   247         """Add the pool name to get the ZFS volume."""
   233         return "%s/%s" % (self.configuration.zfs_volume_base,
   248         return "%s/%s" % (self.configuration.zfs_volume_base,
   234                           volume['name'])
   249                           volume_name)
       
   250 
       
   251     def _piped_execute(self, cmd1, cmd2):
       
   252         """Pipe output of cmd1 into cmd2."""
       
   253         LOG.debug(_("Piping cmd1='%s' into cmd2='%s'") %
       
   254                   (' '.join(cmd1), ' '.join(cmd2)))
       
   255 
       
   256         try:
       
   257             p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE,
       
   258                                   stderr=subprocess.PIPE)
       
   259         except:
       
   260             LOG.error(_("_piped_execute '%s' failed.") % (cmd1))
       
   261             raise
       
   262 
       
   263         # Set the pipe to be blocking because evenlet.green.subprocess uses
       
   264         # the non-blocking pipe.
       
   265         flags = fcntl.fcntl(p1.stdout, fcntl.F_GETFL) & (~os.O_NONBLOCK)
       
   266         fcntl.fcntl(p1.stdout, fcntl.F_SETFL, flags)
       
   267 
       
   268         p2 = subprocess.Popen(cmd2, stdin=p1.stdout,
       
   269                               stdout=subprocess.PIPE,
       
   270                               stderr=subprocess.PIPE)
       
   271         p1.stdout.close()
       
   272         stdout, stderr = p2.communicate()
       
   273         if p2.returncode:
       
   274             msg = (_("_piped_execute failed with the info '%s' and '%s'.") %
       
   275                    (stdout, stderr))
       
   276             raise exception.VolumeBackendAPIException(data=msg)
       
   277 
       
   278     def _zfs_send_recv(self, src, dst, remote=False):
       
   279         """Replicate the ZFS dataset by calling zfs send/recv cmd"""
       
   280         src_snapshot = {'volume_name': src['name'],
       
   281                         'name': 'tmp-snapshot-%s' % src['id']}
       
   282         src_snapshot_name = self._get_zfs_snap_name(src_snapshot)
       
   283         prop_type = self._get_zfs_property('type', src_snapshot_name)
       
   284         # Delete the temporary snapshot if it already exists
       
   285         if prop_type == 'snapshot':
       
   286             self.delete_snapshot(src_snapshot)
       
   287         # Create a temporary snapshot of volume
       
   288         self.create_snapshot(src_snapshot)
       
   289         src_snapshot_name = self._get_zfs_snap_name(src_snapshot)
       
   290 
       
   291         cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name]
       
   292         cmd2 = ['/usr/sbin/zfs', 'receive', dst]
       
   293         self._piped_execute(cmd1, cmd2)
       
   294 
       
   295         # Delete the temporary src snapshot and dst snapshot
       
   296         self.delete_snapshot(src_snapshot)
       
   297         dst_snapshot_name = "%s@tmp-snapshot-%s" % (dst, src['id'])
       
   298         cmd = ['/usr/sbin/zfs', 'destroy', dst_snapshot_name]
       
   299         self._execute(*cmd)
   235 
   300 
   236     def _get_zvol_path(self, volume):
   301     def _get_zvol_path(self, volume):
   237         """Get the ZFS volume path."""
   302         """Get the ZFS volume path."""
   238         return "/dev/zvol/rdsk/%s" % self._get_zfs_volume_name(volume)
   303         return "/dev/zvol/rdsk/%s" % self._get_zfs_volume_name(volume['name'])
   239 
   304 
   240     def _update_volume_stats(self):
   305     def _update_volume_stats(self):
   241         """Retrieve volume status info."""
   306         """Retrieve volume status info."""
   242 
   307 
   243         LOG.debug(_("Updating volume status"))
   308         LOG.debug(_("Updating volume status"))
   254         avail_size = self._get_zfs_property('avail', dataset)
   319         avail_size = self._get_zfs_property('avail', dataset)
   255         stats['total_capacity_gb'] = \
   320         stats['total_capacity_gb'] = \
   256             (Size(used_size) + Size(avail_size)).get(Size.gb_units)
   321             (Size(used_size) + Size(avail_size)).get(Size.gb_units)
   257         stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units)
   322         stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units)
   258         stats['reserved_percentage'] = self.configuration.reserved_percentage
   323         stats['reserved_percentage'] = self.configuration.reserved_percentage
       
   324         stats['location_info'] =\
       
   325             ('ZFSVolumeDriver:%(hostname)s:%(zfs_volume_base)s' %
       
   326              {'hostname': self.hostname,
       
   327               'zfs_volume_base': self.configuration.zfs_volume_base})
   259 
   328 
   260         self._stats = stats
   329         self._stats = stats
   261 
   330 
   262     def extend_volume(self, volume, new_size):
   331     def extend_volume(self, volume, new_size):
   263         """Extend an existing volume's size."""
   332         """Extend an existing volume's size."""
   264         volsize_str = 'volsize=%sg' % new_size
   333         volsize_str = 'volsize=%sg' % new_size
   265         zfs_volume = self._get_zfs_volume_name(volume)
   334         zfs_volume = self._get_zfs_volume_name(volume['name'])
   266         try:
   335         try:
   267             self._execute('/usr/sbin/zfs', 'set', volsize_str, zfs_volume)
   336             self._execute('/usr/sbin/zfs', 'set', volsize_str, zfs_volume)
   268         except Exception:
   337         except Exception:
   269             msg = (_("Failed to extend volume size to %(new_size)s GB.")
   338             msg = (_("Failed to extend volume size to %(new_size)s GB.")
   270                    % {'new_size': new_size})
   339                    % {'new_size': new_size})
   271             raise exception.VolumeBackendAPIException(data=msg)
   340             raise exception.VolumeBackendAPIException(data=msg)
       
   341 
       
   342     def rename_volume(self, src, dst):
       
   343         """Rename the volume from src to dst in the same zpool."""
       
   344         cmd = ['/usr/sbin/zfs', 'rename', src, dst]
       
   345         self._execute(*cmd)
       
   346 
       
   347         LOG.debug(_("Rename the volume '%s' to '%s'") % (src, dst))
       
   348 
       
   349     def migrate_volume(self, context, volume, host):
       
   350         """Migrate the volume among different backends on the same server.
       
   351 
       
   352         The volume migration can only run locally by calling zfs send/recv
       
   353         cmds and the specified host needs to be on the same server with the
       
   354         host. But, one exception is when the src and dst volume are located
       
   355         under the same zpool locally or remotely, the migration will be done
       
   356         by just renaming the volume.
       
   357         :param context: context
       
   358         :param volume: a dictionary describing the volume to migrate
       
   359         :param host: a dictionary describing the host to migrate to
       
   360         """
       
   361         false_ret = (False, None)
       
   362         if volume['status'] != 'available':
       
   363             LOG.debug(_("Status of volume '%s' is '%s', not 'available'.") %
       
   364                       (volume['name'], volume['status']))
       
   365             return false_ret
       
   366 
       
   367         if 'capabilities' not in host or \
       
   368            'location_info' not in host['capabilities']:
       
   369             LOG.debug(_("No location_info or capabilities are in host info"))
       
   370             return false_ret
       
   371 
       
   372         info = host['capabilities']['location_info']
       
   373         if (self.hostname != info.split(':')[1]):
       
   374             LOG.debug(_("Migration between two different servers '%s' and "
       
   375                       "'%s' is not supported yet.") %
       
   376                       (self.hostname, info.split(':')[1]))
       
   377             return false_ret
       
   378 
       
   379         dst_volume = "%s/%s" % (info.split(':')[-1], volume['name'])
       
   380         src_volume = self._get_zfs_volume_name(volume['name'])
       
   381         # check if the src and dst volume are under the same zpool
       
   382         if (src_volume.split('/')[0] == dst_volume.split('/')[0]):
       
   383             self.rename_volume(src_volume, dst_volume)
       
   384         else:
       
   385             self._zfs_send_recv(volume, dst_volume)
       
   386             # delete the source volume
       
   387             self.delete_volume(volume)
       
   388 
       
   389         provider_location = {}
       
   390         return (True, provider_location)
   272 
   391 
   273 
   392 
   274 class STMFDriver(ZFSVolumeDriver):
   393 class STMFDriver(ZFSVolumeDriver):
   275     """Abstract base class for common COMSTAR operations."""
   394     """Abstract base class for common COMSTAR operations."""
   276     __metaclass__ = abc.ABCMeta
   395     __metaclass__ = abc.ABCMeta
   490         properties = {}
   609         properties = {}
   491 
   610 
   492         properties['target_discovered'] = True
   611         properties['target_discovered'] = True
   493         properties['target_iqn'] = target_name
   612         properties['target_iqn'] = target_name
   494 
   613 
       
   614         # Here the san_is_local means that the cinder-volume runs in the
       
   615         # iSCSI target with iscsi_ip_address.
       
   616         if self.configuration.san_is_local:
       
   617             target_ip = self.configuration.iscsi_ip_address
       
   618         else:
       
   619             target_ip = self.configuration.san_ip
   495         properties['target_portal'] = ('%s:%d' %
   620         properties['target_portal'] = ('%s:%d' %
   496                                        (self.configuration.iscsi_ip_address,
   621                                        (target_ip,
   497                                         self.configuration.iscsi_port))
   622                                         self.configuration.iscsi_port))
   498         view_lun = self._get_view_and_lun(luid)
   623         view_lun = self._get_view_and_lun(luid)
   499         if view_lun['lun'] is not None:
   624         if view_lun['lun'] is not None:
   500             properties['target_lun'] = view_lun['lun']
   625             properties['target_lun'] = view_lun['lun']
   501         properties['volume_id'] = volume['id']
   626         properties['volume_id'] = volume['id']