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:
Csaba Henk 2015-08-20 16:50:01 +02:00 committed by Ben Swartzlander
parent bf3c40e439
commit b6675d96f1
5 changed files with 803 additions and 582 deletions

View File

@ -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,

View File

@ -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}

View File

@ -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

View File

@ -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)

View File

@ -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)