From f0f6c1fb715232d62134381068b8c9d99ef5d3d0 Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Fri, 4 Mar 2016 11:46:53 +0800 Subject: [PATCH] Add missing file after merge. --- cinder/volume/drivers/zfssa/zfssaiscsi.py | 1193 +++++++++++++++++++++ 1 file changed, 1193 insertions(+) create mode 100644 cinder/volume/drivers/zfssa/zfssaiscsi.py diff --git a/cinder/volume/drivers/zfssa/zfssaiscsi.py b/cinder/volume/drivers/zfssa/zfssaiscsi.py new file mode 100644 index 000000000..c39235153 --- /dev/null +++ b/cinder/volume/drivers/zfssa/zfssaiscsi.py @@ -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() -- 2.45.2