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