5403N/A# vim: tabstop=4 shiftwidth=4 softtabstop=4
5403N/A# Copyright (c) 2012 OpenStack LLC.
5403N/A# All Rights Reserved.
5403N/A#
6847N/A# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
5403N/A#
5403N/A# Licensed under the Apache License, Version 2.0 (the "License"); you may
5403N/A# not use this file except in compliance with the License. You may obtain
5403N/A# a copy of the License at
5403N/A#
5403N/A# http://www.apache.org/licenses/LICENSE-2.0
5403N/A#
5403N/A# Unless required by applicable law or agreed to in writing, software
5403N/A# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
5403N/A# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
5403N/A# License for the specific language governing permissions and limitations
5403N/A# under the License.
5403N/A"""
5403N/AVolume driver for Solaris ZFS NFS storage
5403N/A"""
5403N/A
5403N/Aimport os
5403N/A
5403N/Afrom oslo_config import cfg
5403N/Afrom oslo_log import log as logging
5403N/Afrom oslo_utils import units
5403N/A
5403N/Afrom cinder import exception
6847N/Afrom cinder.i18n import _, _LI
5403N/Afrom cinder.volume.drivers import nfs
5403N/A
5403N/AZFS_NFS_VERSION = '1.0.0'
5403N/A
5403N/ALOG = logging.getLogger(__name__)
5403N/A
5403N/Asolaris_zfs_nfs_opts = [
5403N/A cfg.BoolOpt('nfs_round_robin',
5403N/A default=True,
5403N/A help=('Schedule volumes round robin across NFS shares.')),
5403N/A]
5403N/A
5403N/ACONF = cfg.CONF
5403N/ACONF.register_opts(solaris_zfs_nfs_opts)
5403N/A
5403N/A
5403N/Aclass ZfsNfsVolumeDriver(nfs.NfsDriver):
5403N/A """Local ZFS NFS volume operations."""
5403N/A
5403N/A driver_volume_type = 'nfs'
5403N/A driver_prefix = 'nfs'
5403N/A volume_backend_name = 'Solaris_NFS'
5403N/A
5403N/A def __init__(self, *args, **kwargs):
5403N/A super(ZfsNfsVolumeDriver, self).__init__(*args, **kwargs)
5403N/A self.configuration.append_config_values(solaris_zfs_nfs_opts)
5403N/A
5403N/A self.last_rr_pos = None
5403N/A
5403N/A if self.configuration.nfs_mount_options:
5403N/A LOG.warning(_("Solaris NFS driver ignores mount options"))
5403N/A
5403N/A def _update_volume_stats(self):
5403N/A """Retrieve volume status info."""
5403N/A
5403N/A stats = {}
5403N/A backend_name = self.configuration.safe_get('volume_backend_name')
5403N/A stats["volume_backend_name"] = backend_name or self.__class__.__name__
5403N/A stats["driver_version"] = ZFS_NFS_VERSION
5403N/A stats["vendor_name"] = 'Oracle'
5403N/A stats['storage_protocol'] = self.driver_volume_type
5403N/A
5403N/A self._ensure_shares_mounted()
5403N/A
5403N/A global_capacity = 0
5403N/A global_free = 0
5403N/A for share in self._mounted_shares:
5403N/A capacity, free, used = self._get_capacity_info(share)
5403N/A global_capacity += capacity
5403N/A global_free += free
5403N/A
5403N/A stats['total_capacity_gb'] = global_capacity / float(units.Gi)
5403N/A stats['free_capacity_gb'] = global_free / float(units.Gi)
5403N/A stats['reserved_percentage'] = 0
5403N/A stats['QoS_support'] = False
5403N/A self._stats = stats
5403N/A
5403N/A def _create_sparsed_file(self, path, size):
5403N/A """Creates a sparse file of a given size in GiB."""
5403N/A self._execute('/usr/bin/truncate', '-s', '%sG' % size, path)
5403N/A
5403N/A def _create_regular_file(self, path, size):
5403N/A """Creates a regular file of given size in GiB."""
5403N/A
5403N/A block_size_mb = 1
5403N/A block_count = size * units.Gi / (block_size_mb * units.Mi)
5403N/A
5403N/A self._execute('/usr/bin/dd', 'if=/dev/zero', 'of=%s' % path,
5403N/A 'bs=%dM' % block_size_mb,
5403N/A 'count=%d' % block_count)
5403N/A
5403N/A def _set_rw_permissions(self, path):
5403N/A """Sets access permissions for given NFS path.
5403N/A
5403N/A :param path: the volume file path.
5403N/A """
5403N/A os.chmod(path, 0o660)
5403N/A
5403N/A def _set_rw_permissions_for_all(self, path):
5403N/A """Sets 666 permissions for the path."""
5403N/A mode = os.stat(path).st_mode
5403N/A os.chmod(path, mode | 0o666)
5403N/A
5403N/A def _set_rw_permissions_for_owner(self, path):
5403N/A """Sets read-write permissions to the owner for the path."""
5403N/A mode = os.stat(path).st_mode
5403N/A os.chmod(path, mode | 0o600)
5403N/A
5403N/A def _delete(self, path):
5403N/A os.unlink(path)
5403N/A
5403N/A def _get_capacity_info(self, nfs_share):
5403N/A """Calculate available space on the NFS share.
5403N/A
5403N/A :param nfs_share: example 172.18.194.100:/var/nfs
5403N/A """
5403N/A
5403N/A mount_point = self._get_mount_point_for_share(nfs_share)
5403N/A
5403N/A st = os.statvfs(mount_point)
5403N/A total_available = st.f_frsize * st.f_bavail
5403N/A total_size = st.f_frsize * st.f_blocks
5403N/A
5403N/A du, _ = self._execute('/usr/bin/gdu', '-sb', '--apparent-size',
5403N/A '--exclude', '*snapshot*', mount_point)
5403N/A total_allocated = float(du.split()[0])
5403N/A return total_size, total_available, total_allocated
5403N/A
5403N/A def _round_robin(self, sharelist):
5403N/A """
5403N/A Implement a round robin generator for share list
5403N/A """
5403N/A
5403N/A mylen = len(sharelist)
5403N/A
5403N/A if self.last_rr_pos is None:
5403N/A start_pos = 0
5403N/A else:
5403N/A start_pos = (self.last_rr_pos + 1) % mylen
5403N/A
5403N/A pos = start_pos
5403N/A while True:
5403N/A yield sharelist[pos], pos
5403N/A pos = (pos + 1) % mylen
5403N/A if pos == start_pos:
5403N/A break
5403N/A
5403N/A def _find_share(self, volume_size_in_gib):
5403N/A """Choose NFS share among available ones for given volume size.
5403N/A
5403N/A For instances with more than one share that meets the criteria, the
5403N/A share with the least "allocated" space will be selected.
5403N/A
5403N/A :param volume_size_in_gib: int size in GB
5403N/A """
5403N/A
5403N/A if not self._mounted_shares:
5403N/A raise exception.NfsNoSharesMounted()
5403N/A
5403N/A target_share = None
5403N/A if self.configuration.nfs_round_robin:
5403N/A # Round Robin volume placement on shares
5403N/A
5403N/A LOG.debug(_("_find_share using round robin"))
5403N/A
5403N/A for nfs_share, pos in self._round_robin(self._mounted_shares):
5403N/A if not self._is_share_eligible(nfs_share, volume_size_in_gib):
5403N/A continue
5403N/A target_share = nfs_share
5403N/A self.last_rr_pos = pos
5403N/A break
5403N/A else:
5403N/A # Place volume on share with the most free space.
5403N/A
5403N/A LOG.debug(_("_find_share using select most free"))
5403N/A
5403N/A target_share_reserved = 0
5403N/A
5403N/A for nfs_share in self._mounted_shares:
5403N/A if not self._is_share_eligible(nfs_share, volume_size_in_gib):
5403N/A continue
5403N/A total_size, total_available, total_allocated = \
5403N/A self._get_capacity_info(nfs_share)
5403N/A if target_share is not None:
5403N/A if target_share_reserved > total_allocated:
5403N/A target_share = nfs_share
5403N/A target_share_reserved = total_allocated
5403N/A else:
5403N/A target_share = nfs_share
5403N/A target_share_reserved = total_allocated
5403N/A
5403N/A if target_share is None:
5403N/A raise exception.NfsNoSuitableShareFound(
5403N/A volume_size=volume_size_in_gib)
5403N/A
5403N/A LOG.debug('Selected %s as target nfs share.', target_share)
5403N/A
5403N/A return target_share
5403N/A
6847N/A def extend_volume(self, volume, new_size):
6847N/A """Extend an existing volume to the new size."""
6847N/A LOG.info(_LI('Extending volume %s.'), volume['id'])
6847N/A extend_by = int(new_size) - volume['size']
6847N/A if not self._is_share_eligible(volume['provider_location'],
6847N/A extend_by):
6847N/A raise exception.ExtendVolumeError(reason='Insufficient space to'
6847N/A ' extend volume %s to %sG'
6847N/A % (volume['id'], new_size))
6847N/A path = self.local_path(volume)
6847N/A LOG.info(_LI('Resizing file to %sG.'), new_size)
6847N/A self._execute('/usr/bin/truncate', '-s', '%sG' % new_size, path)
6847N/A
5403N/A def set_nas_security_options(self, is_new_cinder_install):
5403N/A """Secure NAS options.
5403N/A
5403N/A For Solaris we always operate in a secure mode and do not
5403N/A rely on root or any rootwrap utilities.
5403N/A
5403N/A With RBAC we can do what we need as the cinder user. We
5403N/A set the nas_secure_file.XXX to be true by default. We ignore
5403N/A any conf file setting for these string vars.
5403N/A
5403N/A We don't ever use these nas_secure_file_XXX vars in this driver
5403N/A but we still set the value to true. This might prevent admin/users
5403N/A from opening bugs stating we are not running in a secure mode.
5403N/A """
5403N/A
5403N/A self.configuration.nas_secure_file_operations = 'true'
5403N/A self.configuration.nas_secure_file_permissions = 'true'
5403N/A self._execute_as_root = False
5403N/A
5403N/A LOG.debug('NAS variable secure_file_permissions setting is: %s' %
5403N/A self.configuration.nas_secure_file_permissions)
5403N/A
5403N/A LOG.debug('NAS variable secure_file_operations setting is: %s' %
5403N/A self.configuration.nas_secure_file_operations)