glusterfs: directory mapped share layout
The directory mapped layout has been isolated from glusterfs driver's management logic, and captured in a layout class. This enables the glusterfs driver to work with several layouts -- as of the time of writing this, with both layouts implemented so far: - directory mapped layout (the old way, also the default); - volume mapped layout. Partially implements bp modular-glusterfs-share-layouts Change-Id: I832f8ee3defcb9b76b3e7db0e1ce64a52abe09b7
This commit is contained in:
parent
bf3c40e439
commit
b6675d96f1
|
@ -55,6 +55,7 @@ import manila.share.drivers.emc.plugins.isilon.isilon
|
|||
import manila.share.drivers.generic
|
||||
import manila.share.drivers.glusterfs
|
||||
import manila.share.drivers.glusterfs.layout
|
||||
import manila.share.drivers.glusterfs.layout_directory
|
||||
import manila.share.drivers.glusterfs.layout_volume
|
||||
import manila.share.drivers.hdfs.hdfs_native
|
||||
import manila.share.drivers.hds.sop
|
||||
|
@ -115,6 +116,8 @@ _global_opt_lists = [
|
|||
manila.share.drivers.generic.share_opts,
|
||||
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
|
||||
manila.share.drivers.glusterfs.layout.glusterfs_share_layout_opts,
|
||||
manila.share.drivers.glusterfs.layout_directory.
|
||||
glusterfs_directory_mapped_opts,
|
||||
manila.share.drivers.glusterfs.layout_volume.glusterfs_volume_mapped_opts,
|
||||
manila.share.drivers.hdfs.hdfs_native.hdfs_native_share_opts,
|
||||
manila.share.drivers.hds.sop.hdssop_share_opts,
|
||||
|
|
|
@ -24,47 +24,27 @@ server supports only NFSv3 protocol.
|
|||
TODO(rraja): support SMB protocol.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LE
|
||||
from manila.share import driver
|
||||
from manila.share.drivers import ganesha
|
||||
from manila.share.drivers.ganesha import utils as ganesha_utils
|
||||
from manila.share.drivers.glusterfs import common
|
||||
from manila.share.drivers.glusterfs import layout
|
||||
from manila import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
GlusterfsManilaShare_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.'),
|
||||
cfg.StrOpt('glusterfs_nfs_server_type',
|
||||
default='Gluster',
|
||||
help='Type of NFS server that mediate access to the Gluster '
|
||||
'volumes (Gluster or Ganesha).'),
|
||||
cfg.StrOpt('glusterfs_server_password',
|
||||
default=None,
|
||||
secret=True,
|
||||
help="Remote GlusterFS server node's login password. "
|
||||
"This is not required if 'glusterfs_path_to_private_key'"
|
||||
' is configured.'),
|
||||
cfg.StrOpt('glusterfs_path_to_private_key',
|
||||
default=None,
|
||||
help='Path of Manila host\'s private SSH key file.'),
|
||||
cfg.StrOpt('glusterfs_ganesha_server_ip',
|
||||
default=None,
|
||||
help="Remote Ganesha server node's IP address."),
|
||||
|
@ -85,191 +65,77 @@ CONF.register_opts(GlusterfsManilaShare_opts)
|
|||
NFS_EXPORT_DIR = 'nfs.export-dir'
|
||||
NFS_EXPORT_VOL = 'nfs.export-volumes'
|
||||
|
||||
GLUSTERFS_VERSION_MIN = (3, 5)
|
||||
|
||||
|
||||
class GlusterfsShareDriver(driver.ExecuteMixin, driver.GaneshaMixin,
|
||||
driver.ShareDriver,):
|
||||
layout.GlusterfsShareDriverBase):
|
||||
"""Execute commands relating to Shares."""
|
||||
|
||||
GLUSTERFS_VERSION_MIN = (3, 5)
|
||||
|
||||
supported_layouts = ('layout_directory.GlusterfsDirectoryMappedLayout',
|
||||
'layout_volume.GlusterfsVolumeMappedLayout')
|
||||
supported_protocols = ('NFS',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GlusterfsShareDriver, self).__init__(False, *args, **kwargs)
|
||||
self._helpers = {}
|
||||
self.gluster_manager = None
|
||||
self.configuration.append_config_values(GlusterfsManilaShare_opts)
|
||||
self.backend_name = self.configuration.safe_get(
|
||||
'share_backend_name') or 'GlusterFS'
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Prepares the backend and appropriate NAS helpers."""
|
||||
super(GlusterfsShareDriver, self).do_setup(context)
|
||||
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 = common.GlusterManager(
|
||||
self.configuration.glusterfs_target,
|
||||
self._execute,
|
||||
self.configuration.glusterfs_path_to_private_key,
|
||||
self.configuration.glusterfs_server_password,
|
||||
requires={'volume': True}
|
||||
)
|
||||
self.gluster_manager.check_gluster_version(GLUSTERFS_VERSION_MIN)
|
||||
try:
|
||||
self._execute('mount.glusterfs', check_exit_code=False)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENOENT:
|
||||
raise exception.GlusterfsException(
|
||||
_('mount.glusterfs is not installed.'))
|
||||
else:
|
||||
raise
|
||||
|
||||
# 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.ProcessExecutionError as exc:
|
||||
if (self.gluster_manager.
|
||||
get_gluster_vol_option('features.quota')) != 'on':
|
||||
LOG.error(_LE("Error in tuning GlusterFS volume to enable "
|
||||
"creation of shares of specific size: %s"),
|
||||
exc.stderr)
|
||||
raise exception.GlusterfsException(exc)
|
||||
|
||||
self._setup_helpers()
|
||||
self._ensure_gluster_vol_mounted()
|
||||
|
||||
def _setup_helpers(self):
|
||||
"""Initializes protocol-specific NAS drivers."""
|
||||
# TODO(rraja): The below seems crude. Accommodate CIFS helper as well?
|
||||
nfs_helper = getattr(
|
||||
self.nfs_helper = getattr(
|
||||
sys.modules[__name__],
|
||||
self.configuration.glusterfs_nfs_server_type + 'NFSHelper')
|
||||
self._helpers['NFS'] = nfs_helper(self._execute,
|
||||
self.configuration,
|
||||
gluster_manager=self.gluster_manager)
|
||||
for helper in self._helpers.values():
|
||||
helper.init_helper()
|
||||
|
||||
def do_setup(self, context):
|
||||
# in order to do an initial instantialization of the helper
|
||||
self._get_helper()
|
||||
super(GlusterfsShareDriver, self).do_setup(context)
|
||||
|
||||
def _setup_via_manager(self, gluster_manager, gluster_manager_parent=None):
|
||||
# exporting the whole volume must be prohibited
|
||||
# to not to defeat access control
|
||||
args = ('volume', 'set', gluster_manager.volume, NFS_EXPORT_VOL,
|
||||
'off')
|
||||
try:
|
||||
gluster_manager.gluster_call(*args)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
LOG.error(_LE("Error in tuning GlusterFS volume to prevent "
|
||||
"exporting the entire volume: %s"), exc.stderr)
|
||||
raise exception.GlusterfsException("gluster %s failed" %
|
||||
' '.join(args))
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
||||
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._execute,
|
||||
self.gluster_manager.export, mount_path,
|
||||
ensure=True)
|
||||
except exception.GlusterfsException:
|
||||
LOG.error(_LE('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())
|
||||
|
||||
data = dict(
|
||||
storage_protocol='NFS',
|
||||
vendor_name='Red Hat',
|
||||
share_backend_name=self.backend_name,
|
||||
reserved_percentage=self.configuration.reserved_share_percentage,
|
||||
total_capacity_gb=(smpv.f_blocks * smpv.f_frsize) >> 30,
|
||||
free_capacity_gb=(smpv.f_bavail * smpv.f_frsize) >> 30)
|
||||
reserved_percentage=self.configuration.reserved_share_percentage)
|
||||
super(GlusterfsShareDriver, self)._update_share_stats(data)
|
||||
|
||||
def get_network_allocations_number(self):
|
||||
return 0
|
||||
|
||||
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
|
||||
self._get_helper(share)
|
||||
sizestr = six.text_type(share['size']) + 'GB'
|
||||
share_dir = '/' + share['name']
|
||||
local_share_path = self._get_local_share_path(share)
|
||||
cmd = ['mkdir', local_share_path]
|
||||
# set hard limit quota on the sub-directory/share
|
||||
args = ('volume', 'quota', self.gluster_manager.volume,
|
||||
'limit-usage', share_dir, sizestr)
|
||||
try:
|
||||
self._execute(*cmd, run_as_root=True)
|
||||
self.gluster_manager.gluster_call(*args)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
self._cleanup_create_share(local_share_path, share['name'])
|
||||
LOG.error(_LE('Unable to create share %s'), share['name'])
|
||||
raise exception.GlusterfsException(exc)
|
||||
|
||||
export_location = os.path.join(self.gluster_manager.qualified,
|
||||
share['name'])
|
||||
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._execute(*cmd, run_as_root=True)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
LOG.error(_LE('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._execute(*cmd, run_as_root=True)
|
||||
except exception.ProcessExecutionError:
|
||||
LOG.error(_LE('Unable to delete share %s'), share['name'])
|
||||
raise
|
||||
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
"""Might not be needed?"""
|
||||
pass
|
||||
|
||||
def _get_helper(self, share):
|
||||
def _get_helper(self, gluster_mgr=None):
|
||||
"""Choose a protocol specific helper class."""
|
||||
if share['share_proto'] == 'NFS':
|
||||
return self._helpers['NFS']
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=(_('Unsupported share type, %s.')
|
||||
% share['share_proto']))
|
||||
helper = self.nfs_helper(self._execute, self.configuration,
|
||||
gluster_manager=gluster_mgr)
|
||||
helper.init_helper()
|
||||
return helper
|
||||
|
||||
def allow_access(self, context, share, access, share_server=None):
|
||||
def _allow_access_via_manager(self, gluster_mgr, context, share, access,
|
||||
share_server=None):
|
||||
"""Allow access to the share."""
|
||||
self._get_helper(share).allow_access('/', share, access)
|
||||
self._get_helper(gluster_mgr).allow_access('/', share, access)
|
||||
|
||||
def deny_access(self, context, share, access, share_server=None):
|
||||
"""Deny access to the share."""
|
||||
self._get_helper(share).deny_access('/', share, access)
|
||||
def _deny_access_via_manager(self, gluster_mgr, context, share, access,
|
||||
share_server=None):
|
||||
"""Allow access to the share."""
|
||||
self._get_helper(gluster_mgr).deny_access('/', share, access)
|
||||
|
||||
|
||||
class GlusterNFSHelper(ganesha.NASHelperBase):
|
||||
|
@ -280,19 +146,6 @@ class GlusterNFSHelper(ganesha.NASHelperBase):
|
|||
super(GlusterNFSHelper, self).__init__(execute, config_object,
|
||||
**kwargs)
|
||||
|
||||
def init_helper(self):
|
||||
# exporting the whole volume must be prohibited
|
||||
# to not to defeat access control
|
||||
args = ('volume', 'set', self.gluster_manager.volume, NFS_EXPORT_VOL,
|
||||
'off')
|
||||
try:
|
||||
self.gluster_manager.gluster_call(*args)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
LOG.error(_LE("Error in tuning GlusterFS volume to prevent "
|
||||
"exporting the entire volume: %s"), exc.stderr)
|
||||
raise exception.GlusterfsException("gluster %s failed" %
|
||||
' '.join(args))
|
||||
|
||||
def _get_export_dir_dict(self):
|
||||
"""Get the export entries of shares in the GlusterFS volume."""
|
||||
export_dir = self.gluster_manager.get_gluster_vol_option(
|
||||
|
@ -364,7 +217,10 @@ class GlusterNFSHelper(ganesha.NASHelperBase):
|
|||
if host in ddict[edir]:
|
||||
return True
|
||||
ddict[edir].append(host)
|
||||
self._manage_access(share['name'], access['access_type'],
|
||||
path = self.gluster_manager.path
|
||||
if not path:
|
||||
path = "/"
|
||||
self._manage_access(path[1:], access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
def deny_access(self, base, share, access):
|
||||
|
@ -375,12 +231,17 @@ class GlusterNFSHelper(ganesha.NASHelperBase):
|
|||
ddict[edir].remove(host)
|
||||
if not ddict[edir]:
|
||||
ddict.pop(edir)
|
||||
self._manage_access(share['name'], access['access_type'],
|
||||
path = self.gluster_manager.path
|
||||
if not path:
|
||||
path = "/"
|
||||
self._manage_access(path[1:], access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
|
||||
class GaneshaNFSHelper(ganesha.GaneshaNASHelper):
|
||||
|
||||
shared_data = {}
|
||||
|
||||
def __init__(self, execute, config_object, **kwargs):
|
||||
self.gluster_manager = kwargs.pop('gluster_manager')
|
||||
if config_object.glusterfs_ganesha_server_ip:
|
||||
|
@ -391,9 +252,29 @@ class GaneshaNFSHelper(ganesha.GaneshaNASHelper):
|
|||
privatekey=config_object.glusterfs_path_to_private_key)
|
||||
else:
|
||||
execute = ganesha_utils.RootExecutor(execute)
|
||||
ganesha_host = config_object.glusterfs_ganesha_server_ip
|
||||
if not ganesha_host:
|
||||
ganesha_host = 'localhost'
|
||||
kwargs['tag'] = '-'.join(('GLUSTER', 'Ganesha', ganesha_host))
|
||||
super(GaneshaNFSHelper, self).__init__(execute, config_object,
|
||||
**kwargs)
|
||||
|
||||
def init_helper(self):
|
||||
@utils.synchronized(self.tag)
|
||||
def _init_helper():
|
||||
if self.tag in self.shared_data:
|
||||
return True
|
||||
super(GaneshaNFSHelper, self).init_helper()
|
||||
self.shared_data[self.tag] = {
|
||||
'ganesha': self.ganesha,
|
||||
'export_template': self.export_template}
|
||||
return False
|
||||
|
||||
if _init_helper():
|
||||
tagdata = self.shared_data[self.tag]
|
||||
self.ganesha = tagdata['ganesha']
|
||||
self.export_template = tagdata['export_template']
|
||||
|
||||
def _default_config_hook(self):
|
||||
"""Callback to provide default export block."""
|
||||
dconf = super(GaneshaNFSHelper, self)._default_config_hook()
|
||||
|
@ -405,4 +286,4 @@ class GaneshaNFSHelper(ganesha.GaneshaNASHelper):
|
|||
"""Callback to create FSAL subblock."""
|
||||
return {"Hostname": self.gluster_manager.host,
|
||||
"Volume": self.gluster_manager.volume,
|
||||
"Volpath": "/" + share['name']}
|
||||
"Volpath": self.gluster_manager.path}
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
# 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 os
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LE
|
||||
from manila.share.drivers.glusterfs import common
|
||||
from manila.share.drivers.glusterfs import layout
|
||||
|
||||
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.'),
|
||||
cfg.StrOpt('glusterfs_server_password',
|
||||
default=None,
|
||||
secret=True,
|
||||
help="Remote GlusterFS server node's login password. "
|
||||
"This is not required if 'glusterfs_path_to_private_key'"
|
||||
' is configured.'),
|
||||
cfg.StrOpt('glusterfs_path_to_private_key',
|
||||
default=None,
|
||||
help='Path of Manila host\'s private SSH key file.'),
|
||||
]
|
||||
|
||||
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(
|
||||
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.ProcessExecutionError as exc:
|
||||
if (self.gluster_manager.
|
||||
get_gluster_vol_option('features.quota')) != 'on':
|
||||
LOG.error(_LE("Error in tuning GlusterFS volume to enable "
|
||||
"creation of shares of specific size: %s"),
|
||||
exc.stderr)
|
||||
raise exception.GlusterfsException(exc)
|
||||
|
||||
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.error(_LE('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
|
||||
sizestr = six.text_type(share['size']) + 'GB'
|
||||
share_dir = '/' + share['name']
|
||||
local_share_path = self._get_local_share_path(share)
|
||||
cmd = ['mkdir', local_share_path]
|
||||
# set hard limit quota on the sub-directory/share
|
||||
args = ('volume', 'quota', self.gluster_manager.volume,
|
||||
'limit-usage', share_dir, sizestr)
|
||||
try:
|
||||
self.driver._execute(*cmd, run_as_root=True)
|
||||
self.gluster_manager.gluster_call(*args)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
self._cleanup_create_share(local_share_path, share['name'])
|
||||
LOG.error(_LE('Unable to create share %s'), share['name'])
|
||||
raise exception.GlusterfsException(exc)
|
||||
|
||||
export_location = os.path.join(self.gluster_manager.qualified,
|
||||
share['name'])
|
||||
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(_LE('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.error(_LE('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):
|
||||
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):
|
||||
raise NotImplementedError
|
||||
|
||||
def shrink_share(self, share, new_size, share_server=None):
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,375 @@
|
|||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share import configuration as config
|
||||
from manila.share.drivers.glusterfs import common
|
||||
from manila.share.drivers.glusterfs import layout_directory
|
||||
from manila import test
|
||||
from manila.tests import fake_share
|
||||
from manila.tests import fake_utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
fake_gluster_manager_attrs = {
|
||||
'export': '127.0.0.1:/testvol',
|
||||
'host': '127.0.0.1',
|
||||
'qualified': 'testuser@127.0.0.1:/testvol',
|
||||
'user': 'testuser',
|
||||
'volume': 'testvol',
|
||||
'path_to_private_key': '/fakepath/to/privatekey',
|
||||
'remote_server_password': 'fakepassword',
|
||||
'components': {'user': 'testuser', 'host': '127.0.0.1',
|
||||
'volume': 'testvol', 'path': None}
|
||||
}
|
||||
|
||||
fake_local_share_path = '/mnt/nfs/testvol/fakename'
|
||||
|
||||
fake_path_to_private_key = '/fakepath/to/privatekey'
|
||||
fake_remote_server_password = 'fakepassword'
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class GlusterfsDirectoryMappedLayoutTestCase(test.TestCase):
|
||||
"""Tests GlusterfsDirectoryMappedLayout."""
|
||||
|
||||
def setUp(self):
|
||||
super(GlusterfsDirectoryMappedLayoutTestCase, self).setUp()
|
||||
fake_utils.stub_out_utils_execute(self)
|
||||
self._execute = fake_utils.fake_execute
|
||||
self._context = context.get_admin_context()
|
||||
self.addCleanup(fake_utils.fake_execute_set_repliers, [])
|
||||
self.addCleanup(fake_utils.fake_execute_clear_log)
|
||||
|
||||
CONF.set_default('glusterfs_target', '127.0.0.1:/testvol')
|
||||
CONF.set_default('glusterfs_mount_point_base', '/mnt/nfs')
|
||||
CONF.set_default('glusterfs_server_password',
|
||||
fake_remote_server_password)
|
||||
CONF.set_default('glusterfs_path_to_private_key',
|
||||
fake_path_to_private_key)
|
||||
|
||||
self.fake_driver = mock.Mock()
|
||||
self.mock_object(self.fake_driver, '_execute',
|
||||
self._execute)
|
||||
self.fake_driver.GLUSTERFS_VERSION_MIN = (3, 6)
|
||||
self.fake_conf = config.Configuration(None)
|
||||
self.mock_object(common.GlusterManager, 'make_gluster_call')
|
||||
self._layout = layout_directory.GlusterfsDirectoryMappedLayout(
|
||||
self.fake_driver, configuration=self.fake_conf)
|
||||
self._layout.gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
|
||||
self.share = fake_share.fake_share(share_proto='NFS')
|
||||
|
||||
def test_do_setup(self):
|
||||
fake_gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
|
||||
self.mock_object(fake_gluster_manager, 'get_gluster_version',
|
||||
mock.Mock(return_value=('3', '5')))
|
||||
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
|
||||
for method in methods:
|
||||
self.mock_object(self._layout, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
|
||||
self._layout.do_setup(self._context)
|
||||
|
||||
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._layout.configuration.glusterfs_target, self._execute,
|
||||
self._layout.configuration.glusterfs_path_to_private_key,
|
||||
self._layout.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self._layout.gluster_manager.gluster_call.assert_called_once_with(
|
||||
'volume', 'quota', 'testvol', 'enable')
|
||||
self._layout._check_mount_glusterfs.assert_called_once_with()
|
||||
self._layout._ensure_gluster_vol_mounted.assert_called_once_with()
|
||||
|
||||
def test_do_setup_glusterfs_target_not_set(self):
|
||||
self._layout.configuration.glusterfs_target = None
|
||||
self.assertRaises(exception.GlusterfsException, self._layout.do_setup,
|
||||
self._context)
|
||||
|
||||
def test_do_setup_error_enabling_creation_share_specific_size(self):
|
||||
attrs = {'volume': 'testvol',
|
||||
'gluster_call.side_effect': exception.ProcessExecutionError,
|
||||
'get_gluster_vol_option.return_value': 'off'}
|
||||
fake_gluster_manager = mock.Mock(**attrs)
|
||||
self.mock_object(layout_directory.LOG, 'error')
|
||||
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
|
||||
for method in methods:
|
||||
self.mock_object(self._layout, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
|
||||
self.assertRaises(exception.GlusterfsException, self._layout.do_setup,
|
||||
self._context)
|
||||
|
||||
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._layout.configuration.glusterfs_target, self._execute,
|
||||
self._layout.configuration.glusterfs_path_to_private_key,
|
||||
self._layout.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self._layout.gluster_manager.gluster_call.assert_called_once_with(
|
||||
'volume', 'quota', 'testvol', 'enable')
|
||||
(self._layout.gluster_manager.get_gluster_vol_option.
|
||||
assert_called_once_with('features.quota'))
|
||||
layout_directory.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
self._layout._check_mount_glusterfs.assert_called_once_with()
|
||||
self.assertFalse(self._layout._ensure_gluster_vol_mounted.called)
|
||||
|
||||
def test_do_setup_error_already_enabled_creation_share_specific_size(self):
|
||||
attrs = {'volume': 'testvol',
|
||||
'gluster_call.side_effect': exception.ProcessExecutionError,
|
||||
'get_gluster_vol_option.return_value': 'on'}
|
||||
fake_gluster_manager = mock.Mock(**attrs)
|
||||
self.mock_object(layout_directory.LOG, 'error')
|
||||
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
|
||||
for method in methods:
|
||||
self.mock_object(self._layout, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
|
||||
self._layout.do_setup(self._context)
|
||||
|
||||
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._layout.configuration.glusterfs_target, self._execute,
|
||||
self._layout.configuration.glusterfs_path_to_private_key,
|
||||
self._layout.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self._layout.gluster_manager.gluster_call.assert_called_once_with(
|
||||
'volume', 'quota', 'testvol', 'enable')
|
||||
(self._layout.gluster_manager.get_gluster_vol_option.
|
||||
assert_called_once_with('features.quota'))
|
||||
self.assertFalse(layout_directory.LOG.error.called)
|
||||
self._layout._check_mount_glusterfs.assert_called_once_with()
|
||||
self._layout._ensure_gluster_vol_mounted.assert_called_once_with()
|
||||
|
||||
def test_share_manager(self):
|
||||
self._layout._glustermanager = mock.Mock()
|
||||
|
||||
self._layout._share_manager(self.share)
|
||||
|
||||
self._layout._glustermanager.assert_called_once_with(
|
||||
{'user': 'testuser', 'host': '127.0.0.1',
|
||||
'volume': 'testvol', 'path': '/fakename'})
|
||||
|
||||
def test_ensure_gluster_vol_mounted(self):
|
||||
common._mount_gluster_vol = mock.Mock()
|
||||
|
||||
self._layout._ensure_gluster_vol_mounted()
|
||||
|
||||
self.assertTrue(common._mount_gluster_vol.called)
|
||||
|
||||
def test_ensure_gluster_vol_mounted_error(self):
|
||||
common._mount_gluster_vol =\
|
||||
mock.Mock(side_effect=exception.GlusterfsException)
|
||||
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._layout._ensure_gluster_vol_mounted)
|
||||
|
||||
def test_get_local_share_path(self):
|
||||
with mock.patch.object(os, 'access', return_value=True):
|
||||
|
||||
ret = self._layout._get_local_share_path(self.share)
|
||||
|
||||
self.assertEqual('/mnt/nfs/testvol/fakename', ret)
|
||||
|
||||
def test_local_share_path_not_exists(self):
|
||||
with mock.patch.object(os, 'access', return_value=False):
|
||||
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._layout._get_local_share_path,
|
||||
self.share)
|
||||
|
||||
def test_update_share_stats(self):
|
||||
test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288,
|
||||
f_bavail=524288)
|
||||
self._layout._get_mount_point_for_gluster_vol = \
|
||||
mock.Mock(return_value='/mnt/nfs/testvol')
|
||||
some_no = 42
|
||||
not_some_no = some_no + 1
|
||||
os_stat = lambda path: mock.Mock(st_dev=some_no) if path == '/mnt/nfs' \
|
||||
else mock.Mock(st_dev=not_some_no)
|
||||
with mock.patch.object(os, 'statvfs', return_value=test_statvfs):
|
||||
with mock.patch.object(os, 'stat', os_stat):
|
||||
|
||||
ret = self._layout._update_share_stats()
|
||||
|
||||
test_data = {
|
||||
'total_capacity_gb': 2,
|
||||
'free_capacity_gb': 2,
|
||||
}
|
||||
self.assertEqual(test_data, ret)
|
||||
|
||||
def test_update_share_stats_gluster_mnt_unavailable(self):
|
||||
self._layout._get_mount_point_for_gluster_vol = \
|
||||
mock.Mock(return_value='/mnt/nfs/testvol')
|
||||
some_no = 42
|
||||
with mock.patch.object(os, 'stat',
|
||||
return_value=mock.Mock(st_dev=some_no)):
|
||||
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._layout._update_share_stats)
|
||||
|
||||
def test_create_share(self):
|
||||
exec_cmd1 = 'mkdir %s' % fake_local_share_path
|
||||
expected_exec = [exec_cmd1, ]
|
||||
expected_ret = 'testuser@127.0.0.1:/testvol/fakename'
|
||||
self.mock_object(
|
||||
self._layout, '_get_local_share_path',
|
||||
mock.Mock(return_value=fake_local_share_path))
|
||||
|
||||
ret = self._layout.create_share(self._context, self.share)
|
||||
|
||||
self._layout._get_local_share_path.called_once_with(self.share)
|
||||
self._layout.gluster_manager.gluster_call.assert_called_once_with(
|
||||
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '1GB')
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertEqual(expected_ret, ret)
|
||||
|
||||
def test_create_share_unable_to_create_share(self):
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
|
||||
self.mock_object(
|
||||
self._layout, '_get_local_share_path',
|
||||
mock.Mock(return_value=fake_local_share_path))
|
||||
self.mock_object(self._layout, '_cleanup_create_share')
|
||||
self.mock_object(layout_directory.LOG, 'error')
|
||||
expected_exec = ['mkdir %s' % fake_local_share_path]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0],
|
||||
exec_runner)])
|
||||
|
||||
self.assertRaises(
|
||||
exception.GlusterfsException, self._layout.create_share,
|
||||
self._context, self.share)
|
||||
|
||||
self._layout._get_local_share_path.called_once_with(self.share)
|
||||
self._layout._cleanup_create_share.assert_called_once_with(
|
||||
fake_local_share_path, self.share['name'])
|
||||
layout_directory.LOG.error.assert_called_once_with(
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_create_share_can_be_called_with_extra_arg_share_server(self):
|
||||
self._layout._get_local_share_path = mock.Mock()
|
||||
with mock.patch.object(os.path, 'join', return_value=None):
|
||||
|
||||
share_server = None
|
||||
ret = self._layout.create_share(self._context, self.share,
|
||||
share_server)
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self._layout._get_local_share_path.called_once_with(self.share)
|
||||
self._layout._get_local_share_path.assert_called_once_with(
|
||||
self.share)
|
||||
os.path.join.assert_called_once_with(
|
||||
self._layout.gluster_manager.qualified, self.share['name'])
|
||||
|
||||
def test_cleanup_create_share_local_share_path_exists(self):
|
||||
expected_exec = ['rm -rf %s' % fake_local_share_path]
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
|
||||
|
||||
ret = self._layout._cleanup_create_share(fake_local_share_path,
|
||||
self.share['name'])
|
||||
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_cleanup_create_share_cannot_cleanup_unusable_share(self):
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
|
||||
expected_exec = ['rm -rf %s' % fake_local_share_path]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0],
|
||||
exec_runner)])
|
||||
self.mock_object(layout_directory.LOG, 'error')
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
|
||||
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._layout._cleanup_create_share,
|
||||
fake_local_share_path, self.share['name'])
|
||||
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
layout_directory.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
|
||||
def test_cleanup_create_share_local_share_path_does_not_exist(self):
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=False))
|
||||
|
||||
ret = self._layout._cleanup_create_share(fake_local_share_path,
|
||||
self.share['name'])
|
||||
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_delete_share(self):
|
||||
self._layout._get_local_share_path =\
|
||||
mock.Mock(return_value='/mnt/nfs/testvol/fakename')
|
||||
|
||||
self._layout.delete_share(self._context, self.share)
|
||||
|
||||
self.assertEqual(['rm -rf /mnt/nfs/testvol/fakename'],
|
||||
fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_cannot_delete_share(self):
|
||||
self._layout._get_local_share_path =\
|
||||
mock.Mock(return_value='/mnt/nfs/testvol/fakename')
|
||||
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
|
||||
expected_exec = ['rm -rf %s' % (self._layout._get_local_share_path())]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._layout.delete_share, self._context, self.share)
|
||||
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_delete_share_can_be_called_with_extra_arg_share_server(self):
|
||||
self._layout._get_local_share_path = mock.Mock()
|
||||
|
||||
share_server = None
|
||||
ret = self._layout.delete_share(self._context, self.share,
|
||||
share_server)
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self._layout._get_local_share_path.assert_called_once_with(self.share)
|
||||
|
||||
def test_ensure_share(self):
|
||||
self.assertIsNone(self._layout.ensure_share(self._context, self.share))
|
||||
|
||||
@ddt.data(
|
||||
('create_share_from_snapshot', ('context', 'share', 'snapshot'),
|
||||
{'share_server': None}),
|
||||
('create_snapshot', ('context', 'snapshot'), {'share_server': None}),
|
||||
('delete_snapshot', ('context', 'snapshot'), {'share_server': None}),
|
||||
('manage_existing', ('share', 'driver_options'), {}),
|
||||
('unmanage', ('share',), {}),
|
||||
('extend_share', ('share', 'new_size'), {'share_server': None}),
|
||||
('shrink_share', ('share', 'new_size'), {'share_server': None}))
|
||||
def test_nonimplemented_methods(self, method_invocation):
|
||||
method, args, kwargs = method_invocation
|
||||
self.assertRaises(NotImplementedError, getattr(self._layout, method),
|
||||
*args, **kwargs)
|
|
@ -14,17 +14,17 @@
|
|||
# under the License.
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import os
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share import configuration as config
|
||||
from manila.share.drivers import ganesha
|
||||
from manila.share.drivers import glusterfs
|
||||
from manila.share.drivers.glusterfs import common
|
||||
from manila.share.drivers.glusterfs import layout
|
||||
from manila import test
|
||||
from manila.tests import fake_share
|
||||
from manila.tests import fake_utils
|
||||
|
@ -43,15 +43,12 @@ fake_gluster_manager_attrs = {
|
|||
'remote_server_password': 'fakepassword',
|
||||
}
|
||||
|
||||
fake_local_share_path = '/mnt/nfs/testvol/fakename'
|
||||
|
||||
fake_path_to_private_key = '/fakepath/to/privatekey'
|
||||
fake_remote_server_password = 'fakepassword'
|
||||
fake_share_name = 'fakename'
|
||||
NFS_EXPORT_DIR = 'nfs.export-dir'
|
||||
NFS_EXPORT_VOL = 'nfs.export-volumes'
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class GlusterfsShareDriverTestCase(test.TestCase):
|
||||
"""Tests GlusterfsShareDriver."""
|
||||
|
||||
|
@ -63,387 +60,94 @@ class GlusterfsShareDriverTestCase(test.TestCase):
|
|||
self.addCleanup(fake_utils.fake_execute_set_repliers, [])
|
||||
self.addCleanup(fake_utils.fake_execute_clear_log)
|
||||
|
||||
CONF.set_default('glusterfs_target', '127.0.0.1:/testvol')
|
||||
CONF.set_default('glusterfs_mount_point_base', '/mnt/nfs')
|
||||
CONF.set_default('reserved_share_percentage', 50)
|
||||
CONF.set_default('glusterfs_server_password',
|
||||
fake_remote_server_password)
|
||||
CONF.set_default('glusterfs_path_to_private_key',
|
||||
fake_path_to_private_key)
|
||||
CONF.set_default('driver_handles_share_servers', False)
|
||||
|
||||
self.fake_conf = config.Configuration(None)
|
||||
self._driver = glusterfs.GlusterfsShareDriver(
|
||||
execute=self._execute,
|
||||
configuration=self.fake_conf)
|
||||
self._driver.gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
|
||||
self._helper_nfs = mock.Mock()
|
||||
self.share = fake_share.fake_share(share_proto='NFS')
|
||||
|
||||
def test_do_setup(self):
|
||||
fake_gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
|
||||
self.mock_object(fake_gluster_manager, 'get_gluster_version',
|
||||
mock.Mock(return_value=('3', '5')))
|
||||
methods = ('_ensure_gluster_vol_mounted', '_setup_helpers')
|
||||
for method in methods:
|
||||
self.mock_object(self._driver, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
expected_exec = ['mount.glusterfs']
|
||||
exec_cmd1 = 'mount.glusterfs'
|
||||
expected_exec = [exec_cmd1]
|
||||
args = ('volume', 'quota', 'testvol', 'enable')
|
||||
self._driver.do_setup(self._context)
|
||||
self.assertEqual(fake_gluster_manager, self._driver.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._driver.configuration.glusterfs_target, self._execute,
|
||||
self._driver.configuration.glusterfs_path_to_private_key,
|
||||
self._driver.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self._driver.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
self._driver._setup_helpers.assert_called_once_with()
|
||||
self._driver._ensure_gluster_vol_mounted.assert_called_once_with()
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
self.mock_object(layout.GlusterfsShareDriverBase, 'do_setup')
|
||||
_context = mock.Mock()
|
||||
|
||||
def test_do_setup_glusterfs_target_not_set(self):
|
||||
self._driver.configuration.glusterfs_target = None
|
||||
self.assertRaises(exception.GlusterfsException, self._driver.do_setup,
|
||||
self._context)
|
||||
self._driver.do_setup(_context)
|
||||
|
||||
def test_do_setup_mount_glusterfs_not_installed(self):
|
||||
self._driver._get_helper.assert_called_once_with()
|
||||
layout.GlusterfsShareDriverBase.do_setup.assert_called_once_with(
|
||||
_context)
|
||||
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
|
||||
@ddt.data(True, False)
|
||||
def test_setup_via_manager(self, has_parent):
|
||||
gmgr = mock.Mock()
|
||||
gmgr.gluster_call = mock.Mock()
|
||||
gmgr_parent = mock.Mock() if has_parent else None
|
||||
|
||||
expected_exec = ['mount.glusterfs']
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
self._driver._setup_via_manager(
|
||||
gmgr, gluster_manager_parent=gmgr_parent)
|
||||
|
||||
self.assertRaises(exception.GlusterfsException, self._driver.do_setup,
|
||||
self._context)
|
||||
gmgr.gluster_call.assert_called_once_with(
|
||||
'volume', 'set', gmgr.volume, 'nfs.export-volumes', 'off')
|
||||
|
||||
def test_do_setup_error_enabling_creation_share_specific_size(self):
|
||||
attrs = {'volume': 'testvol',
|
||||
'gluster_call.side_effect': exception.ProcessExecutionError,
|
||||
'get_gluster_vol_option.return_value': 'off'}
|
||||
fake_gluster_manager = mock.Mock(**attrs)
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
methods = ('_ensure_gluster_vol_mounted', '_setup_helpers')
|
||||
for method in methods:
|
||||
self.mock_object(self._driver, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
expected_exec = ['mount.glusterfs']
|
||||
exec_cmd1 = 'mount.glusterfs'
|
||||
expected_exec = [exec_cmd1]
|
||||
args = ('volume', 'quota', 'testvol', 'enable')
|
||||
self.assertRaises(exception.GlusterfsException, self._driver.do_setup,
|
||||
self._context)
|
||||
self.assertEqual(fake_gluster_manager, self._driver.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._driver.configuration.glusterfs_target, self._execute,
|
||||
self._driver.configuration.glusterfs_path_to_private_key,
|
||||
self._driver.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self._driver.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
(self._driver.gluster_manager.get_gluster_vol_option.
|
||||
assert_called_once_with('features.quota'))
|
||||
glusterfs.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
self.assertFalse(self._driver._setup_helpers.called)
|
||||
self.assertFalse(self._driver._ensure_gluster_vol_mounted.called)
|
||||
@ddt.data(exception.ProcessExecutionError, RuntimeError)
|
||||
def test_setup_via_manager_exception(self, _exception):
|
||||
gmgr = mock.Mock()
|
||||
gmgr.gluster_call = mock.Mock(side_effect=_exception)
|
||||
gmgr.volume = 'somevol'
|
||||
|
||||
def test_do_setup_error_already_enabled_creation_share_specific_size(self):
|
||||
attrs = {'volume': 'testvol',
|
||||
'gluster_call.side_effect': exception.ProcessExecutionError,
|
||||
'get_gluster_vol_option.return_value': 'on'}
|
||||
fake_gluster_manager = mock.Mock(**attrs)
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
methods = ('_ensure_gluster_vol_mounted', '_setup_helpers')
|
||||
for method in methods:
|
||||
self.mock_object(self._driver, method)
|
||||
self.mock_object(common, 'GlusterManager',
|
||||
mock.Mock(return_value=fake_gluster_manager))
|
||||
expected_exec = ['mount.glusterfs']
|
||||
exec_cmd1 = 'mount.glusterfs'
|
||||
expected_exec = [exec_cmd1]
|
||||
args = ('volume', 'quota', 'testvol', 'enable')
|
||||
self._driver.do_setup(self._context)
|
||||
self.assertEqual(fake_gluster_manager, self._driver.gluster_manager)
|
||||
common.GlusterManager.assert_called_once_with(
|
||||
self._driver.configuration.glusterfs_target, self._execute,
|
||||
self._driver.configuration.glusterfs_path_to_private_key,
|
||||
self._driver.configuration.glusterfs_server_password,
|
||||
requires={'volume': True})
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self._driver.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
(self._driver.gluster_manager.get_gluster_vol_option.
|
||||
assert_called_once_with('features.quota'))
|
||||
self.assertFalse(glusterfs.LOG.error.called)
|
||||
self._driver._setup_helpers.assert_called_once_with()
|
||||
self._driver._ensure_gluster_vol_mounted.assert_called_once_with()
|
||||
self.assertRaises(
|
||||
{exception.ProcessExecutionError:
|
||||
exception.GlusterfsException}.get(
|
||||
_exception, _exception), self._driver._setup_via_manager, gmgr)
|
||||
|
||||
def test_setup_helpers(self):
|
||||
self.mock_object(glusterfs, 'GlusterNFSHelper',
|
||||
mock.Mock(return_value=self._helper_nfs))
|
||||
self._driver._setup_helpers()
|
||||
glusterfs.GlusterNFSHelper.assert_called_once_with(
|
||||
self._execute, self.fake_conf,
|
||||
gluster_manager=self._driver.gluster_manager)
|
||||
self.assertEqual(1, len(self._driver._helpers))
|
||||
self.assertEqual(self._helper_nfs, self._driver._helpers['NFS'])
|
||||
self._driver._helpers['NFS'].init_helper.assert_called_once_with()
|
||||
|
||||
def test_ensure_gluster_vol_mounted(self):
|
||||
common._mount_gluster_vol = mock.Mock()
|
||||
self._driver._ensure_gluster_vol_mounted()
|
||||
self.assertTrue(common._mount_gluster_vol.called)
|
||||
|
||||
def test_ensure_gluster_vol_mounted_error(self):
|
||||
common._mount_gluster_vol =\
|
||||
mock.Mock(side_effect=exception.GlusterfsException)
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._driver._ensure_gluster_vol_mounted)
|
||||
|
||||
def test_get_local_share_path(self):
|
||||
with mock.patch.object(os, 'access', return_value=True):
|
||||
expected_ret = '/mnt/nfs/testvol/fakename'
|
||||
ret = self._driver._get_local_share_path(self.share)
|
||||
self.assertEqual(ret, expected_ret)
|
||||
|
||||
def test_local_share_path_not_exists(self):
|
||||
with mock.patch.object(os, 'access', return_value=False):
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._driver._get_local_share_path,
|
||||
self.share)
|
||||
|
||||
def test_get_share_stats_refresh_false(self):
|
||||
self._driver._stats = mock.Mock()
|
||||
ret = self._driver.get_share_stats()
|
||||
self.assertEqual(ret, self._driver._stats)
|
||||
|
||||
def test_get_share_stats_refresh_true(self):
|
||||
def foo():
|
||||
self._driver._stats = {'key': 'value'}
|
||||
self._driver._update_share_stats = mock.Mock(side_effect=foo)
|
||||
ret = self._driver.get_share_stats(refresh=True)
|
||||
self.assertEqual(ret, {'key': 'value'})
|
||||
def test_check_for_setup_error(self):
|
||||
self._driver.check_for_setup_error()
|
||||
|
||||
def test_update_share_stats(self):
|
||||
test_data = {
|
||||
'share_backend_name': 'GlusterFS',
|
||||
'driver_handles_share_servers': False,
|
||||
'vendor_name': 'Red Hat',
|
||||
'driver_version': '1.0',
|
||||
'storage_protocol': 'NFS',
|
||||
'reserved_percentage': 50,
|
||||
'QoS_support': False,
|
||||
'total_capacity_gb': 2,
|
||||
'free_capacity_gb': 2,
|
||||
'pools': None,
|
||||
'snapshot_support': False,
|
||||
}
|
||||
test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288,
|
||||
f_bavail=524288)
|
||||
self._driver._get_mount_point_for_gluster_vol = \
|
||||
mock.Mock(return_value='/mnt/nfs/testvol')
|
||||
some_no = 42
|
||||
not_some_no = some_no + 1
|
||||
os_stat = lambda path: mock.Mock(st_dev=some_no) if path == '/mnt/nfs' \
|
||||
else mock.Mock(st_dev=not_some_no)
|
||||
with mock.patch.object(os, 'statvfs', return_value=test_statvfs):
|
||||
with mock.patch.object(os, 'stat', os_stat):
|
||||
self._driver._update_share_stats()
|
||||
self.assertEqual(self._driver._stats, test_data)
|
||||
self.mock_object(layout.GlusterfsShareDriverBase,
|
||||
'_update_share_stats')
|
||||
|
||||
def test_update_share_stats_gluster_mnt_unavailable(self):
|
||||
self._driver._get_mount_point_for_gluster_vol = \
|
||||
mock.Mock(return_value='/mnt/nfs/testvol')
|
||||
some_no = 42
|
||||
with mock.patch.object(os, 'stat',
|
||||
return_value=mock.Mock(st_dev=some_no)):
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._driver._update_share_stats)
|
||||
self._driver._update_share_stats()
|
||||
|
||||
def test_create_share(self):
|
||||
(layout.GlusterfsShareDriverBase._update_share_stats.
|
||||
assert_called_once_with({'storage_protocol': 'NFS',
|
||||
'vendor_name': 'Red Hat',
|
||||
'share_backend_name': 'GlusterFS',
|
||||
'reserved_percentage': 50}))
|
||||
|
||||
def test_get_network_allocations_number(self):
|
||||
self.assertEqual(0, self._driver.get_network_allocations_number())
|
||||
|
||||
def test_get_helper(self):
|
||||
ret = self._driver._get_helper()
|
||||
self.assertIsInstance(ret, self._driver.nfs_helper)
|
||||
|
||||
@ddt.data({'op': 'allow', 'kwargs': {}},
|
||||
{'op': 'allow', 'kwargs': {'share_server': None}},
|
||||
{'op': 'deny', 'kwargs': {}},
|
||||
{'op': 'deny', 'kwargs': {'share_server': None}})
|
||||
@ddt.unpack
|
||||
def test_allow_deny_access_via_manager(self, op, kwargs):
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
args = ('volume', 'quota', 'testvol', 'limit-usage', '/fakename',
|
||||
'1GB')
|
||||
exec_cmd1 = 'mkdir %s' % fake_local_share_path
|
||||
expected_exec = [exec_cmd1, ]
|
||||
expected_ret = 'testuser@127.0.0.1:/testvol/fakename'
|
||||
self.mock_object(
|
||||
self._driver, '_get_local_share_path',
|
||||
mock.Mock(return_value=fake_local_share_path))
|
||||
ret = self._driver.create_share(self._context, self.share)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_local_share_path.called_once_with(self.share)
|
||||
self._driver.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertEqual(expected_ret, ret)
|
||||
gmgr = mock.Mock()
|
||||
|
||||
def test_create_share_unable_to_create_share(self):
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
ret = getattr(self._driver, "_%s_access_via_manager" % op
|
||||
)(gmgr, self._context, self.share,
|
||||
fake_share.fake_access, **kwargs)
|
||||
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
self.mock_object(
|
||||
self._driver, '_get_local_share_path',
|
||||
mock.Mock(return_value=fake_local_share_path))
|
||||
self.mock_object(self._driver, '_cleanup_create_share')
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
expected_exec = ['mkdir %s' % fake_local_share_path]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0],
|
||||
exec_runner)])
|
||||
self.assertRaises(
|
||||
exception.GlusterfsException, self._driver.create_share,
|
||||
self._context, self.share)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_local_share_path.called_once_with(self.share)
|
||||
self._driver._cleanup_create_share.assert_called_once_with(
|
||||
fake_local_share_path, self.share['name'])
|
||||
glusterfs.LOG.error.assert_called_once_with(
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_create_share_error_unsupported_share_type(self):
|
||||
self.mock_object(
|
||||
self._driver, '_get_helper',
|
||||
mock.Mock(side_effect=exception.
|
||||
InvalidShare(reason="Unsupported Share type")))
|
||||
self.assertRaises(exception.InvalidShare, self._driver.create_share,
|
||||
self._context, self.share)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
|
||||
def test_create_share_can_be_called_with_extra_arg_share_server(self):
|
||||
share_server = None
|
||||
self._driver._get_local_share_path = mock.Mock()
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
with mock.patch.object(os.path, 'join', return_value=None):
|
||||
ret = self._driver.create_share(self._context, self.share,
|
||||
share_server)
|
||||
self.assertIsNone(ret)
|
||||
self._driver._get_local_share_path.called_once_with(self.share)
|
||||
self._driver._get_local_share_path.assert_called_once_with(
|
||||
self.share)
|
||||
os.path.join.assert_called_once_with(
|
||||
self._driver.gluster_manager.qualified, self.share['name'])
|
||||
|
||||
def test_cleanup_create_share_local_share_path_exists(self):
|
||||
expected_exec = ['rm -rf %s' % fake_local_share_path]
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
|
||||
ret = self._driver._cleanup_create_share(fake_local_share_path,
|
||||
self.share['name'])
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_cleanup_create_share_cannot_cleanup_unusable_share(self):
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
|
||||
expected_exec = ['rm -rf %s' % fake_local_share_path]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0],
|
||||
exec_runner)])
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._driver._cleanup_create_share,
|
||||
fake_local_share_path, self.share['name'])
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
glusterfs.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
|
||||
def test_cleanup_create_share_local_share_path_does_not_exist(self):
|
||||
self.mock_object(os.path, 'exists', mock.Mock(return_value=False))
|
||||
ret = self._driver._cleanup_create_share(fake_local_share_path,
|
||||
self.share['name'])
|
||||
os.path.exists.assert_called_once_with(fake_local_share_path)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_delete_share(self):
|
||||
self._driver._get_local_share_path =\
|
||||
mock.Mock(return_value='/mnt/nfs/testvol/fakename')
|
||||
|
||||
expected_exec = ['rm -rf /mnt/nfs/testvol/fakename']
|
||||
|
||||
self._driver.delete_share(self._context, self.share)
|
||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
||||
|
||||
def test_cannot_delete_share(self):
|
||||
self._driver._get_local_share_path =\
|
||||
mock.Mock(return_value='/mnt/nfs/testvol/fakename')
|
||||
|
||||
def exec_runner(*ignore_args, **ignore_kw):
|
||||
raise exception.ProcessExecutionError
|
||||
|
||||
expected_exec = ['rm -rf %s' % (self._driver._get_local_share_path())]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._driver.delete_share, self._context, self.share)
|
||||
|
||||
def test_delete_share_can_be_called_with_extra_arg_share_server(self):
|
||||
share_server = None
|
||||
self._driver._get_local_share_path = mock.Mock()
|
||||
ret = self._driver.delete_share(self._context, self.share,
|
||||
share_server)
|
||||
self.assertIsNone(ret)
|
||||
self._driver._get_local_share_path.assert_called_once_with(self.share)
|
||||
|
||||
def test_get_helper_NFS(self):
|
||||
self._driver._helpers['NFS'] = None
|
||||
ret = self._driver._get_helper(self.share)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_get_helper_not_implemented(self):
|
||||
share = fake_share.fake_share(share_proto='Others')
|
||||
self.assertRaises(
|
||||
exception.InvalidShare, self._driver._get_helper, share)
|
||||
|
||||
def test_allow_access(self):
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
ret = self._driver.allow_access(self._context, self.share,
|
||||
fake_share.fake_access)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_helper().\
|
||||
allow_access.assert_called_once_with(
|
||||
'/', self.share, fake_share.fake_access)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_allow_access_can_be_called_with_extra_arg_share_server(self):
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
ret = self._driver.allow_access(self._context, self.share,
|
||||
fake_share.fake_access,
|
||||
share_server=None)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_helper().\
|
||||
allow_access.assert_called_once_with(
|
||||
'/', self.share, fake_share.fake_access)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_deny_access(self):
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
ret = self._driver.deny_access(self._context, self.share,
|
||||
fake_share.fake_access)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_helper().\
|
||||
deny_access.assert_called_once_with(
|
||||
'/', self.share, fake_share.fake_access)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_deny_access_can_be_called_with_extra_arg_share_server(self):
|
||||
self.mock_object(self._driver, '_get_helper')
|
||||
ret = self._driver.deny_access(self._context, self.share,
|
||||
fake_share.fake_access,
|
||||
share_server=None)
|
||||
self._driver._get_helper.assert_called_once_with(self.share)
|
||||
self._driver._get_helper().\
|
||||
deny_access.assert_called_once_with(
|
||||
'/', self.share, fake_share.fake_access)
|
||||
self._driver._get_helper.assert_called_once_with(gmgr)
|
||||
getattr(
|
||||
self._driver._get_helper(),
|
||||
"%s_access" % op).assert_called_once_with(
|
||||
'/', self.share, fake_share.fake_access)
|
||||
self.assertIsNone(ret)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class GlusterNFSHelperTestCase(test.TestCase):
|
||||
"""Tests GlusterNFSHelper."""
|
||||
|
||||
|
@ -456,35 +160,14 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
self._helper = glusterfs.GlusterNFSHelper(
|
||||
self._execute, self.fake_conf, gluster_manager=gluster_manager)
|
||||
|
||||
def test_init_helper(self):
|
||||
args = ('volume', 'set', self._helper.gluster_manager.volume,
|
||||
NFS_EXPORT_VOL, 'off')
|
||||
self._helper.init_helper()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
|
||||
def test_init_helper_vol_error_gluster_vol_set(self):
|
||||
args = ('volume', 'set', self._helper.gluster_manager.volume,
|
||||
NFS_EXPORT_VOL, 'off')
|
||||
|
||||
def raise_exception(*args, **kwargs):
|
||||
raise exception.ProcessExecutionError()
|
||||
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
self.mock_object(self._helper.gluster_manager, 'gluster_call',
|
||||
mock.Mock(side_effect=raise_exception))
|
||||
self.assertRaises(exception.GlusterfsException,
|
||||
self._helper.init_helper)
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
glusterfs.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
|
||||
def test_get_export_dir_dict(self):
|
||||
output_str = '/foo(10.0.0.1|10.0.0.2),/bar(10.0.0.1)'
|
||||
self.mock_object(self._helper.gluster_manager,
|
||||
'get_gluster_vol_option',
|
||||
mock.Mock(return_value=output_str))
|
||||
|
||||
ret = self._helper._get_export_dir_dict()
|
||||
|
||||
self.assertEqual(
|
||||
{'foo': ['10.0.0.1', '10.0.0.2'], 'bar': ['10.0.0.1']}, ret)
|
||||
(self._helper.gluster_manager.get_gluster_vol_option.
|
||||
|
@ -503,9 +186,11 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
export_dir_dict = mock.Mock()
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
|
||||
ret = self._helper._manage_access(fake_share_name,
|
||||
access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
cbk.assert_called_once_with(export_dir_dict, fake_share_name,
|
||||
access['access_to'])
|
||||
|
@ -526,9 +211,11 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
NFS_EXPORT_DIR, export_str)
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
|
||||
ret = self._helper._manage_access(fake_share_name,
|
||||
access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
|
@ -555,10 +242,12 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
self.mock_object(self._helper.gluster_manager, 'gluster_call',
|
||||
mock.Mock(side_effect=raise_exception))
|
||||
self.mock_object(glusterfs.LOG, 'error')
|
||||
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._helper._manage_access,
|
||||
fake_share_name, access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
|
@ -575,9 +264,11 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
export_dir_dict = {'fakename': ['10.0.0.1']}
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
|
||||
ret = self._helper._manage_access(fake_share_name,
|
||||
access['access_type'],
|
||||
access['access_to'], cbk)
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
|
@ -588,22 +279,30 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
share = fake_share.fake_share()
|
||||
export_dir_dict = {'example.com': ['10.0.0.1']}
|
||||
export_str = '/example.com(10.0.0.1),/fakename(10.0.0.1)'
|
||||
args = ('volume', 'set', self._helper.gluster_manager.volume,
|
||||
NFS_EXPORT_DIR, export_str)
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
self._helper.gluster_manager.path = '/fakename'
|
||||
|
||||
self._helper.allow_access(None, share, access)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
'volume', 'set', self._helper.gluster_manager.volume,
|
||||
NFS_EXPORT_DIR, export_str)
|
||||
|
||||
def test_allow_access_with_share_having_access(self):
|
||||
@ddt.data({'share_key': 'fakename', 'gmgr_path': '/fakename'},
|
||||
{'share_key': '', 'gmgr_path': None})
|
||||
@ddt.unpack
|
||||
def test_allow_access_with_share_having_access(self, share_key, gmgr_path):
|
||||
access = fake_share.fake_access()
|
||||
share = fake_share.fake_share()
|
||||
export_dir_dict = {'fakename': ['10.0.0.1']}
|
||||
export_dir_dict = {share_key: ['10.0.0.1']}
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
self._helper.gluster_manager.path = gmgr_path
|
||||
|
||||
self._helper.allow_access(None, share, access)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self.assertFalse(self._helper.gluster_manager.gluster_call.called)
|
||||
|
||||
|
@ -613,23 +312,32 @@ class GlusterNFSHelperTestCase(test.TestCase):
|
|||
export_dir_dict = {}
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
self._helper.gluster_manager.path = '/fakename'
|
||||
|
||||
self._helper.deny_access(None, share, access)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self.assertFalse(self._helper.gluster_manager.gluster_call.called)
|
||||
|
||||
def test_deny_access_with_share_having_access(self):
|
||||
@ddt.data({'share_key': 'fakename', 'gmgr_path': '/fakename'},
|
||||
{'share_key': '', 'gmgr_path': None})
|
||||
@ddt.unpack
|
||||
def test_deny_access_with_share_having_access(self, share_key, gmgr_path):
|
||||
access = fake_share.fake_access()
|
||||
share = fake_share.fake_share()
|
||||
export_dir_dict = {
|
||||
'example.com': ['10.0.0.1'],
|
||||
'fakename': ['10.0.0.1'],
|
||||
share_key: ['10.0.0.1'],
|
||||
}
|
||||
export_str = '/example.com(10.0.0.1)'
|
||||
args = ('volume', 'set', self._helper.gluster_manager.volume,
|
||||
NFS_EXPORT_DIR, export_str)
|
||||
self.mock_object(self._helper, '_get_export_dir_dict',
|
||||
mock.Mock(return_value=export_dir_dict))
|
||||
self._helper.gluster_manager.path = gmgr_path
|
||||
|
||||
self._helper.deny_access(None, share, access)
|
||||
|
||||
self._helper._get_export_dir_dict.assert_called_once_with()
|
||||
self._helper.gluster_manager.gluster_call.assert_called_once_with(
|
||||
*args)
|
||||
|
@ -654,12 +362,14 @@ class GaneshaNFSHelperTestCase(test.TestCase):
|
|||
self._helper = glusterfs.GaneshaNFSHelper(
|
||||
self._execute, self.fake_conf,
|
||||
gluster_manager=self.gluster_manager)
|
||||
self._helper.tag = 'GLUSTER-Ganesha-localhost'
|
||||
|
||||
def test_init_local_ganesha_server(self):
|
||||
glusterfs.ganesha_utils.RootExecutor.assert_called_once_with(
|
||||
self._execute)
|
||||
glusterfs.ganesha.GaneshaNASHelper.__init__.assert_has_calls(
|
||||
[mock.call(self._root_execute, self.fake_conf)])
|
||||
[mock.call(self._root_execute, self.fake_conf,
|
||||
tag='GLUSTER-Ganesha-localhost')])
|
||||
|
||||
def test_init_remote_ganesha_server(self):
|
||||
ssh_execute = mock.Mock(return_value=('', ''))
|
||||
|
@ -672,7 +382,41 @@ class GaneshaNFSHelperTestCase(test.TestCase):
|
|||
glusterfs.ganesha_utils.SSHExecutor.assert_called_once_with(
|
||||
'fakeip', 22, None, 'root', password=None, privatekey=None)
|
||||
glusterfs.ganesha.GaneshaNASHelper.__init__.assert_has_calls(
|
||||
[mock.call(ssh_execute, self.fake_conf)])
|
||||
[mock.call(ssh_execute, self.fake_conf,
|
||||
tag='GLUSTER-Ganesha-fakeip')])
|
||||
|
||||
def test_init_helper(self):
|
||||
ganeshelper = mock.Mock()
|
||||
exptemp = mock.Mock()
|
||||
|
||||
def set_attributes(*a, **kw):
|
||||
self._helper.ganesha = ganeshelper
|
||||
self._helper.export_template = exptemp
|
||||
|
||||
self.mock_object(ganesha.GaneshaNASHelper, 'init_helper',
|
||||
mock.Mock(side_effect=set_attributes))
|
||||
self.assertEqual({}, glusterfs.GaneshaNFSHelper.shared_data)
|
||||
|
||||
self._helper.init_helper()
|
||||
|
||||
ganesha.GaneshaNASHelper.init_helper.assert_called_once_with()
|
||||
self.assertEqual(ganeshelper, self._helper.ganesha)
|
||||
self.assertEqual(exptemp, self._helper.export_template)
|
||||
self.assertEqual({
|
||||
'GLUSTER-Ganesha-localhost': {
|
||||
'ganesha': ganeshelper,
|
||||
'export_template': exptemp}},
|
||||
glusterfs.GaneshaNFSHelper.shared_data)
|
||||
|
||||
other_helper = glusterfs.GaneshaNFSHelper(
|
||||
self._execute, self.fake_conf,
|
||||
gluster_manager=self.gluster_manager)
|
||||
other_helper.tag = 'GLUSTER-Ganesha-localhost'
|
||||
|
||||
other_helper.init_helper()
|
||||
|
||||
self.assertEqual(ganeshelper, other_helper.ganesha)
|
||||
self.assertEqual(exptemp, other_helper.export_template)
|
||||
|
||||
def test_default_config_hook(self):
|
||||
fake_conf_dict = {'key': 'value1'}
|
||||
|
@ -692,7 +436,9 @@ class GaneshaNFSHelperTestCase(test.TestCase):
|
|||
mock.Mock(return_value=fake_conf_dict))
|
||||
self.mock_object(glusterfs.ganesha_utils, 'patch',
|
||||
mock.Mock(side_effect=fake_patch_run))
|
||||
|
||||
ret = self._helper._default_config_hook()
|
||||
|
||||
glusterfs.ganesha.GaneshaNASHelper._default_config_hook.\
|
||||
assert_called_once_with()
|
||||
glusterfs.ganesha_utils.path_from.assert_called_once_with(
|
||||
|
@ -704,10 +450,13 @@ class GaneshaNFSHelperTestCase(test.TestCase):
|
|||
self.assertEqual(fake_conf_dict, ret)
|
||||
|
||||
def test_fsal_hook(self):
|
||||
self._helper.gluster_manager.path = '/fakename'
|
||||
output = {
|
||||
'Hostname': '127.0.0.1',
|
||||
'Volume': 'testvol',
|
||||
'Volpath': '/fakename'
|
||||
}
|
||||
|
||||
ret = self._helper._fsal_hook('/fakepath', self.share, self.access)
|
||||
|
||||
self.assertEqual(output, ret)
|
||||
|
|
Loading…
Reference in New Issue