manila/manila/share/drivers/glusterfs/layout_directory.py

250 lines
9.9 KiB
Python

# Copyright (c) 2015 Red Hat, Inc.
# 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.
"""GlusterFS directory mapped share layout."""
import math
import os
from oslo_config import cfg
from oslo_log import log
import six
import xml.etree.cElementTree as etree
from manila import exception
from manila.i18n import _
from manila.share.drivers.glusterfs import common
from manila.share.drivers.glusterfs import layout
from manila import utils
LOG = log.getLogger(__name__)
glusterfs_directory_mapped_opts = [
cfg.StrOpt('glusterfs_target',
help='Specifies the GlusterFS volume to be mounted on the '
'Manila host. It is of the form '
'[remoteuser@]<volserver>:<volid>.'),
cfg.StrOpt('glusterfs_mount_point_base',
default='$state_path/mnt',
help='Base directory containing mount points for Gluster '
'volumes.'),
]
CONF = cfg.CONF
CONF.register_opts(glusterfs_directory_mapped_opts)
class GlusterfsDirectoryMappedLayout(layout.GlusterfsShareLayoutBase):
def __init__(self, driver, *args, **kwargs):
super(GlusterfsDirectoryMappedLayout, self).__init__(
driver, *args, **kwargs)
self.configuration.append_config_values(
common.glusterfs_common_opts)
self.configuration.append_config_values(
glusterfs_directory_mapped_opts)
def _glustermanager(self, gluster_address):
"""Create GlusterManager object for gluster_address."""
return common.GlusterManager(
gluster_address, self.driver._execute,
self.configuration.glusterfs_path_to_private_key,
self.configuration.glusterfs_server_password,
requires={'volume': True})
def do_setup(self, context):
"""Prepares the backend and appropriate NAS helpers."""
if not self.configuration.glusterfs_target:
raise exception.GlusterfsException(
_('glusterfs_target configuration that specifies the GlusterFS'
' volume to be mounted on the Manila host is not set.'))
self.gluster_manager = self._glustermanager(
self.configuration.glusterfs_target)
self.gluster_manager.check_gluster_version(
self.driver.GLUSTERFS_VERSION_MIN)
self._check_mount_glusterfs()
# enable quota options of a GlusteFS volume to allow
# creation of shares of specific size
args = ('volume', 'quota', self.gluster_manager.volume, 'enable')
try:
self.gluster_manager.gluster_call(*args)
except exception.GlusterfsException:
if (self.gluster_manager.
get_vol_option('features.quota')) != 'on':
LOG.exception("Error in tuning GlusterFS volume to enable "
"creation of shares of specific size.")
raise
self._ensure_gluster_vol_mounted()
def _share_manager(self, share):
comp_path = self.gluster_manager.components.copy()
comp_path.update({'path': '/' + share['name']})
return self._glustermanager(comp_path)
def _get_mount_point_for_gluster_vol(self):
"""Return mount point for the GlusterFS volume."""
return os.path.join(self.configuration.glusterfs_mount_point_base,
self.gluster_manager.volume)
def _ensure_gluster_vol_mounted(self):
"""Ensure GlusterFS volume is native-mounted on Manila host."""
mount_path = self._get_mount_point_for_gluster_vol()
try:
common._mount_gluster_vol(self.driver._execute,
self.gluster_manager.export, mount_path,
ensure=True)
except exception.GlusterfsException:
LOG.exception('Could not mount the Gluster volume %s',
self.gluster_manager.volume)
raise
def _get_local_share_path(self, share):
"""Determine mount path of the GlusterFS volume in the Manila host."""
local_vol_path = self._get_mount_point_for_gluster_vol()
if not os.access(local_vol_path, os.R_OK):
raise exception.GlusterfsException('share path %s does not exist' %
local_vol_path)
return os.path.join(local_vol_path, share['name'])
def _update_share_stats(self):
"""Retrieve stats info from the GlusterFS volume."""
# sanity check for gluster ctl mount
smpb = os.stat(self.configuration.glusterfs_mount_point_base)
smp = os.stat(self._get_mount_point_for_gluster_vol())
if smpb.st_dev == smp.st_dev:
raise exception.GlusterfsException(
_("GlusterFS control mount is not available")
)
smpv = os.statvfs(self._get_mount_point_for_gluster_vol())
return {'total_capacity_gb': (smpv.f_blocks * smpv.f_frsize) >> 30,
'free_capacity_gb': (smpv.f_bavail * smpv.f_frsize) >> 30}
def create_share(self, ctx, share, share_server=None):
"""Create a sub-directory/share in the GlusterFS volume."""
# probe into getting a NAS protocol helper for the share in order
# to facilitate early detection of unsupported protocol type
local_share_path = self._get_local_share_path(share)
cmd = ['mkdir', local_share_path]
try:
self.driver._execute(*cmd, run_as_root=True)
self._set_directory_quota(share, share['size'])
except Exception as exc:
if isinstance(exc, exception.ProcessExecutionError):
exc = exception.GlusterfsException(exc)
if isinstance(exc, exception.GlusterfsException):
self._cleanup_create_share(local_share_path, share['name'])
LOG.error('Unable to create share %s', share['name'])
raise exc
comp_share = self.gluster_manager.components.copy()
comp_share['path'] = '/' + share['name']
export_location = self.driver._setup_via_manager(
{'share': share,
'manager': self._glustermanager(comp_share)})
return export_location
def _cleanup_create_share(self, share_path, share_name):
"""Cleanup share that errored out during its creation."""
if os.path.exists(share_path):
cmd = ['rm', '-rf', share_path]
try:
self.driver._execute(*cmd, run_as_root=True)
except exception.ProcessExecutionError as exc:
LOG.error('Cannot cleanup share, %s, that errored out '
'during its creation, but exists in GlusterFS '
'volume.', share_name)
raise exception.GlusterfsException(exc)
def delete_share(self, context, share, share_server=None):
"""Remove a sub-directory/share from the GlusterFS volume."""
local_share_path = self._get_local_share_path(share)
cmd = ['rm', '-rf', local_share_path]
try:
self.driver._execute(*cmd, run_as_root=True)
except exception.ProcessExecutionError:
LOG.exception('Unable to delete share %s', share['name'])
raise
def ensure_share(self, context, share, share_server=None):
pass
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None, parent_share=None):
raise NotImplementedError
def create_snapshot(self, context, snapshot, share_server=None):
raise NotImplementedError
def delete_snapshot(self, context, snapshot, share_server=None):
raise NotImplementedError
def manage_existing(self, share, driver_options):
raise NotImplementedError
def unmanage(self, share):
raise NotImplementedError
def extend_share(self, share, new_size, share_server=None):
"""Extend a sub-directory/share in the GlusterFS volume."""
self._set_directory_quota(share, new_size)
def shrink_share(self, share, new_size, share_server=None):
"""Shrink a sub-directory/share in the GlusterFS volume."""
usage = self._get_directory_usage(share)
consumed_limit = int(math.ceil(usage))
if consumed_limit > new_size:
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
self._set_directory_quota(share, new_size)
def _set_directory_quota(self, share, new_size):
sizestr = six.text_type(new_size) + 'GB'
share_dir = '/' + share['name']
args = ('volume', 'quota', self.gluster_manager.volume,
'limit-usage', share_dir, sizestr)
try:
self.gluster_manager.gluster_call(*args)
except exception.GlusterfsException:
LOG.error('Unable to set quota share %s', share['name'])
raise
def _get_directory_usage(self, share):
share_dir = '/' + share['name']
args = ('--xml', 'volume', 'quota', self.gluster_manager.volume,
'list', share_dir)
try:
out, err = self.gluster_manager.gluster_call(*args)
except exception.GlusterfsException:
LOG.error('Unable to get quota share %s', share['name'])
raise
volxml = etree.fromstring(out)
usage_byte = volxml.find('./volQuota/limit/used_space').text
usage = utils.translate_string_size_to_float(usage_byte)
return usage