]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add missing file after merge.
authorThomas Goirand <zigo@debian.org>
Fri, 4 Mar 2016 03:46:53 +0000 (11:46 +0800)
committerThomas Goirand <zigo@debian.org>
Fri, 4 Mar 2016 03:46:53 +0000 (11:46 +0800)
cinder/volume/drivers/zfssa/zfssaiscsi.py [new file with mode: 0644]

diff --git a/cinder/volume/drivers/zfssa/zfssaiscsi.py b/cinder/volume/drivers/zfssa/zfssaiscsi.py
new file mode 100644 (file)
index 0000000..c392351
--- /dev/null
@@ -0,0 +1,1193 @@
+# Copyright (c) 2014, 2016, 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
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+ZFS Storage Appliance Cinder Volume Driver
+"""
+import ast
+import math
+
+from oslo_config import cfg
+from oslo_log import log
+from oslo_serialization import base64
+from oslo_utils import excutils
+from oslo_utils import units
+import six
+
+from cinder import exception
+from cinder import utils
+from cinder.i18n import _, _LE, _LI, _LW
+from cinder.image import image_utils
+from cinder.volume import driver
+from cinder.volume.drivers.san import san
+from cinder.volume.drivers.zfssa import zfssarest
+from cinder.volume import volume_types
+
+import taskflow.engines
+from taskflow.patterns import linear_flow as lf
+from taskflow import task
+
+CONF = cfg.CONF
+LOG = log.getLogger(__name__)
+
+ZFSSA_OPTS = [
+    cfg.StrOpt('zfssa_pool',
+               help='Storage pool name.'),
+    cfg.StrOpt('zfssa_project',
+               help='Project name.'),
+    cfg.StrOpt('zfssa_lun_volblocksize', default='8k',
+               choices=['512', '1k', '2k', '4k', '8k', '16k', '32k', '64k',
+                        '128k'],
+               help='Block size.'),
+    cfg.BoolOpt('zfssa_lun_sparse', default=False,
+                help='Flag to enable sparse (thin-provisioned): True, False.'),
+    cfg.StrOpt('zfssa_lun_compression', default='off',
+               choices=['off', 'lzjb', 'gzip-2', 'gzip', 'gzip-9'],
+               help='Data compression.'),
+    cfg.StrOpt('zfssa_lun_logbias', default='latency',
+               choices=['latency', 'throughput'],
+               help='Synchronous write bias.'),
+    cfg.StrOpt('zfssa_initiator_group', default='',
+               help='iSCSI initiator group.'),
+    cfg.StrOpt('zfssa_initiator', default='',
+               help='iSCSI initiator IQNs. (comma separated)'),
+    cfg.StrOpt('zfssa_initiator_user', default='',
+               help='iSCSI initiator CHAP user (name).'),
+    cfg.StrOpt('zfssa_initiator_password', default='',
+               help='Secret of the iSCSI initiator CHAP user.', secret=True),
+    cfg.StrOpt('zfssa_initiator_config', default='',
+               help='iSCSI initiators configuration.'),
+    cfg.StrOpt('zfssa_target_group', default='tgt-grp',
+               help='iSCSI target group name.'),
+    cfg.StrOpt('zfssa_target_user', default='',
+               help='iSCSI target CHAP user (name).'),
+    cfg.StrOpt('zfssa_target_password', default='', secret=True,
+               help='Secret of the iSCSI target CHAP user.'),
+    cfg.StrOpt('zfssa_target_portal',
+               help='iSCSI target portal (Data-IP:Port, w.x.y.z:3260).'),
+    cfg.StrOpt('zfssa_target_interfaces',
+               help='Network interfaces of iSCSI targets. (comma separated)'),
+    cfg.IntOpt('zfssa_rest_timeout',
+               help='REST connection timeout. (seconds)'),
+    cfg.StrOpt('zfssa_replication_ip', default='',
+               help='IP address used for replication data. (maybe the same as '
+                    'data ip)'),
+    cfg.BoolOpt('zfssa_enable_local_cache', default=True,
+                help='Flag to enable local caching: True, False.'),
+    cfg.StrOpt('zfssa_cache_project', default='os-cinder-cache',
+               help='Name of ZFSSA project where cache volumes are stored.'),
+    cfg.StrOpt('zfssa_manage_policy', default='loose',
+               choices=['loose', 'strict'],
+               help='Driver policy for volume manage.')
+]
+
+CONF.register_opts(ZFSSA_OPTS)
+
+ZFSSA_LUN_SPECS = {
+    'zfssa:volblocksize',
+    'zfssa:sparse',
+    'zfssa:compression',
+    'zfssa:logbias',
+}
+
+
+def factory_zfssa():
+    return zfssarest.ZFSSAApi()
+
+
+class ZFSSAISCSIDriver(driver.ISCSIDriver):
+    """ZFSSA Cinder iSCSI volume driver.
+
+    Version history:
+    1.0.1:
+        Backend enabled volume migration.
+        Local cache feature.
+    1.0.2:
+        Volume manage/unmanage support.
+    """
+    VERSION = '1.0.2'
+    protocol = 'iSCSI'
+
+    def __init__(self, *args, **kwargs):
+        super(ZFSSAISCSIDriver, self).__init__(*args, **kwargs)
+        self.configuration.append_config_values(ZFSSA_OPTS)
+        self.configuration.append_config_values(san.san_opts)
+        self.zfssa = None
+        self.tgt_zfssa = None
+        self._stats = None
+        self.tgtiqn = None
+
+    def _get_target_alias(self):
+        """return target alias."""
+        return self.configuration.zfssa_target_group
+
+    def do_setup(self, context):
+        """Setup - create multiple elements.
+
+        Project, initiators, initiatorgroup, target and targetgroup.
+        """
+        lcfg = self.configuration
+        LOG.info(_LI('Connecting to host: %s.'), lcfg.san_ip)
+        self.zfssa = factory_zfssa()
+        self.tgt_zfssa = factory_zfssa()
+        self.zfssa.set_host(lcfg.san_ip, timeout=lcfg.zfssa_rest_timeout)
+        auth_str = '%s:%s' % (lcfg.san_login, lcfg.san_password)
+        auth_str = base64.encode_as_text(auth_str)
+        self.zfssa.login(auth_str)
+
+        self.zfssa.create_project(lcfg.zfssa_pool, lcfg.zfssa_project,
+                                  compression=lcfg.zfssa_lun_compression,
+                                  logbias=lcfg.zfssa_lun_logbias)
+
+        schemas = [
+            {'property': 'cinder_managed',
+             'description': 'Managed by Cinder',
+             'type': 'Boolean'}]
+
+        if lcfg.zfssa_enable_local_cache:
+            self.zfssa.create_project(lcfg.zfssa_pool,
+                                      lcfg.zfssa_cache_project,
+                                      compression=lcfg.zfssa_lun_compression,
+                                      logbias=lcfg.zfssa_lun_logbias)
+            schemas.extend([
+                {'property': 'image_id',
+                 'description': 'OpenStack image ID',
+                 'type': 'String'},
+                {'property': 'updated_at',
+                 'description': 'Most recent updated time of image',
+                 'type': 'String'}])
+
+        self.zfssa.create_schemas(schemas)
+
+        if (lcfg.zfssa_initiator_config != ''):
+            initiator_config = ast.literal_eval(lcfg.zfssa_initiator_config)
+            for initiator_group in initiator_config:
+                zfssa_initiator_group = initiator_group
+                for zfssa_initiator in initiator_config[zfssa_initiator_group]:
+                    self.zfssa.create_initiator(zfssa_initiator['iqn'],
+                                                zfssa_initiator_group + '-' +
+                                                zfssa_initiator['iqn'],
+                                                chapuser=
+                                                zfssa_initiator['user'],
+                                                chapsecret=
+                                                zfssa_initiator['password'])
+                    if (zfssa_initiator_group != 'default'):
+                        self.zfssa.add_to_initiatorgroup(
+                            zfssa_initiator['iqn'],
+                            zfssa_initiator_group)
+        else:
+            LOG.warning(_LW('zfssa_initiator_config not found. '
+                            'Using deprecated configuration options.'))
+            if (not lcfg.zfssa_initiator and
+                (not lcfg.zfssa_initiator_group and
+                 lcfg.zfssa_initiator_group != 'default')):
+                LOG.error(_LE('zfssa_initiator cannot be empty when '
+                              'creating a zfssa_initiator_group.'))
+                raise exception.InvalidConfigurationValue(
+                    value='',
+                    option='zfssa_initiator')
+
+            if (lcfg.zfssa_initiator != '' and
+                (lcfg.zfssa_initiator_group == '' or
+                 lcfg.zfssa_initiator_group == 'default')):
+                LOG.warning(_LW('zfssa_initiator: %(ini)s'
+                                ' wont be used on '
+                                'zfssa_initiator_group= %(inigrp)s.'),
+                            {'ini': lcfg.zfssa_initiator,
+                             'inigrp': lcfg.zfssa_initiator_group})
+
+            # Setup initiator and initiator group
+            if (lcfg.zfssa_initiator != '' and
+               lcfg.zfssa_initiator_group != '' and
+               lcfg.zfssa_initiator_group != 'default'):
+                for initiator in lcfg.zfssa_initiator.split(','):
+                    initiator = initiator.strip()
+                    self.zfssa.create_initiator(
+                        initiator, lcfg.zfssa_initiator_group + '-' +
+                        initiator, chapuser=lcfg.zfssa_initiator_user,
+                        chapsecret=lcfg.zfssa_initiator_password)
+                    self.zfssa.add_to_initiatorgroup(
+                        initiator, lcfg.zfssa_initiator_group)
+
+        # Parse interfaces
+        interfaces = []
+        for interface in lcfg.zfssa_target_interfaces.split(','):
+            if interface == '':
+                continue
+            interfaces.append(interface)
+
+        # Setup target and target group
+        iqn = self.zfssa.create_target(
+            self._get_target_alias(),
+            interfaces,
+            tchapuser=lcfg.zfssa_target_user,
+            tchapsecret=lcfg.zfssa_target_password)
+
+        self.zfssa.add_to_targetgroup(iqn, lcfg.zfssa_target_group)
+
+        if lcfg.zfssa_manage_policy not in ("loose", "strict"):
+            err_msg = (_("zfssa_manage_policy property needs to be set to"
+                         " 'strict' or 'loose'. Current value is: %s.") %
+                       lcfg.zfssa_manage_policy)
+            LOG.error(err_msg)
+            raise exception.InvalidInput(reason=err_msg)
+
+    def check_for_setup_error(self):
+        """Check that driver can login.
+
+        Check also pool, project, initiators, initiatorgroup, target and
+        targetgroup.
+        """
+        lcfg = self.configuration
+
+        self.zfssa.verify_pool(lcfg.zfssa_pool)
+        self.zfssa.verify_project(lcfg.zfssa_pool, lcfg.zfssa_project)
+
+        if (lcfg.zfssa_initiator_config != ''):
+            initiator_config = ast.literal_eval(lcfg.zfssa_initiator_config)
+            for initiator_group in initiator_config:
+                zfssa_initiator_group = initiator_group
+                for zfssa_initiator in initiator_config[zfssa_initiator_group]:
+                    self.zfssa.verify_initiator(zfssa_initiator['iqn'])
+        else:
+            if (lcfg.zfssa_initiator != '' and
+               lcfg.zfssa_initiator_group != '' and
+               lcfg.zfssa_initiator_group != 'default'):
+                for initiator in lcfg.zfssa_initiator.split(','):
+                    self.zfssa.verify_initiator(initiator)
+
+            self.zfssa.verify_target(self._get_target_alias())
+
+    def _get_provider_info(self, volume, lun=None):
+        """Return provider information."""
+        lcfg = self.configuration
+        project = lcfg.zfssa_project
+        if ((lcfg.zfssa_enable_local_cache is True) and
+                (volume['name'].startswith('os-cache-vol-'))):
+            project = lcfg.zfssa_cache_project
+
+        if lun is None:
+            lun = self.zfssa.get_lun(lcfg.zfssa_pool,
+                                     project,
+                                     volume['name'])
+
+        if isinstance(lun['number'], list):
+            lun['number'] = lun['number'][0]
+
+        if self.tgtiqn is None:
+            self.tgtiqn = self.zfssa.get_target(self._get_target_alias())
+
+        loc = "%s %s %s" % (lcfg.zfssa_target_portal, self.tgtiqn,
+                            lun['number'])
+        LOG.debug('_get_provider_info: provider_location: %s', loc)
+        provider = {'provider_location': loc}
+        if lcfg.zfssa_target_user != '' and lcfg.zfssa_target_password != '':
+            provider['provider_auth'] = ('CHAP %s %s' %
+                                         (lcfg.zfssa_target_user,
+                                          lcfg.zfssa_target_password))
+
+        return provider
+
+    def create_volume(self, volume):
+        """Create a volume on ZFSSA."""
+        LOG.debug('zfssa.create_volume: volume=' + volume['name'])
+        lcfg = self.configuration
+        volsize = str(volume['size']) + 'g'
+        specs = self._get_voltype_specs(volume)
+        specs.update({'custom:cinder_managed': True})
+        self.zfssa.create_lun(lcfg.zfssa_pool,
+                              lcfg.zfssa_project,
+                              volume['name'],
+                              volsize,
+                              lcfg.zfssa_target_group,
+                              specs)
+
+    def delete_volume(self, volume):
+        """Deletes a volume with the given volume['name']."""
+        LOG.debug('zfssa.delete_volume: name=%s', volume['name'])
+        lcfg = self.configuration
+
+        try:
+            lun2del = self.zfssa.get_lun(lcfg.zfssa_pool,
+                                         lcfg.zfssa_project,
+                                         volume['name'])
+        except exception.VolumeBackendAPIException as ex:
+            # NOTE(jdg): This will log an error and continue
+            # if for some reason the volume no longer exists
+            # on the backend
+            if 'Error Getting Volume' in ex.message:
+                LOG.error(_LE("Volume ID %s was not found on "
+                              "the zfssa device while attempting "
+                              "delete_volume operation."), volume['id'])
+                return
+
+        # Delete clone temp snapshot. see create_cloned_volume()
+        if 'origin' in lun2del and 'id' in volume:
+            if lun2del['nodestroy']:
+                self.zfssa.set_lun_props(lcfg.zfssa_pool,
+                                         lcfg.zfssa_project,
+                                         volume['name'],
+                                         nodestroy=False)
+
+            tmpsnap = 'tmp-snapshot-%s' % volume['id']
+            if lun2del['origin']['snapshot'] == tmpsnap:
+                self.zfssa.delete_snapshot(lcfg.zfssa_pool,
+                                           lcfg.zfssa_project,
+                                           lun2del['origin']['share'],
+                                           lun2del['origin']['snapshot'])
+                return
+
+        self.zfssa.delete_lun(pool=lcfg.zfssa_pool,
+                              project=lcfg.zfssa_project,
+                              lun=volume['name'])
+
+        if ('origin' in lun2del and
+                lun2del['origin']['project'] == lcfg.zfssa_cache_project):
+            self._check_origin(lun2del, volume['name'])
+
+    def create_snapshot(self, snapshot):
+        """Creates a snapshot of a volume.
+
+        Snapshot name: snapshot['name']
+        Volume name: snapshot['volume_name']
+        """
+        LOG.debug('zfssa.create_snapshot: snapshot=%s', snapshot['name'])
+        lcfg = self.configuration
+        self.zfssa.create_snapshot(lcfg.zfssa_pool,
+                                   lcfg.zfssa_project,
+                                   snapshot['volume_name'],
+                                   snapshot['name'])
+
+    def delete_snapshot(self, snapshot):
+        """Deletes a snapshot."""
+        LOG.debug('zfssa.delete_snapshot: snapshot=%s', snapshot['name'])
+        lcfg = self.configuration
+        numclones = self.zfssa.num_clones(lcfg.zfssa_pool,
+                                          lcfg.zfssa_project,
+                                          snapshot['volume_name'],
+                                          snapshot['name'])
+        if numclones > 0:
+            LOG.error(_LE('Snapshot %s: has clones'), snapshot['name'])
+            raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
+
+        self.zfssa.delete_snapshot(lcfg.zfssa_pool,
+                                   lcfg.zfssa_project,
+                                   snapshot['volume_name'],
+                                   snapshot['name'])
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Creates a volume from a snapshot - clone a snapshot."""
+        LOG.debug('zfssa.create_volume_from_snapshot: volume=%s',
+                  volume['name'])
+        LOG.debug('zfssa.create_volume_from_snapshot: snapshot=%s',
+                  snapshot['name'])
+        if not self._verify_clone_size(snapshot, volume['size'] * units.Gi):
+            exception_msg = (_('Error verifying clone size on '
+                               'Volume clone: %(clone)s '
+                               'Size: %(size)d on'
+                               'Snapshot: %(snapshot)s')
+                             % {'clone': volume['name'],
+                                'size': volume['size'],
+                                'snapshot': snapshot['name']})
+            LOG.error(exception_msg)
+            raise exception.InvalidInput(reason=exception_msg)
+
+        lcfg = self.configuration
+        self.zfssa.clone_snapshot(lcfg.zfssa_pool,
+                                  lcfg.zfssa_project,
+                                  snapshot['volume_name'],
+                                  snapshot['name'],
+                                  lcfg.zfssa_project,
+                                  volume['name'])
+
+    def _update_volume_status(self):
+        """Retrieve status info from volume group."""
+        LOG.debug("Updating volume status")
+        self._stats = None
+        data = {}
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        data["volume_backend_name"] = backend_name or self.__class__.__name__
+        data["vendor_name"] = 'Oracle'
+        data["driver_version"] = self.VERSION
+        data["storage_protocol"] = self.protocol
+
+        lcfg = self.configuration
+        (avail, total) = self.zfssa.get_project_stats(lcfg.zfssa_pool,
+                                                      lcfg.zfssa_project)
+        if avail is None or total is None:
+            return
+
+        host = lcfg.san_ip
+        pool = lcfg.zfssa_pool
+        project = lcfg.zfssa_project
+        auth_str = '%s:%s' % (lcfg.san_login, lcfg.san_password)
+        auth_str = base64.encode_as_text(auth_str)
+        zfssa_tgt_group = lcfg.zfssa_target_group
+        repl_ip = lcfg.zfssa_replication_ip
+
+        data['location_info'] = "%s:%s:%s:%s:%s:%s" % (host, auth_str, pool,
+                                                       project,
+                                                       zfssa_tgt_group,
+                                                       repl_ip)
+
+        data['total_capacity_gb'] = int(total) / units.Gi
+        data['free_capacity_gb'] = int(avail) / units.Gi
+        data['reserved_percentage'] = 0
+        data['QoS_support'] = False
+
+        pool_details = self.zfssa.get_pool_details(lcfg.zfssa_pool)
+        data['zfssa_poolprofile'] = pool_details['profile']
+        data['zfssa_volblocksize'] = lcfg.zfssa_lun_volblocksize
+        data['zfssa_sparse'] = six.text_type(lcfg.zfssa_lun_sparse)
+        data['zfssa_compression'] = lcfg.zfssa_lun_compression
+        data['zfssa_logbias'] = lcfg.zfssa_lun_logbias
+
+        self._stats = data
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume status.
+
+        If 'refresh' is True, run update the stats first.
+        """
+        if refresh:
+            self._update_volume_status()
+        return self._stats
+
+    def create_export(self, context, volume, connector):
+        pass
+
+    def remove_export(self, context, volume):
+        pass
+
+    def ensure_export(self, context, volume):
+        pass
+
+    def extend_volume(self, volume, new_size):
+        """Driver entry point to extent volume size."""
+        LOG.debug('extend_volume: volume name: %s', volume['name'])
+        lcfg = self.configuration
+        self.zfssa.set_lun_props(lcfg.zfssa_pool,
+                                 lcfg.zfssa_project,
+                                 volume['name'],
+                                 volsize=new_size * units.Gi)
+
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a clone of the specified volume."""
+        zfssa_snapshot = {'volume_name': src_vref['name'],
+                          'name': 'tmp-snapshot-%s' % volume['id']}
+        self.create_snapshot(zfssa_snapshot)
+        try:
+            self.create_volume_from_snapshot(volume, zfssa_snapshot)
+        except exception.VolumeBackendAPIException:
+            LOG.error(_LE('Clone Volume:'
+                          '%(volume)s failed from source volume:'
+                          '%(src_vref)s'),
+                      {'volume': volume['name'],
+                       'src_vref': src_vref['name']})
+            # Cleanup snapshot
+            self.delete_snapshot(zfssa_snapshot)
+
+    @utils.synchronized('zfssaiscsi', external=True)
+    def clone_image(self, context, volume,
+                    image_location, image_meta,
+                    image_service):
+        """Create a volume efficiently from an existing image.
+
+        Verify the image ID being used:
+
+        (1) If there is no existing cache volume, create one and transfer
+        image data to it. Take a snapshot.
+
+        (2) If a cache volume already exists, verify if it is either alternated
+        or updated. If so try to remove it, raise exception if removal fails.
+        Create a new cache volume as in (1).
+
+        Clone a volume from the cache volume and returns it to Cinder.
+
+        A file lock is placed on this method to prevent:
+
+        (a) a race condition when a cache volume has been verified, but then
+        gets deleted before it is cloned.
+
+        (b) failure of subsequent clone_image requests if the first request is
+        still pending.
+        """
+        LOG.debug('Cloning image %(image)s to volume %(volume)s',
+                  {'image': image_meta['id'], 'volume': volume['name']})
+        lcfg = self.configuration
+        cachevol_size = 0
+        if not lcfg.zfssa_enable_local_cache:
+            return None, False
+
+        with image_utils.TemporaryImages.fetch(image_service,
+                                               context,
+                                               image_meta['id']) as tmp_image:
+            info = image_utils.qemu_img_info(tmp_image)
+            cachevol_size = int(math.ceil(float(info.virtual_size) / units.Gi))
+
+        if cachevol_size > volume['size']:
+            exception_msg = (_LE('Image size %(img_size)dGB is larger '
+                                 'than volume size %(vol_size)dGB.'),
+                             {'img_size': cachevol_size,
+                              'vol_size': volume['size']})
+            LOG.error(exception_msg)
+            return None, False
+
+        specs = self._get_voltype_specs(volume)
+        cachevol_props = {'size': cachevol_size}
+
+        try:
+            cache_vol, cache_snap = self._verify_cache_volume(context,
+                                                              image_meta,
+                                                              image_service,
+                                                              specs,
+                                                              cachevol_props)
+            # A cache volume and a snapshot should be ready by now
+            # Create a clone from the cache volume
+            self.zfssa.clone_snapshot(lcfg.zfssa_pool,
+                                      lcfg.zfssa_cache_project,
+                                      cache_vol,
+                                      cache_snap,
+                                      lcfg.zfssa_project,
+                                      volume['name'])
+            if cachevol_size < volume['size']:
+                self.extend_volume(volume, volume['size'])
+        except exception.VolumeBackendAPIException as exc:
+            exception_msg = (_LE('Cannot clone image %(image)s to '
+                                 'volume %(volume)s. Error: %(error)s.'),
+                             {'volume': volume['name'],
+                              'image': image_meta['id'],
+                              'error': exc.message})
+            LOG.error(exception_msg)
+            return None, False
+
+        return None, True
+
+    def _verify_cache_volume(self, context, img_meta,
+                             img_service, specs, cachevol_props):
+        """Verify if we have a cache volume that we want.
+
+        If we don't, create one.
+        If we do, check if it's been updated:
+          * If so, delete it and recreate a new volume
+          * If not, we are good.
+
+        If it's out of date, delete it and create a new one.
+        After the function returns, there should be a cache volume available,
+        ready for cloning.
+        """
+        lcfg = self.configuration
+        cachevol_name = 'os-cache-vol-%s' % img_meta['id']
+        cachesnap_name = 'image-%s' % img_meta['id']
+        cachevol_meta = {
+            'cache_name': cachevol_name,
+            'snap_name': cachesnap_name,
+        }
+        cachevol_props.update(cachevol_meta)
+        cache_vol, cache_snap = None, None
+        updated_at = six.text_type(img_meta['updated_at'].isoformat())
+        LOG.debug('Verifying cache volume %s:', cachevol_name)
+
+        try:
+            cache_vol = self.zfssa.get_lun(lcfg.zfssa_pool,
+                                           lcfg.zfssa_cache_project,
+                                           cachevol_name)
+            if (not cache_vol.get('updated_at', None) or
+                    not cache_vol.get('image_id', None)):
+                exc_msg = (_('Cache volume %s does not have required '
+                             'properties') % cachevol_name)
+                LOG.error(exc_msg)
+                raise exception.VolumeBackendAPIException(data=exc_msg)
+
+            cache_snap = self.zfssa.get_lun_snapshot(lcfg.zfssa_pool,
+                                                     lcfg.zfssa_cache_project,
+                                                     cachevol_name,
+                                                     cachesnap_name)
+        except exception.VolumeNotFound:
+            # There is no existing cache volume, create one:
+            return self._create_cache_volume(context,
+                                             img_meta,
+                                             img_service,
+                                             specs,
+                                             cachevol_props)
+        except exception.SnapshotNotFound:
+            exception_msg = (_('Cache volume %(cache_vol)s'
+                               'does not have snapshot %(cache_snap)s.'),
+                             {'cache_vol': cachevol_name,
+                              'cache_snap': cachesnap_name})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        # A cache volume does exist, check if it's updated:
+        if ((cache_vol['updated_at'] != updated_at) or
+                (cache_vol['image_id'] != img_meta['id'])):
+            # The cache volume is updated, but has clones:
+            if cache_snap['numclones'] > 0:
+                exception_msg = (_('Cannot delete '
+                                   'cache volume: %(cachevol_name)s. '
+                                   'It was updated at %(updated_at)s '
+                                   'and currently has %(numclones)s '
+                                   'volume instances.'),
+                                 {'cachevol_name': cachevol_name,
+                                  'updated_at': updated_at,
+                                  'numclones': cache_snap['numclones']})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+            # The cache volume is updated, but has no clone, so we delete it
+            # and re-create a new one:
+            self.zfssa.delete_lun(lcfg.zfssa_pool,
+                                  lcfg.zfssa_cache_project,
+                                  cachevol_name)
+            return self._create_cache_volume(context,
+                                             img_meta,
+                                             img_service,
+                                             specs,
+                                             cachevol_props)
+
+        return cachevol_name, cachesnap_name
+
+    def _create_cache_volume(self, context, img_meta,
+                             img_service, specs, cachevol_props):
+        """Create a cache volume from an image.
+
+        Returns names of the cache volume and its snapshot.
+        """
+        lcfg = self.configuration
+        cachevol_size = int(cachevol_props['size'])
+        lunsize = "%sg" % six.text_type(cachevol_size)
+        lun_props = {
+            'custom:image_id': img_meta['id'],
+            'custom:updated_at': (
+                six.text_type(img_meta['updated_at'].isoformat())),
+        }
+        lun_props.update(specs)
+
+        cache_vol = {
+            'name': cachevol_props['cache_name'],
+            'id': img_meta['id'],
+            'size': cachevol_size,
+        }
+        LOG.debug('Creating cache volume %s.', cache_vol['name'])
+
+        try:
+            self.zfssa.create_lun(lcfg.zfssa_pool,
+                                  lcfg.zfssa_cache_project,
+                                  cache_vol['name'],
+                                  lunsize,
+                                  lcfg.zfssa_target_group,
+                                  lun_props)
+            super(ZFSSAISCSIDriver, self).copy_image_to_volume(context,
+                                                               cache_vol,
+                                                               img_service,
+                                                               img_meta['id'])
+            self.zfssa.create_snapshot(lcfg.zfssa_pool,
+                                       lcfg.zfssa_cache_project,
+                                       cache_vol['name'],
+                                       cachevol_props['snap_name'])
+        except Exception as exc:
+            exc_msg = (_('Fail to create cache volume %(volume)s. '
+                         'Error: %(err)s'),
+                       {'volume': cache_vol['name'],
+                        'err': six.text_type(exc)})
+            LOG.error(exc_msg)
+            self.zfssa.delete_lun(lcfg.zfssa_pool,
+                                  lcfg.zfssa_cache_project,
+                                  cache_vol['name'])
+            raise exception.VolumeBackendAPIException(data=exc_msg)
+
+        return cachevol_props['cache_name'], cachevol_props['snap_name']
+
+    def local_path(self, volume):
+        """Not implemented."""
+        pass
+
+    def backup_volume(self, context, backup, backup_service):
+        """Not implemented."""
+        pass
+
+    def restore_backup(self, context, backup, volume, backup_service):
+        """Not implemented."""
+        pass
+
+    def _verify_clone_size(self, snapshot, size):
+        """Check whether the clone size is the same as the parent volume."""
+        lcfg = self.configuration
+        lun = self.zfssa.get_lun(lcfg.zfssa_pool,
+                                 lcfg.zfssa_project,
+                                 snapshot['volume_name'])
+        return lun['size'] == size
+
+    def initialize_connection(self, volume, connector):
+        lcfg = self.configuration
+        init_groups = self.zfssa.get_initiator_initiatorgroup(
+            connector['initiator'])
+        if not init_groups:
+            if lcfg.zfssa_initiator_group == 'default':
+                init_groups.append('default')
+            else:
+                exception_msg = (_('Failed to find iSCSI initiator group '
+                                   'containing %(initiator)s.')
+                                 % {'initiator': connector['initiator']})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+        if ((lcfg.zfssa_enable_local_cache is True) and
+                (volume['name'].startswith('os-cache-vol-'))):
+            project = lcfg.zfssa_cache_project
+        else:
+            project = lcfg.zfssa_project
+
+        for initiator_group in init_groups:
+            self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool,
+                                              project,
+                                              volume['name'],
+                                              initiator_group)
+        iscsi_properties = {}
+        provider = self._get_provider_info(volume)
+
+        (target_portal, iqn, lun) = provider['provider_location'].split()
+        iscsi_properties['target_discovered'] = False
+        iscsi_properties['target_portal'] = target_portal
+        iscsi_properties['target_iqn'] = iqn
+        iscsi_properties['target_lun'] = int(lun)
+        iscsi_properties['volume_id'] = volume['id']
+
+        if 'provider_auth' in provider:
+            (auth_method, auth_username, auth_password) = provider[
+                'provider_auth'].split()
+            iscsi_properties['auth_method'] = auth_method
+            iscsi_properties['auth_username'] = auth_username
+            iscsi_properties['auth_password'] = auth_password
+
+        return {
+            'driver_volume_type': 'iscsi',
+            'data': iscsi_properties
+        }
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Driver entry point to terminate a connection for a volume."""
+        LOG.debug('terminate_connection: volume name: %s.', volume['name'])
+        lcfg = self.configuration
+        project = lcfg.zfssa_project
+        if ((lcfg.zfssa_enable_local_cache is True) and
+                (volume['name'].startswith('os-cache-vol-'))):
+            project = lcfg.zfssa_cache_project
+        self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool,
+                                          project,
+                                          volume['name'],
+                                          '')
+
+    def _get_voltype_specs(self, volume):
+        """Get specs suitable for volume creation."""
+        vtype = volume.get('volume_type_id', None)
+        extra_specs = None
+        if vtype:
+            extra_specs = volume_types.get_volume_type_extra_specs(vtype)
+
+        return self._get_specs(extra_specs)
+
+    def _get_specs(self, xspecs):
+        """Return a dict with extra specs and/or config values."""
+        result = {}
+        for spc in ZFSSA_LUN_SPECS:
+            val = None
+            prop = spc.split(':')[1]
+            cfg = 'zfssa_lun_' + prop
+            if xspecs:
+                val = xspecs.pop(spc, None)
+
+            if val is None:
+                val = self.configuration.safe_get(cfg)
+
+            if val is not None and val != '':
+                result.update({prop: val})
+
+        return result
+
+    def migrate_volume(self, ctxt, volume, host):
+        LOG.debug('Attempting ZFSSA enabled volume migration. volume: %(id)s, '
+                  'host: %(host)s, status=%(status)s.',
+                  {'id': volume['id'],
+                   'host': host,
+                   'status': volume['status']})
+
+        lcfg = self.configuration
+        default_ret = (False, None)
+
+        if volume['status'] != "available":
+            LOG.debug('Only available volumes can be migrated using backend '
+                      'assisted migration. Defaulting to generic migration.')
+            return default_ret
+
+        if (host['capabilities']['vendor_name'] != 'Oracle' or
+                host['capabilities']['storage_protocol'] != self.protocol):
+            LOG.debug('Source and destination drivers need to be Oracle iSCSI '
+                      'to use backend assisted migration. Defaulting to '
+                      'generic migration.')
+            return default_ret
+
+        if 'location_info' not in host['capabilities']:
+            LOG.debug('Could not find location_info in capabilities reported '
+                      'by the destination driver. Defaulting to generic '
+                      'migration.')
+            return default_ret
+
+        loc_info = host['capabilities']['location_info']
+
+        try:
+            (tgt_host, auth_str, tgt_pool, tgt_project, tgt_tgtgroup,
+             tgt_repl_ip) = loc_info.split(':')
+        except ValueError:
+            LOG.error(_LE("Location info needed for backend enabled volume "
+                          "migration not in correct format: %s. Continuing "
+                          "with generic volume migration."), loc_info)
+            return default_ret
+
+        if tgt_repl_ip == '':
+            msg = _LE("zfssa_replication_ip not set in cinder.conf. "
+                      "zfssa_replication_ip is needed for backend enabled "
+                      "volume migration. Continuing with generic volume "
+                      "migration.")
+            LOG.error(msg)
+            return default_ret
+
+        src_pool = lcfg.zfssa_pool
+        src_project = lcfg.zfssa_project
+
+        try:
+            LOG.info(_LI('Connecting to target host: %s for backend enabled '
+                         'migration.'), tgt_host)
+            self.tgt_zfssa.set_host(tgt_host)
+            self.tgt_zfssa.login(auth_str)
+
+            # Verify that the replication service is online
+            try:
+                self.zfssa.verify_service('replication')
+                self.tgt_zfssa.verify_service('replication')
+            except exception.VolumeBackendAPIException:
+                return default_ret
+
+            # ensure that a target group by the same name exists on the target
+            # system also, if not, use default migration.
+            lun = self.zfssa.get_lun(src_pool, src_project, volume['name'])
+
+            if lun['targetgroup'] != tgt_tgtgroup:
+                return default_ret
+
+            tgt_asn = self.tgt_zfssa.get_asn()
+            src_asn = self.zfssa.get_asn()
+
+            # verify on the source system that the destination has been
+            # registered as a replication target
+            tgts = self.zfssa.get_replication_targets()
+            targets = []
+            for target in tgts['targets']:
+                if target['asn'] == tgt_asn:
+                    targets.append(target)
+
+            if targets == []:
+                LOG.debug('Target host: %(host)s for volume migration '
+                          'not configured as a replication target '
+                          'for volume: %(vol)s.',
+                          {'host': tgt_repl_ip,
+                           'vol': volume['name']})
+                return default_ret
+
+            # Multiple ips from the same appliance may be configured
+            # as different targets
+            for target in targets:
+                if target['address'] == tgt_repl_ip + ':216':
+                    break
+
+            if target['address'] != tgt_repl_ip + ':216':
+                LOG.debug('Target with replication ip: %s not configured on '
+                          'the source appliance for backend enabled volume '
+                          'migration. Proceeding with default migration.',
+                          tgt_repl_ip)
+                return default_ret
+
+            flow = lf.Flow('zfssa_volume_migration').add(
+                MigrateVolumeInit(),
+                MigrateVolumeCreateAction(provides='action_id'),
+                MigrateVolumeSendReplUpdate(),
+                MigrateVolumeSeverRepl(),
+                MigrateVolumeMoveVol(),
+                MigrateVolumeCleanUp()
+            )
+            taskflow.engines.run(flow,
+                                 store={'driver': self,
+                                        'tgt_zfssa': self.tgt_zfssa,
+                                        'tgt_pool': tgt_pool,
+                                        'tgt_project': tgt_project,
+                                        'volume': volume, 'tgt_asn': tgt_asn,
+                                        'src_zfssa': self.zfssa,
+                                        'src_asn': src_asn,
+                                        'src_pool': src_pool,
+                                        'src_project': src_project,
+                                        'target': target})
+
+            return(True, None)
+
+        except Exception:
+            LOG.error(_LE("Error migrating volume: %s"), volume['name'])
+            raise
+
+    def update_migrated_volume(self, ctxt, volume, new_volume,
+                               original_volume_status):
+        """Return model update for migrated volume.
+
+        :param volume: The original volume that was migrated to this backend
+        :param new_volume: The migration volume object that was created on
+                           this backend as part of the migration process
+        :param original_volume_status: The status of the original volume
+        :returns: model_update to update DB with any needed changes
+        """
+
+        lcfg = self.configuration
+        original_name = CONF.volume_name_template % volume['id']
+        current_name = CONF.volume_name_template % new_volume['id']
+
+        LOG.debug('Renaming migrated volume: %(cur)s to %(org)s',
+                  {'cur': current_name,
+                   'org': original_name})
+        self.zfssa.set_lun_props(lcfg.zfssa_pool, lcfg.zfssa_project,
+                                 current_name, name=original_name)
+        return {'_name_id': None}
+
+    @utils.synchronized('zfssaiscsi', external=True)
+    def _check_origin(self, lun, volname):
+        """Verify the cache volume of a bootable volume.
+
+        If the cache no longer has clone, it will be deleted.
+        There is a small lag between the time a clone is deleted and the number
+        of clones being updated accordingly. There is also a race condition
+        when multiple volumes (clones of a cache volume) are deleted at once,
+        leading to the number of clones reported incorrectly. The file lock is
+        here to avoid such issues.
+        """
+        lcfg = self.configuration
+        cache = lun['origin']
+        numclones = -1
+        if (cache['snapshot'].startswith('image-') and
+                cache['share'].startswith('os-cache-vol')):
+            try:
+                numclones = self.zfssa.num_clones(lcfg.zfssa_pool,
+                                                  lcfg.zfssa_cache_project,
+                                                  cache['share'],
+                                                  cache['snapshot'])
+            except Exception:
+                LOG.debug('Cache volume is already deleted.')
+                return
+
+            LOG.debug('Checking cache volume %(name)s, numclones = %(clones)d',
+                      {'name': cache['share'], 'clones': numclones})
+
+        # Sometimes numclones still hold old values even when all clones
+        # have been deleted. So we handle this situation separately here:
+        if numclones == 1:
+            try:
+                self.zfssa.get_lun(lcfg.zfssa_pool,
+                                   lcfg.zfssa_project,
+                                   volname)
+                # The volume does exist, so return
+                return
+            except exception.VolumeNotFound:
+                # The volume is already deleted
+                numclones = 0
+
+        if numclones == 0:
+            try:
+                self.zfssa.delete_lun(lcfg.zfssa_pool,
+                                      lcfg.zfssa_cache_project,
+                                      cache['share'])
+            except exception.VolumeBackendAPIException:
+                LOG.warning(_LW("Volume %s exists but can't be deleted"),
+                            cache['share'])
+
+    def manage_existing(self, volume, existing_ref):
+        """Manage an existing volume in the ZFSSA backend.
+
+        :param volume: Reference to the new volume.
+        :param existing_ref: Reference to the existing volume to be managed.
+        """
+        lcfg = self.configuration
+
+        existing_vol = self._get_existing_vol(existing_ref)
+        self._verify_volume_to_manage(existing_vol)
+
+        new_vol_name = volume['name']
+
+        try:
+            self.zfssa.set_lun_props(lcfg.zfssa_pool,
+                                     lcfg.zfssa_project,
+                                     existing_vol['name'],
+                                     name=new_vol_name,
+                                     schema={"custom:cinder_managed": True})
+        except exception.VolumeBackendAPIException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed to rename volume %(existing)s to "
+                              "%(new)s. Volume manage failed."),
+                          {'existing': existing_vol['name'],
+                           'new': new_vol_name})
+        return None
+
+    def manage_existing_get_size(self, volume, existing_ref):
+        """Return size of the volume to be managed by manage_existing."""
+        existing_vol = self._get_existing_vol(existing_ref)
+
+        size = existing_vol['size']
+        return int(math.ceil(float(size) / units.Gi))
+
+    def unmanage(self, volume):
+        """Remove an existing volume from cinder management.
+
+        :param volume: Reference to the volume to be unmanaged.
+        """
+        lcfg = self.configuration
+        new_name = 'unmanaged-' + volume['name']
+        try:
+            self.zfssa.set_lun_props(lcfg.zfssa_pool,
+                                     lcfg.zfssa_project,
+                                     volume['name'],
+                                     name=new_name,
+                                     schema={"custom:cinder_managed": False})
+        except exception.VolumeBackendAPIException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed to rename volume %(existing)s to"
+                              " %(new)s. Volume unmanage failed."),
+                          {'existing': volume['name'],
+                           'new': new_name})
+        return None
+
+    def _verify_volume_to_manage(self, volume):
+        lcfg = self.configuration
+        if lcfg.zfssa_manage_policy == 'loose':
+            return
+
+        vol_name = volume['name']
+
+        if 'cinder_managed' not in volume:
+            err_msg = (_("Unknown if the volume: %s to be managed is "
+                         "already being managed by Cinder. Aborting manage "
+                         "volume. Please add 'cinder_managed' custom schema "
+                         "property to the volume and set its value to False."
+                         " Alternatively, set the value of cinder config "
+                         "policy 'zfssa_manage_policy' to 'loose' to "
+                         "remove this restriction.") % vol_name)
+            LOG.error(err_msg)
+            raise exception.InvalidInput(reason=err_msg)
+
+        if volume['cinder_managed'] is True:
+            msg = (_("Volume: %s is already being managed by Cinder.")
+                   % vol_name)
+            LOG.error(msg)
+            raise exception.ManageExistingAlreadyManaged(volume_ref=vol_name)
+
+    def _get_existing_vol(self, existing_ref):
+        lcfg = self.configuration
+        if 'source-name' not in existing_ref:
+            msg = (_("Reference to volume: %s to be managed must contain "
+                   "source-name.") % existing_ref)
+            raise exception.ManageExistingInvalidReference(
+                existing_ref=existing_ref, reason=msg)
+        try:
+            existing_vol = self.zfssa.get_lun(lcfg.zfssa_pool,
+                                              lcfg.zfssa_project,
+                                              existing_ref['source-name'])
+        except exception.VolumeNotFound:
+            err_msg = (_("Volume %s doesn't exist on the ZFSSA "
+                         "backend.") % existing_vol['name'])
+            LOG.error(err_msg)
+            raise exception.InvalidInput(reason=err_msg)
+        return existing_vol
+
+
+class MigrateVolumeInit(task.Task):
+    def execute(self, src_zfssa, volume, src_pool, src_project):
+        LOG.debug('Setting inherit flag on source backend to False.')
+        src_zfssa.edit_inherit_replication_flag(src_pool, src_project,
+                                                volume['name'], set=False)
+
+    def revert(self, src_zfssa, volume, src_pool, src_project, **kwargs):
+        LOG.debug('Rollback: Setting inherit flag on source appliance to '
+                  'True.')
+        src_zfssa.edit_inherit_replication_flag(src_pool, src_project,
+                                                volume['name'], set=True)
+
+
+class MigrateVolumeCreateAction(task.Task):
+    def execute(self, src_zfssa, volume, src_pool, src_project, target,
+                tgt_pool):
+        LOG.debug('Creating replication action on source appliance.')
+        action_id = src_zfssa.create_replication_action(src_pool,
+                                                        src_project,
+                                                        target['label'],
+                                                        tgt_pool,
+                                                        volume['name'])
+
+        self._action_id = action_id
+        return action_id
+
+    def revert(self, src_zfssa, **kwargs):
+        if hasattr(self, '_action_id'):
+            LOG.debug('Rollback: deleting replication action on source '
+                      'appliance.')
+            src_zfssa.delete_replication_action(self._action_id)
+
+
+class MigrateVolumeSendReplUpdate(task.Task):
+    def execute(self, src_zfssa, action_id):
+        LOG.debug('Sending replication update from source appliance.')
+        src_zfssa.send_repl_update(action_id)
+        LOG.debug('Deleting replication action on source appliance.')
+        src_zfssa.delete_replication_action(action_id)
+        self._action_deleted = True
+
+
+class MigrateVolumeSeverRepl(task.Task):
+    def execute(self, tgt_zfssa, src_asn, action_id, driver):
+        source = tgt_zfssa.get_replication_source(src_asn)
+        if not source:
+            err = (_('Source with host ip/name: %s not found on the '
+                     'target appliance for backend enabled volume '
+                     'migration, procedding with default migration.'),
+                   driver.configuration.san_ip)
+            LOG.error(err)
+            raise exception.VolumeBackendAPIException(data=err)
+        LOG.debug('Severing replication package on destination appliance.')
+        tgt_zfssa.sever_replication(action_id, source['name'],
+                                    project=action_id)
+
+
+class MigrateVolumeMoveVol(task.Task):
+    def execute(self, tgt_zfssa, tgt_pool, tgt_project, action_id, volume):
+        LOG.debug('Moving LUN to destination project on destination '
+                  'appliance.')
+        tgt_zfssa.move_volume(tgt_pool, action_id, volume['name'], tgt_project)
+        LOG.debug('Deleting temporary project on destination appliance.')
+        tgt_zfssa.delete_project(tgt_pool, action_id)
+        self._project_deleted = True
+
+    def revert(self, tgt_zfssa, tgt_pool, tgt_project, action_id, volume,
+               **kwargs):
+        if not hasattr(self, '_project_deleted'):
+            LOG.debug('Rollback: deleting temporary project on destination '
+                      'appliance.')
+            tgt_zfssa.delete_project(tgt_pool, action_id)
+
+
+class MigrateVolumeCleanUp(task.Task):
+    def execute(self, driver, volume, tgt_zfssa):
+        LOG.debug('Finally, delete source volume on source appliance.')
+        driver.delete_volume(volume)
+        tgt_zfssa.logout()