"Fossies" - the Fresh Open Source Software Archive

Member "cinder-14.0.2/cinder/volume/drivers/nexenta/iscsi.py" (4 Oct 2019, 29253 Bytes) of package /linux/misc/openstack/cinder-14.0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "iscsi.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_15.0.0.

    1 # Copyright 2016 Nexenta Systems, Inc. All Rights Reserved.
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 
   15 import six
   16 
   17 from oslo_log import log as logging
   18 from oslo_utils import excutils
   19 
   20 from cinder import exception
   21 from cinder.i18n import _
   22 from cinder import interface
   23 from cinder.volume import driver
   24 from cinder.volume.drivers.nexenta import jsonrpc
   25 from cinder.volume.drivers.nexenta import options
   26 from cinder.volume.drivers.nexenta import utils
   27 
   28 VERSION = '1.3.1'
   29 LOG = logging.getLogger(__name__)
   30 
   31 
   32 @interface.volumedriver
   33 class NexentaISCSIDriver(driver.ISCSIDriver):
   34     """Executes volume driver commands on Nexenta Appliance.
   35 
   36     Version history:
   37 
   38     .. code-block:: none
   39 
   40         1.0.0 - Initial driver version.
   41         1.0.1 - Fixed bug #1236626: catch "does not exist" exception of
   42                 lu_exists.
   43         1.1.0 - Changed class name to NexentaISCSIDriver.
   44         1.1.1 - Ignore "does not exist" exception of nms.snapshot.destroy.
   45         1.1.2 - Optimized create_cloned_volume, replaced zfs send recv with zfs
   46                 clone.
   47         1.1.3 - Extended volume stats provided by _update_volume_stats method.
   48         1.2.0 - Added volume migration with storage assist method.
   49         1.2.1 - Fixed bug #1263258: now migrate_volume update provider_location
   50                 of migrated volume; after migrating volume migrate_volume
   51                 destroy snapshot on migration destination.
   52         1.3.0 - Added retype method.
   53         1.3.0.1 - Target creation refactor.
   54         1.3.1 - Added ZFS cleanup.
   55     """
   56 
   57     VERSION = VERSION
   58 
   59     # ThirdPartySystems wiki page
   60     CI_WIKI_NAME = "Nexenta_CI"
   61 
   62     def __init__(self, *args, **kwargs):
   63         super(NexentaISCSIDriver, self).__init__(*args, **kwargs)
   64         self.nms = None
   65         self.targets = {}
   66         if self.configuration:
   67             self.configuration.append_config_values(
   68                 options.NEXENTA_CONNECTION_OPTS)
   69             self.configuration.append_config_values(
   70                 options.NEXENTA_ISCSI_OPTS)
   71             self.configuration.append_config_values(
   72                 options.NEXENTA_DATASET_OPTS)
   73             self.configuration.append_config_values(
   74                 options.NEXENTA_RRMGR_OPTS)
   75         self.nms_protocol = self.configuration.nexenta_rest_protocol
   76         self.nms_host = self.configuration.nexenta_host
   77         self.nms_port = self.configuration.nexenta_rest_port
   78         self.nms_user = self.configuration.nexenta_user
   79         self.nms_password = self.configuration.nexenta_password
   80         self.volume = self.configuration.nexenta_volume
   81         self.volume_compression = (
   82             self.configuration.nexenta_dataset_compression)
   83         self.volume_deduplication = self.configuration.nexenta_dataset_dedup
   84         self.volume_description = (
   85             self.configuration.nexenta_dataset_description)
   86         self.rrmgr_compression = self.configuration.nexenta_rrmgr_compression
   87         self.rrmgr_tcp_buf_size = self.configuration.nexenta_rrmgr_tcp_buf_size
   88         self.rrmgr_connections = self.configuration.nexenta_rrmgr_connections
   89         self.iscsi_target_portal_port = (
   90             self.configuration.nexenta_iscsi_target_portal_port)
   91 
   92         self._needless_objects = set()
   93 
   94     @staticmethod
   95     def get_driver_options():
   96         return (options.NEXENTA_CONNECTION_OPTS + options.NEXENTA_ISCSI_OPTS +
   97                 options.NEXENTA_DATASET_OPTS + options.NEXENTA_RRMGR_OPTS)
   98 
   99     @property
  100     def backend_name(self):
  101         backend_name = None
  102         if self.configuration:
  103             backend_name = self.configuration.safe_get('volume_backend_name')
  104         if not backend_name:
  105             backend_name = self.__class__.__name__
  106         return backend_name
  107 
  108     def do_setup(self, context):
  109         if self.nms_protocol == 'auto':
  110             protocol, auto = 'http', True
  111         else:
  112             protocol, auto = self.nms_protocol, False
  113         self.nms = jsonrpc.NexentaJSONProxy(
  114             protocol, self.nms_host, self.nms_port, '/rest/nms', self.nms_user,
  115             self.nms_password, auto=auto)
  116 
  117     def check_for_setup_error(self):
  118         """Verify that the volume for our zvols exists.
  119 
  120         :raise: :py:exc:`LookupError`
  121         """
  122         if not self.nms.volume.object_exists(self.volume):
  123             raise LookupError(_("Volume %s does not exist in Nexenta SA") %
  124                               self.volume)
  125 
  126     def _get_zvol_name(self, volume_name):
  127         """Return zvol name that corresponds given volume name."""
  128         return '%s/%s' % (self.volume, volume_name)
  129 
  130     def _create_target(self, target_idx):
  131         target_name = '%s%s-%i' % (
  132             self.configuration.nexenta_target_prefix,
  133             self.nms_host,
  134             target_idx
  135         )
  136         target_group_name = self._get_target_group_name(target_name)
  137 
  138         if not self._target_exists(target_name):
  139             try:
  140                 self.nms.iscsitarget.create_target({
  141                     'target_name': target_name})
  142             except exception.NexentaException as exc:
  143                 if 'already' in exc.args[0]:
  144                     LOG.info('Ignored target creation error "%s" while '
  145                              'ensuring export.',
  146                              exc)
  147                 else:
  148                     raise
  149         if not self._target_group_exists(target_group_name):
  150             try:
  151                 self.nms.stmf.create_targetgroup(target_group_name)
  152             except exception.NexentaException as exc:
  153                 if ('already' in exc.args[0]):
  154                     LOG.info('Ignored target group creation error "%s" '
  155                              'while ensuring export.',
  156                              exc)
  157                 else:
  158                     raise
  159         if not self._target_member_in_target_group(target_group_name,
  160                                                    target_name):
  161             try:
  162                 self.nms.stmf.add_targetgroup_member(target_group_name,
  163                                                      target_name)
  164             except exception.NexentaException as exc:
  165                 if ('already' in exc.args[0]):
  166                     LOG.info('Ignored target group member addition error '
  167                              '"%s" while ensuring export.',
  168                              exc)
  169                 else:
  170                     raise
  171 
  172         self.targets[target_name] = []
  173         return target_name
  174 
  175     def _get_target_name(self, volume):
  176         """Return iSCSI target name with least LUs."""
  177         provider_location = volume.get('provider_location')
  178         target_names = self.targets.keys()
  179         if provider_location:
  180             target_name = provider_location.split(',1 ')[1].split(' ')[0]
  181             if not(self.targets.get(target_name)):
  182                 self.targets[target_name] = []
  183             if not(volume['name'] in self.targets[target_name]):
  184                 self.targets[target_name].append(volume['name'])
  185         elif not(target_names):
  186             # create first target and target group
  187             target_name = self._create_target(0)
  188             self.targets[target_name].append(volume['name'])
  189         else:
  190             target_name = target_names[0]
  191             for target in target_names:
  192                 if len(self.targets[target]) < len(self.targets[target_name]):
  193                     target_name = target
  194             if len(self.targets[target_name]) >= 20:
  195                 # create new target and target group
  196                 target_name = self._create_target(len(target_names))
  197             if not(volume['name'] in self.targets[target_name]):
  198                 self.targets[target_name].append(volume['name'])
  199         return target_name
  200 
  201     def _get_target_group_name(self, target_name):
  202         """Return Nexenta iSCSI target group name for volume."""
  203         return target_name.replace(
  204             self.configuration.nexenta_target_prefix,
  205             self.configuration.nexenta_target_group_prefix
  206         )
  207 
  208     @staticmethod
  209     def _get_clone_snapshot_name(volume):
  210         """Return name for snapshot that will be used to clone the volume."""
  211         return 'cinder-clone-snapshot-%(id)s' % volume
  212 
  213     @staticmethod
  214     def _is_clone_snapshot_name(snapshot):
  215         """Check if snapshot is created for cloning."""
  216         name = snapshot.split('@')[-1]
  217         return name.startswith('cinder-clone-snapshot-')
  218 
  219     def create_volume(self, volume):
  220         """Create a zvol on appliance.
  221 
  222         :param volume: volume reference
  223         :return: model update dict for volume reference
  224         """
  225         self.nms.zvol.create(
  226             self._get_zvol_name(volume['name']),
  227             '%sG' % (volume['size'],),
  228             six.text_type(self.configuration.nexenta_blocksize),
  229             self.configuration.nexenta_sparse)
  230 
  231     def extend_volume(self, volume, new_size):
  232         """Extend an existing volume.
  233 
  234         :param volume: volume reference
  235         :param new_size: volume new size in GB
  236         """
  237         LOG.info('Extending volume: %(id)s New size: %(size)s GB',
  238                  {'id': volume['id'], 'size': new_size})
  239         self.nms.zvol.set_child_prop(self._get_zvol_name(volume['name']),
  240                                      'volsize', '%sG' % new_size)
  241 
  242     def delete_volume(self, volume):
  243         """Destroy a zvol on appliance.
  244 
  245         :param volume: volume reference
  246         """
  247         volume_name = self._get_zvol_name(volume['name'])
  248         try:
  249             props = self.nms.zvol.get_child_props(volume_name, 'origin') or {}
  250             self.nms.zvol.destroy(volume_name, '')
  251         except exception.NexentaException as exc:
  252             if 'does not exist' in exc.args[0]:
  253                 LOG.info('Volume %s does not exist, it '
  254                          'seems it was already deleted.', volume_name)
  255                 return
  256             if 'zvol has children' in exc.args[0]:
  257                 self._mark_as_garbage(volume_name)
  258                 LOG.info('Volume %s will be deleted later.', volume_name)
  259                 return
  260             raise
  261         origin = props.get('origin')
  262         self._collect_garbage(origin)
  263 
  264     def create_cloned_volume(self, volume, src_vref):
  265         """Creates a clone of the specified volume.
  266 
  267         :param volume: new volume reference
  268         :param src_vref: source volume reference
  269         """
  270         snapshot = {'volume_name': src_vref['name'],
  271                     'name': self._get_clone_snapshot_name(volume),
  272                     'volume_size': src_vref['size']}
  273         LOG.debug('Creating temp snapshot of the original volume: '
  274                   '%(volume_name)s@%(name)s', snapshot)
  275         # We don't delete this snapshot, because this snapshot will be origin
  276         # of new volume. This snapshot will be automatically promoted by NMS
  277         # when user will delete origin volume. But when cloned volume deleted
  278         # we check its origin property and delete source snapshot if needed.
  279         self.create_snapshot(snapshot)
  280         try:
  281             self.create_volume_from_snapshot(volume, snapshot)
  282             self._mark_as_garbage('@'.join(
  283                 (self._get_zvol_name(src_vref['name']), snapshot['name'])))
  284         except exception.NexentaException:
  285             with excutils.save_and_reraise_exception():
  286                 LOG.exception(
  287                     'Volume creation failed, deleting created snapshot '
  288                     '%(volume_name)s@%(name)s', snapshot)
  289             try:
  290                 self.delete_snapshot(snapshot)
  291             except (exception.NexentaException, exception.SnapshotIsBusy):
  292                 LOG.warning('Failed to delete zfs snapshot '
  293                             '%(volume_name)s@%(name)s', snapshot)
  294             raise
  295 
  296     def _get_zfs_send_recv_cmd(self, src, dst):
  297         """Returns rrmgr command for source and destination."""
  298         return utils.get_rrmgr_cmd(src, dst,
  299                                    compression=self.rrmgr_compression,
  300                                    tcp_buf_size=self.rrmgr_tcp_buf_size,
  301                                    connections=self.rrmgr_connections)
  302 
  303     @staticmethod
  304     def get_nms_for_url(url):
  305         """Returns initialized nms object for url."""
  306         auto, scheme, user, password, host, port, path = (
  307             utils.parse_nms_url(url))
  308         return jsonrpc.NexentaJSONProxy(scheme, host, port, path, user,
  309                                         password, auto=auto)
  310 
  311     def migrate_volume(self, ctxt, volume, host):
  312         """Migrate if volume and host are managed by Nexenta appliance.
  313 
  314         :param ctxt: context
  315         :param volume: a dictionary describing the volume to migrate
  316         :param host: a dictionary describing the host to migrate to
  317         """
  318         LOG.debug('Enter: migrate_volume: id=%(id)s, host=%(host)s',
  319                   {'id': volume['id'], 'host': host})
  320         false_ret = (False, None)
  321 
  322         if volume['status'] not in ('available', 'retyping'):
  323             return false_ret
  324 
  325         if 'capabilities' not in host:
  326             return false_ret
  327 
  328         capabilities = host['capabilities']
  329 
  330         if ('location_info' not in capabilities or
  331                 'iscsi_target_portal_port' not in capabilities or
  332                 'nms_url' not in capabilities):
  333             return false_ret
  334 
  335         nms_url = capabilities['nms_url']
  336         dst_parts = capabilities['location_info'].split(':')
  337 
  338         if (capabilities.get('vendor_name') != 'Nexenta' or
  339                 dst_parts[0] != self.__class__.__name__ or
  340                 capabilities['free_capacity_gb'] < volume['size']):
  341             return false_ret
  342 
  343         dst_host, dst_volume = dst_parts[1:]
  344 
  345         ssh_bound = False
  346         ssh_bindings = self.nms.appliance.ssh_list_bindings()
  347         for bind in ssh_bindings:
  348             if dst_host.startswith(ssh_bindings[bind][3]):
  349                 ssh_bound = True
  350                 break
  351         if not ssh_bound:
  352             LOG.warning("Remote NexentaStor appliance at %s should be "
  353                         "SSH-bound.", dst_host)
  354 
  355         # Create temporary snapshot of volume on NexentaStor Appliance.
  356         snapshot = {
  357             'volume_name': volume['name'],
  358             'name': utils.get_migrate_snapshot_name(volume)
  359         }
  360         self.create_snapshot(snapshot)
  361 
  362         src = '%(volume)s/%(zvol)s@%(snapshot)s' % {
  363             'volume': self.volume,
  364             'zvol': volume['name'],
  365             'snapshot': snapshot['name']
  366         }
  367         dst = ':'.join([dst_host, dst_volume])
  368 
  369         try:
  370             self.nms.appliance.execute(self._get_zfs_send_recv_cmd(src, dst))
  371         except exception.NexentaException as exc:
  372             LOG.warning("Cannot send source snapshot %(src)s to "
  373                         "destination %(dst)s. Reason: %(exc)s",
  374                         {'src': src, 'dst': dst, 'exc': exc})
  375             return false_ret
  376         finally:
  377             try:
  378                 self.delete_snapshot(snapshot)
  379             except exception.NexentaException as exc:
  380                 LOG.warning("Cannot delete temporary source snapshot "
  381                             "%(src)s on NexentaStor Appliance: %(exc)s",
  382                             {'src': src, 'exc': exc})
  383         try:
  384             self.delete_volume(volume)
  385         except exception.NexentaException as exc:
  386             LOG.warning("Cannot delete source volume %(volume)s on "
  387                         "NexentaStor Appliance: %(exc)s",
  388                         {'volume': volume['name'], 'exc': exc})
  389 
  390         dst_nms = self.get_nms_for_url(nms_url)
  391         dst_snapshot = '%s/%s@%s' % (dst_volume, volume['name'],
  392                                      snapshot['name'])
  393         try:
  394             dst_nms.snapshot.destroy(dst_snapshot, '')
  395         except exception.NexentaException as exc:
  396             LOG.warning("Cannot delete temporary destination snapshot "
  397                         "%(dst)s on NexentaStor Appliance: %(exc)s",
  398                         {'dst': dst_snapshot, 'exc': exc})
  399         return True, None
  400 
  401     def retype(self, context, volume, new_type, diff, host):
  402         """Convert the volume to be of the new type.
  403 
  404         :param context: Context
  405         :param volume: A dictionary describing the volume to migrate
  406         :param new_type: A dictionary describing the volume type to convert to
  407         :param diff: A dictionary with the difference between the two types
  408         :param host: A dictionary describing the host to migrate to, where
  409                      host['host'] is its name, and host['capabilities'] is a
  410                      dictionary of its reported capabilities.
  411         """
  412         LOG.debug('Retype volume request %(vol)s to be %(type)s '
  413                   '(host: %(host)s), diff %(diff)s.',
  414                   {'vol': volume['name'],
  415                    'type': new_type,
  416                    'host': host,
  417                    'diff': diff})
  418 
  419         options = dict(
  420             compression='compression',
  421             dedup='dedup',
  422             description='nms:description'
  423         )
  424 
  425         retyped = False
  426         migrated = False
  427 
  428         capabilities = host['capabilities']
  429         src_backend = self.__class__.__name__
  430         dst_backend = capabilities['location_info'].split(':')[0]
  431         if src_backend != dst_backend:
  432             LOG.warning('Cannot retype from %(src_backend)s to '
  433                         '%(dst_backend)s.',
  434                         {'src_backend': src_backend,
  435                          'dst_backend': dst_backend})
  436             return False
  437 
  438         hosts = (volume['host'], host['host'])
  439         old, new = hosts
  440         if old != new:
  441             migrated, provider_location = self.migrate_volume(
  442                 context, volume, host)
  443 
  444         if not migrated:
  445             nms = self.nms
  446         else:
  447             nms_url = capabilities['nms_url']
  448             nms = self.get_nms_for_url(nms_url)
  449 
  450         zvol = '%s/%s' % (
  451             capabilities['location_info'].split(':')[-1], volume['name'])
  452 
  453         for opt in options:
  454             old, new = diff.get('extra_specs').get(opt, (False, False))
  455             if old != new:
  456                 LOG.debug('Changing %(opt)s from %(old)s to %(new)s.',
  457                           {'opt': opt, 'old': old, 'new': new})
  458                 try:
  459                     nms.zvol.set_child_prop(
  460                         zvol, options[opt], new)
  461                     retyped = True
  462                 except exception.NexentaException:
  463                     LOG.error('Error trying to change %(opt)s'
  464                               ' from %(old)s to %(new)s',
  465                               {'opt': opt, 'old': old, 'new': new})
  466                     return False, None
  467         return retyped or migrated, None
  468 
  469     def create_snapshot(self, snapshot):
  470         """Create snapshot of existing zvol on appliance.
  471 
  472         :param snapshot: snapshot reference
  473         """
  474         self.nms.zvol.create_snapshot(
  475             self._get_zvol_name(snapshot['volume_name']),
  476             snapshot['name'], '')
  477 
  478     def create_volume_from_snapshot(self, volume, snapshot):
  479         """Create new volume from other's snapshot on appliance.
  480 
  481         :param volume: reference of volume to be created
  482         :param snapshot: reference of source snapshot
  483         """
  484         self.nms.zvol.clone(
  485             '%s@%s' % (self._get_zvol_name(snapshot['volume_name']),
  486                        snapshot['name']),
  487             self._get_zvol_name(volume['name']))
  488         if (('size' in volume) and (
  489                 volume['size'] > snapshot['volume_size'])):
  490             self.extend_volume(volume, volume['size'])
  491 
  492     def delete_snapshot(self, snapshot):
  493         """Delete volume's snapshot on appliance.
  494 
  495         :param snapshot: snapshot reference
  496         """
  497         volume_name = self._get_zvol_name(snapshot['volume_name'])
  498         snapshot_name = '%s@%s' % (volume_name, snapshot['name'])
  499         try:
  500             self.nms.snapshot.destroy(snapshot_name, '')
  501         except exception.NexentaException as exc:
  502             if "does not exist" in exc.args[0]:
  503                 LOG.info('Snapshot %s does not exist, it seems it was '
  504                          'already deleted.', snapshot_name)
  505                 return
  506             elif "snapshot has dependent clones" in exc.args[0]:
  507                 self._mark_as_garbage(snapshot_name)
  508                 LOG.info('Snapshot %s has dependent clones, will be '
  509                          'deleted later.', snapshot_name)
  510                 return
  511             raise
  512         self._collect_garbage(volume_name)
  513 
  514     def local_path(self, volume):
  515         """Return local path to existing local volume.
  516 
  517         We never have local volumes, so it raises NotImplementedError.
  518 
  519         :raise: :py:exc:`NotImplementedError`
  520         """
  521         raise NotImplementedError
  522 
  523     def _target_exists(self, target):
  524         """Check if iSCSI target exist.
  525 
  526         :param target: target name
  527         :return: True if target exist, else False
  528         """
  529         targets = self.nms.stmf.list_targets()
  530         if not targets:
  531             return False
  532         return (target in self.nms.stmf.list_targets())
  533 
  534     def _target_group_exists(self, target_group):
  535         """Check if target group exist.
  536 
  537         :param target_group: target group
  538         :return: True if target group exist, else False
  539         """
  540         groups = self.nms.stmf.list_targetgroups()
  541         if not groups:
  542             return False
  543         return target_group in groups
  544 
  545     def _target_member_in_target_group(self, target_group, target_member):
  546         """Check if target member in target group.
  547 
  548         :param target_group: target group
  549         :param target_member: target member
  550         :return: True if target member in target group, else False
  551         :raises NexentaException: if target group doesn't exist
  552         """
  553         members = self.nms.stmf.list_targetgroup_members(target_group)
  554         if not members:
  555             return False
  556         return target_member in members
  557 
  558     def _lu_exists(self, zvol_name):
  559         """Check if LU exists on appliance.
  560 
  561         :param zvol_name: Zvol name
  562         :raises NexentaException: if zvol not exists
  563         :return: True if LU exists, else False
  564         """
  565         try:
  566             return bool(self.nms.scsidisk.lu_exists(zvol_name))
  567         except exception.NexentaException as exc:
  568             if 'does not exist' not in exc.args[0]:
  569                 raise
  570             return False
  571 
  572     def _is_lu_shared(self, zvol_name):
  573         """Check if LU exists on appliance and shared.
  574 
  575         :param zvol_name: Zvol name
  576         :raises NexentaException: if Zvol not exist
  577         :return: True if LU exists and shared, else False
  578         """
  579         try:
  580             shared = self.nms.scsidisk.lu_shared(zvol_name) > 0
  581         except exception.NexentaException as exc:
  582             if 'does not exist for zvol' not in exc.args[0]:
  583                 raise  # Zvol does not exists
  584             shared = False  # LU does not exist
  585         return shared
  586 
  587     def create_export(self, _ctx, volume, connector):
  588         """Create new export for zvol.
  589 
  590         :param volume: reference of volume to be exported
  591         :return: iscsiadm-formatted provider location string
  592         """
  593         model_update = self._do_export(_ctx, volume)
  594         return model_update
  595 
  596     def ensure_export(self, _ctx, volume):
  597         self._do_export(_ctx, volume)
  598 
  599     def _do_export(self, _ctx, volume):
  600         """Recreate parts of export if necessary.
  601 
  602         :param volume: reference of volume to be exported
  603         """
  604         zvol_name = self._get_zvol_name(volume['name'])
  605         target_name = self._get_target_name(volume)
  606         target_group_name = self._get_target_group_name(target_name)
  607 
  608         entry = None
  609         if not self._lu_exists(zvol_name):
  610             try:
  611                 entry = self.nms.scsidisk.create_lu(zvol_name, {})
  612             except exception.NexentaException as exc:
  613                 if 'in use' not in exc.args[0]:
  614                     raise
  615                 LOG.info('Ignored LU creation error "%s" while ensuring '
  616                          'export.', exc)
  617         if not self._is_lu_shared(zvol_name):
  618             try:
  619                 entry = self.nms.scsidisk.add_lun_mapping_entry(zvol_name, {
  620                     'target_group': target_group_name})
  621             except exception.NexentaException as exc:
  622                 if 'view entry exists' not in exc.args[0]:
  623                     raise
  624                 LOG.info('Ignored LUN mapping entry addition error "%s" '
  625                          'while ensuring export.', exc)
  626         model_update = {}
  627         if entry:
  628             provider_location = '%(host)s:%(port)s,1 %(name)s %(lun)s' % {
  629                 'host': self.nms_host,
  630                 'port': self.configuration.nexenta_iscsi_target_portal_port,
  631                 'name': target_name,
  632                 'lun': entry['lun'],
  633             }
  634             model_update = {'provider_location': provider_location}
  635         return model_update
  636 
  637     def remove_export(self, _ctx, volume):
  638         """Destroy all resources created to export zvol.
  639 
  640         :param volume: reference of volume to be unexported
  641         """
  642         target_name = self._get_target_name(volume)
  643         self.targets[target_name].remove(volume['name'])
  644         zvol_name = self._get_zvol_name(volume['name'])
  645         self.nms.scsidisk.delete_lu(zvol_name)
  646 
  647     def get_volume_stats(self, refresh=False):
  648         """Get volume stats.
  649 
  650         If 'refresh' is True, run update the stats first.
  651         """
  652         if refresh:
  653             self._update_volume_stats()
  654 
  655         return self._stats
  656 
  657     def _update_volume_stats(self):
  658         """Retrieve stats info for NexentaStor appliance."""
  659         LOG.debug('Updating volume stats')
  660 
  661         stats = self.nms.volume.get_child_props(
  662             self.configuration.nexenta_volume, 'health|size|used|available')
  663 
  664         total_amount = utils.str2gib_size(stats['size'])
  665         free_amount = utils.str2gib_size(stats['available'])
  666 
  667         location_info = '%(driver)s:%(host)s:%(volume)s' % {
  668             'driver': self.__class__.__name__,
  669             'host': self.nms_host,
  670             'volume': self.volume
  671         }
  672         self._stats = {
  673             'vendor_name': 'Nexenta',
  674             'dedup': self.volume_deduplication,
  675             'compression': self.volume_compression,
  676             'description': self.volume_description,
  677             'driver_version': self.VERSION,
  678             'storage_protocol': 'iSCSI',
  679             'total_capacity_gb': total_amount,
  680             'free_capacity_gb': free_amount,
  681             'reserved_percentage': self.configuration.reserved_percentage,
  682             'QoS_support': False,
  683             'volume_backend_name': self.backend_name,
  684             'location_info': location_info,
  685             'iscsi_target_portal_port': self.iscsi_target_portal_port,
  686             'nms_url': self.nms.url
  687         }
  688 
  689     def _collect_garbage(self, zfs_object):
  690         """Destroys ZFS parent objects
  691 
  692         Recursively destroys ZFS parent volumes and snapshots if they are
  693         marked as garbage
  694 
  695         :param zfs_object: full path to a volume or a snapshot
  696         """
  697         if zfs_object and zfs_object in self._needless_objects:
  698             sp = zfs_object.split('/')
  699             path = '/'.join(sp[:-1])
  700             name = sp[-1]
  701             if '@' in name:  # it's a snapshot:
  702                 volume, snap = name.split('@')
  703                 parent = '/'.join((path, volume))
  704                 try:
  705                     self.nms.snapshot.destroy(zfs_object, '')
  706                 except exception.NexentaException as exc:
  707                     LOG.debug('Error occurred while trying to delete a '
  708                               'snapshot: %s', exc)
  709                     return
  710             else:
  711                 try:
  712                     props = self.nms.zvol.get_child_props(
  713                         zfs_object, 'origin') or {}
  714                 except exception.NexentaException:
  715                     props = {}
  716                 parent = (props['origin'] if 'origin' in props and
  717                                              props['origin'] else '')
  718                 try:
  719                     self.nms.zvol.destroy(zfs_object, '')
  720                 except exception.NexentaException as exc:
  721                     LOG.debug('Error occurred while trying to delete a '
  722                               'volume: %s', exc)
  723                     return
  724             self._needless_objects.remove(zfs_object)
  725             self._collect_garbage(parent)
  726 
  727     def _mark_as_garbage(self, zfs_object):
  728         """Puts ZFS object into list for further removal
  729 
  730         :param zfs_object: full path to a volume or a snapshot
  731         """
  732         self._needless_objects.add(zfs_object)