Merge "Add LVM driver"
This commit is contained in:
commit
89df517977
contrib/ci
devstack
doc/source/devref
etc/manila/rootwrap.d
manila
share/drivers
tests
@ -61,13 +61,11 @@ iniset $TEMPEST_CONFIG share share_creation_retry_number 2
|
||||
SUPPRESS_ERRORS=${SUPPRESS_ERRORS_IN_CLEANUP:-True}
|
||||
iniset $TEMPEST_CONFIG share suppress_errors_in_cleanup $SUPPRESS_ERRORS
|
||||
|
||||
# Enable consistency group tests
|
||||
RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-True}
|
||||
iniset $TEMPEST_CONFIG share run_consistency_group_tests $RUN_MANILA_CG_TESTS
|
||||
USERNAME_FOR_USER_RULES=${USERNAME_FOR_USER_RULES:-"manila"}
|
||||
PASSWORD_FOR_SAMBA_USER=${PASSWORD_FOR_SAMBA_USER:-$USERNAME_FOR_USER_RULES}
|
||||
|
||||
# Enable manage/unmanage tests
|
||||
RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-True}
|
||||
RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True}
|
||||
iniset $TEMPEST_CONFIG share run_manage_unmanage_tests $RUN_MANILA_MANAGE_TESTS
|
||||
|
||||
MANILA_CONF=${MANILA_CONF:-/etc/manila/manila.conf}
|
||||
|
||||
@ -141,6 +139,32 @@ elif [[ "$DRIVER" == "generic" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$DRIVER" == "lvm" ]]; then
|
||||
MANILA_TEMPEST_CONCURRENCY=8
|
||||
RUN_MANILA_CG_TESTS=False
|
||||
RUN_MANILA_MANAGE_TESTS=False
|
||||
iniset $TEMPEST_CONFIG share run_shrink_tests False
|
||||
iniset $TEMPEST_CONFIG share run_migration_tests False
|
||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||
if ! grep $USERNAME_FOR_USER_RULES "/etc/passwd"; then
|
||||
sudo useradd $USERNAME_FOR_USER_RULES
|
||||
fi
|
||||
(echo $PASSWORD_FOR_SAMBA_USER; echo $PASSWORD_FOR_SAMBA_USER) | sudo smbpasswd -s -a $USERNAME_FOR_USER_RULES
|
||||
sudo smbpasswd -e $USERNAME_FOR_USER_RULES
|
||||
samba_daemon_name=smbd
|
||||
if is_fedora; then
|
||||
samba_daemon_name=smb
|
||||
fi
|
||||
sudo service $samba_daemon_name restart
|
||||
fi
|
||||
|
||||
# Enable consistency group tests
|
||||
iniset $TEMPEST_CONFIG share run_consistency_group_tests $RUN_MANILA_CG_TESTS
|
||||
|
||||
# Enable manage/unmanage tests
|
||||
iniset $TEMPEST_CONFIG share run_manage_unmanage_tests $RUN_MANILA_MANAGE_TESTS
|
||||
|
||||
# Also, we should wait until service VM is available
|
||||
# before running Tempest tests using Generic driver in DHSS=False mode.
|
||||
source $BASE/new/manila/contrib/ci/common.sh
|
||||
|
@ -57,6 +57,11 @@ else
|
||||
echo "MANILA_MULTI_BACKEND=False" >> $localrc_path
|
||||
fi
|
||||
|
||||
if [[ "$DRIVER" == "lvm" ]]; then
|
||||
echo "SHARE_DRIVER=manila.share.drivers.lvm.LVMShareDriver" >> $localrc_path
|
||||
echo "SHARE_BACKING_FILE_SIZE=32000M" >> $localrc_path
|
||||
fi
|
||||
|
||||
# Enabling isolated metadata in Neutron is required because
|
||||
# Tempest creates isolated networks and created vm's in scenario tests don't
|
||||
# have access to Nova Metadata service. This leads to unavailability of
|
||||
|
@ -8,12 +8,42 @@ set -o xtrace
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
function _clean_share_group {
|
||||
local vg=$1
|
||||
local vg_prefix=$2
|
||||
# Clean out existing shares
|
||||
for lv in `sudo lvs --noheadings -o lv_name $vg`; do
|
||||
# vg_prefix prefixes the LVs we want
|
||||
if [[ "${lv#$vg_prefix}" != "$lv" ]]; then
|
||||
sudo umount -f $MANILA_MNT_DIR/$lv
|
||||
sudo lvremove -f $vg/$lv
|
||||
sudo rm -rf $MANILA_MNT_DIR/$lv
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function _clean_manila_lvm_backing_file {
|
||||
local vg=$1
|
||||
|
||||
# if there is no logical volume left, it's safe to attempt a cleanup
|
||||
# of the backing file
|
||||
if [ -z "`sudo lvs --noheadings -o lv_name $vg`" ]; then
|
||||
# if the backing physical device is a loop device, it was probably setup by devstack
|
||||
VG_DEV=$(sudo losetup -j $DATA_DIR/${vg}-backing-file | awk -F':' '/backing-file/ { print $1
|
||||
}')
|
||||
if [[ -n "$VG_DEV" ]]; then
|
||||
sudo losetup -d $VG_DEV
|
||||
rm -f $DATA_DIR/${vg}-backing-file
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# cleanup_manila - Remove residual data files, anything left over from previous
|
||||
# runs that a clean run would need to clean up
|
||||
function cleanup_manila {
|
||||
# This is placeholder.
|
||||
# All stuff, that are created by Generic driver will be cleaned up by other services.
|
||||
:
|
||||
# All stuff, that are created by share drivers will be cleaned up by other services.
|
||||
_clean_share_group $SHARE_GROUP $SHARE_NAME_PREFIX
|
||||
_clean_manila_lvm_backing_file $SHARE_GROUP
|
||||
}
|
||||
|
||||
# configure_default_backends - configures default Manila backends with generic driver.
|
||||
@ -151,6 +181,8 @@ function configure_manila {
|
||||
|
||||
iniset $MANILA_CONF DEFAULT wsgi_keep_alive False
|
||||
|
||||
iniset $MANILA_CONF DEFAULT lvm_share_volume_group $SHARE_GROUP
|
||||
|
||||
# Note: set up config group does not mean that this backend will be enabled.
|
||||
# To enable it, specify its name explicitly using "enabled_share_backends" opt.
|
||||
configure_default_backends
|
||||
@ -363,6 +395,32 @@ function init_manila {
|
||||
$MANILA_BIN_DIR/manila-manage db version
|
||||
fi
|
||||
|
||||
if [ "$SHARE_DRIVER" == "manila.share.drivers.lvm.LVMShareDriver" ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
# Configure a default volume group called '`lvm-shares`' for the share
|
||||
# service if it does not yet exist. If you don't wish to use a file backed
|
||||
# volume group, create your own volume group called ``stack-volumes`` before
|
||||
# invoking ``stack.sh``.
|
||||
#
|
||||
# By default, the backing file is 8G in size, and is stored in ``/opt/stack/data``.
|
||||
|
||||
if ! sudo vgs $SHARE_GROUP; then
|
||||
if [ "$CONFIGURE_BACKING_FILE" = "True" ]; then
|
||||
SHARE_BACKING_FILE=${SHARE_BACKING_FILE:-$DATA_DIR/${SHARE_GROUP}-backing-file}
|
||||
# Only create if the file doesn't already exists
|
||||
[[ -f $SHARE_BACKING_FILE ]] || truncate -s $SHARE_BACKING_FILE_SIZE $SHARE_BACKING_FILE
|
||||
DEV=`sudo losetup -f --show $SHARE_BACKING_FILE`
|
||||
else
|
||||
DEV=$SHARE_BACKING_FILE
|
||||
fi
|
||||
# Only create if the loopback device doesn't contain $SHARE_GROUP
|
||||
if ! sudo vgs $SHARE_GROUP; then sudo vgcreate $SHARE_GROUP $DEV; fi
|
||||
fi
|
||||
|
||||
mkdir -p $MANILA_STATE_PATH/shares
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create cache dir
|
||||
sudo mkdir -p $MANILA_AUTH_CACHE_DIR
|
||||
sudo chown $STACK_USER $MANILA_AUTH_CACHE_DIR
|
||||
@ -373,12 +431,49 @@ function init_manila {
|
||||
function install_manila {
|
||||
git_clone $MANILACLIENT_REPO $MANILACLIENT_DIR $MANILACLIENT_BRANCH
|
||||
|
||||
if [ "$SHARE_DRIVER" == "manila.share.drivers.lvm.LVMShareDriver" ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
if is_ubuntu; then
|
||||
sudo apt-get install -y nfs-kernel-server nfs-common samba
|
||||
elif is_fedora; then
|
||||
sudo yum install -y nfs-utils nfs-utils-lib samba
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# install manila-ui if horizon is enabled
|
||||
if is_service_enabled horizon && [ "$MANILA_UI_ENABLED" = "True" ]; then
|
||||
git_clone $MANILA_UI_REPO $MANILA_UI_DIR $MANILA_UI_BRANCH
|
||||
fi
|
||||
}
|
||||
|
||||
#configure_samba - Configure node as Samba server
|
||||
function configure_samba {
|
||||
if [ "$SHARE_DRIVER" == "manila.share.drivers.lvm.LVMShareDriver" ]; then
|
||||
samba_daemon_name=smbd
|
||||
if is_service_enabled m-shr; then
|
||||
if is_fedora; then
|
||||
samba_daemon_name=smb
|
||||
fi
|
||||
sudo service $samba_daemon_name restart || echo "Couldn't restart '$samba_daemon_name' service"
|
||||
fi
|
||||
|
||||
sudo cp /usr/share/samba/smb.conf $SMB_CONF
|
||||
sudo chown stack -R /etc/samba
|
||||
iniset $SMB_CONF global include registry
|
||||
iniset $SMB_CONF global security user
|
||||
if [ ! -d "$SMB_PRIVATE_DIR" ]; then
|
||||
sudo mkdir $SMB_PRIVATE_DIR
|
||||
sudo touch $SMB_PRIVATE_DIR/secrets.tdb
|
||||
fi
|
||||
|
||||
for backend_name in ${MANILA_ENABLED_BACKENDS//,/ }; do
|
||||
iniset $MANILA_CONF $backend_name driver_handles_share_servers False
|
||||
iniset $MANILA_CONF $backend_name lvm_share_export_ip $MANILA_SERVICE_HOST
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# start_manila - Start running processes, including screen
|
||||
function start_manila {
|
||||
# restart apache to reload running horizon if manila-ui is enabled
|
||||
@ -468,6 +563,9 @@ elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
||||
backends for which handlng of share servers is disabled."
|
||||
create_service_share_servers
|
||||
|
||||
echo_summary "Configure Samba server"
|
||||
configure_samba
|
||||
|
||||
echo_summary "Starting Manila"
|
||||
start_manila
|
||||
|
||||
|
@ -127,6 +127,13 @@ MANILA_SHARE_BACKEND1_NAME=${MANILA_SHARE_BACKEND1_NAME:-GENERIC1} # deprecated
|
||||
MANILA_BACKEND2_CONFIG_GROUP_NAME=${MANILA_BACKEND2_CONFIG_GROUP_NAME:-generic2} # deprecated
|
||||
MANILA_SHARE_BACKEND2_NAME=${MANILA_SHARE_BACKEND2_NAME:-GENERIC2} # deprecated
|
||||
|
||||
# Options for configuration of LVM share driver
|
||||
SHARE_BACKING_FILE_SIZE=${SHARE_BACKING_FILE_SIZE:-8400M}
|
||||
SHARE_GROUP=${SHARE_GROUP:-lvm-shares}
|
||||
MANILA_MNT_DIR=${MANILA_MNT_DIR:=$MANILA_STATE_PATH/mnt}
|
||||
SMB_CONF=${SMB_CONF:-/etc/samba/smb.conf}
|
||||
SMB_PRIVATE_DIR=${SMB_PRIVATE_DIR:-/var/lib/samba/private}
|
||||
CONFIGURE_BACKING_FILE=${CONFIGURE_BACKING_FILE:-"True"}
|
||||
|
||||
# Enable manila services
|
||||
# ----------------------
|
||||
|
@ -55,6 +55,8 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
||||
| IBM GPFS | DHSS = False(K) | \- | L | \- | K | K |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
||||
| LVM | DHSS = False (M) | \- | M | \- | M | M |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
||||
| Quobyte | DHSS = False (K) | \- | M | M | \- | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
||||
| Windows SMB | DHSS = True (L) & False (L) | L | L | L | L | L |
|
||||
@ -94,6 +96,8 @@ Mapping of share drivers and share access rules support
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||
| Huawei | NFS (K) |NFS (M),CIFS (K)| \- | NFS (K) |NFS (M),CIFS (K)| \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||
| LVM | NFS (M) | CIFS (M) | \- | NFS (M) | CIFS (M) | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||
| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||
| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- |
|
||||
@ -129,6 +133,8 @@ Mapping of share drivers and security services support
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Huawei | M | M | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| LVM | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Quobyte | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Windows SMB | L | \- | \- |
|
||||
|
@ -7,6 +7,49 @@ chown: CommandFilter, chown, root
|
||||
# manila/utils.py : 'cat', '%s'
|
||||
cat: CommandFilter, cat, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'mkfs.ext4', '/dev/mapper/%s'
|
||||
mkfs.ext4: CommandFilter, /sbin/mkfs.ext4, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'mkfs.ext3', '/dev/mapper/%s'
|
||||
mkfs.ext3: CommandFilter, /sbin/mkfs.ext3, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'smbd', '-s', '%s', '-D'
|
||||
smbd: CommandFilter, /usr/sbin/smbd, root
|
||||
smb: CommandFilter, /usr/sbin/smb, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'rmdir', '%s'
|
||||
rmdir: CommandFilter, /bin/rmdir, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'dd' 'count=0', 'if=%s' % srcstr, 'of=%s'
|
||||
dd: CommandFilter, dd, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'fsck', '-pf', %s
|
||||
fsck: CommandFilter, fsck, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'resize2fs', %s
|
||||
resize2fs: CommandFilter, resize2fs, root
|
||||
|
||||
# manila/share/drivers/helpers.py: 'smbcontrol', 'all', 'close-share', '%s'
|
||||
smbcontrol: CommandFilter, /usr/bin/smbcontrol, root
|
||||
|
||||
# manila/share/drivers/helpers.py: 'net', 'conf', 'addshare', '%s', '%s', 'writeable=y', 'guest_ok=y
|
||||
# manila/share/drivers/helpers.py: 'net', 'conf', 'delshare', '%s'
|
||||
# manila/share/drivers/helpers.py: 'net', 'conf', 'setparm', '%s', '%s', '%s'
|
||||
# manila/share/drivers/helpers.py: 'net', 'conf', 'getparm', '%s', 'hosts allow'
|
||||
net: CommandFilter, /usr/bin/net, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'lvremove', '-f', "%s/%s
|
||||
lvremove: CommandFilter, lvremove, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'lvextend', '-L', '%sG''-n', %s
|
||||
lvextend: CommandFilter, lvextend, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'lvcreate', '-L', %s, '-n', %s
|
||||
lvcreate: CommandFilter, lvcreate, root
|
||||
|
||||
# manila/share/drivers/lvm.py: 'vgs', %s, '--rows'
|
||||
vgs: CommandFilter, vgs, root
|
||||
|
||||
# manila/share/drivers/glusterfs.py: 'mkdir', '%s'
|
||||
# manila/share/drivers/ganesha/manager.py: 'mkdir', '-p', '%s'
|
||||
mkdir: CommandFilter, mkdir, root
|
||||
|
@ -16,13 +16,11 @@
|
||||
"""Generic Driver for shares."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import units
|
||||
import retrying
|
||||
@ -71,8 +69,8 @@ share_opts = [
|
||||
help="Path to SMB config in service instance."),
|
||||
cfg.ListOpt('share_helpers',
|
||||
default=[
|
||||
'CIFS=manila.share.drivers.generic.CIFSHelper',
|
||||
'NFS=manila.share.drivers.generic.NFSHelper',
|
||||
'CIFS=manila.share.drivers.helpers.CIFSHelperIPAccess',
|
||||
'NFS=manila.share.drivers.helpers.NFSHelper',
|
||||
],
|
||||
help='Specify list of share export helpers.'),
|
||||
cfg.StrOpt('share_volume_fstype',
|
||||
@ -1140,331 +1138,3 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
|
||||
clone_list.append(clone_info)
|
||||
|
||||
return clone_list
|
||||
|
||||
|
||||
class NASHelperBase(object):
|
||||
"""Interface to work with share."""
|
||||
|
||||
def __init__(self, execute, ssh_execute, config_object):
|
||||
self.configuration = config_object
|
||||
self._execute = execute
|
||||
self._ssh_exec = ssh_execute
|
||||
|
||||
def init_helper(self, server):
|
||||
pass
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create new export, delete old one if exists."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove export."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Allow access to the host."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Deny access to the host."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def _verify_server_has_public_address(server):
|
||||
if 'public_address' not in server:
|
||||
raise exception.ManilaException(
|
||||
_("Can not get 'public_address' for generation of export."))
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
"""Returns list of exports based on server info."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
"""Returns share path by its export location."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
"""Disables access to share to perform maintenance operations."""
|
||||
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
"""Enables access to share after maintenance operations were done."""
|
||||
|
||||
def _get_maintenance_file_path(self, share_name):
|
||||
return os.path.join(self.configuration.share_mount_path,
|
||||
"%s.maintenance" % share_name)
|
||||
|
||||
|
||||
def nfs_synchronized(f):
|
||||
|
||||
def wrapped_func(self, *args, **kwargs):
|
||||
key = "nfs-%s" % args[0]["instance_id"]
|
||||
|
||||
@utils.synchronized(key)
|
||||
def source_func(self, *args, **kwargs):
|
||||
return f(self, *args, **kwargs)
|
||||
|
||||
return source_func(self, *args, **kwargs)
|
||||
|
||||
return wrapped_func
|
||||
|
||||
|
||||
class NFSHelper(NASHelperBase):
|
||||
"""Interface to work with share."""
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create new export, delete old one if exists."""
|
||||
return ':'.join([server['public_address'],
|
||||
os.path.join(
|
||||
self.configuration.share_mount_path, share_name)])
|
||||
|
||||
def init_helper(self, server):
|
||||
try:
|
||||
self._ssh_exec(server, ['sudo', 'exportfs'])
|
||||
except exception.ProcessExecutionError as e:
|
||||
if 'command not found' in e.stderr:
|
||||
raise exception.ManilaException(
|
||||
_('NFS server is not installed on %s')
|
||||
% server['instance_id'])
|
||||
LOG.error(e.stderr)
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove export."""
|
||||
pass
|
||||
|
||||
@nfs_synchronized
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Allow access to the host."""
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
if access_type != 'ip':
|
||||
reason = 'only ip access type allowed'
|
||||
raise exception.InvalidShareAccess(reason)
|
||||
|
||||
# check if presents in export
|
||||
out, _ = self._ssh_exec(server, ['sudo', 'exportfs'])
|
||||
out = re.search(
|
||||
re.escape(local_path) + '[\s\n]*' + re.escape(access_to), out)
|
||||
if out is not None:
|
||||
raise exception.ShareAccessExists(access_type=access_type,
|
||||
access=access_to)
|
||||
self._ssh_exec(
|
||||
server,
|
||||
['sudo', 'exportfs', '-o', '%s,no_subtree_check' % access_level,
|
||||
':'.join([access_to, local_path])])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
@nfs_synchronized
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Deny access to the host."""
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
self._ssh_exec(server, ['sudo', 'exportfs', '-u',
|
||||
':'.join([access['access_to'], local_path])])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
def _sync_nfs_temp_and_perm_files(self, server):
|
||||
"""Sync changes of exports with permanent NFS config file.
|
||||
|
||||
This is required to ensure, that after share server reboot, exports
|
||||
still exist.
|
||||
"""
|
||||
sync_cmd = [
|
||||
'sudo', 'cp ', const.NFS_EXPORTS_FILE_TEMP, const.NFS_EXPORTS_FILE,
|
||||
'&&',
|
||||
'sudo', 'exportfs', '-a',
|
||||
]
|
||||
self._ssh_exec(server, sync_cmd)
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
self._verify_server_has_public_address(server)
|
||||
path = old_export_location.split(':')[-1]
|
||||
return [':'.join([server['public_address'], path])]
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
return export_location.split(':')[-1]
|
||||
|
||||
@nfs_synchronized
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
backup_exports = [
|
||||
'cat', const.NFS_EXPORTS_FILE,
|
||||
'| grep', share_name,
|
||||
'| sudo tee', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, backup_exports)
|
||||
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
self._ssh_exec(server, ['sudo', 'exportfs', '-u', local_path])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
@nfs_synchronized
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
restore_exports = [
|
||||
'cat', maintenance_file,
|
||||
'| sudo tee -a', const.NFS_EXPORTS_FILE,
|
||||
'&& sudo exportfs -r',
|
||||
'&& sudo rm -f', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, restore_exports)
|
||||
|
||||
|
||||
class CIFSHelper(NASHelperBase):
|
||||
"""Manage shares in samba server by net conf tool.
|
||||
|
||||
Class provides functionality to operate with CIFS shares.
|
||||
Samba server should be configured to use registry as configuration
|
||||
backend to allow dynamically share managements.
|
||||
"""
|
||||
|
||||
def init_helper(self, server):
|
||||
# This is smoke check that we have required dependency
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'list'])
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create share at samba server."""
|
||||
share_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
create_cmd = [
|
||||
'sudo', 'net', 'conf', 'addshare', share_name, share_path,
|
||||
'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
try:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'showshare', share_name, ])
|
||||
except exception.ProcessExecutionError as parent_e:
|
||||
# Share does not exist, create it
|
||||
try:
|
||||
self._ssh_exec(server, create_cmd)
|
||||
except Exception:
|
||||
# If we get here, then it will be useful
|
||||
# to log parent exception too.
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(parent_e)
|
||||
else:
|
||||
# Share exists
|
||||
if recreate:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'delshare', share_name, ])
|
||||
self._ssh_exec(server, create_cmd)
|
||||
else:
|
||||
msg = _('Share section %s already defined.') % share_name
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
parameters = {
|
||||
'browseable': 'yes',
|
||||
'\"create mask\"': '0755',
|
||||
'\"hosts deny\"': '0.0.0.0/0', # deny all by default
|
||||
'\"hosts allow\"': '127.0.0.1',
|
||||
'\"read only\"': 'no',
|
||||
}
|
||||
set_of_commands = [':', ] # : is just placeholder
|
||||
for param, value in parameters.items():
|
||||
# These are combined in one list to run in one process
|
||||
# instead of big chain of one action calls.
|
||||
set_of_commands.extend(['&&', 'sudo', 'net', 'conf', 'setparm',
|
||||
share_name, param, value])
|
||||
self._ssh_exec(server, set_of_commands)
|
||||
return '\\\\%s\\%s' % (server['public_address'], share_name)
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove share definition from samba server."""
|
||||
try:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'delshare', share_name])
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.warning(_LW("Caught error trying delete share: %(error)s, try"
|
||||
"ing delete it forcibly."), {'error': e.stderr})
|
||||
self._ssh_exec(server, ['sudo', 'smbcontrol', 'all', 'close-share',
|
||||
share_name])
|
||||
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Add access for share."""
|
||||
if access_type != 'ip':
|
||||
reason = _('Only ip access type allowed.')
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
if access_level != const.ACCESS_LEVEL_RW:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
|
||||
hosts = self._get_allow_hosts(server, share_name)
|
||||
if access_to in hosts:
|
||||
raise exception.ShareAccessExists(
|
||||
access_type=access_type, access=access_to)
|
||||
hosts.append(access_to)
|
||||
self._set_allow_hosts(server, hosts, share_name)
|
||||
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Remove access for share."""
|
||||
access_to, access_level = access['access_to'], access['access_level']
|
||||
if access_level != const.ACCESS_LEVEL_RW:
|
||||
return
|
||||
try:
|
||||
hosts = self._get_allow_hosts(server, share_name)
|
||||
if access_to in hosts:
|
||||
# Access rule can be in error state, if so
|
||||
# it can be absent in rules, hence - skip removal.
|
||||
hosts.remove(access_to)
|
||||
self._set_allow_hosts(server, hosts, share_name)
|
||||
except exception.ProcessExecutionError:
|
||||
if not force:
|
||||
raise
|
||||
|
||||
def _get_allow_hosts(self, server, share_name):
|
||||
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf', 'getparm',
|
||||
share_name, '\"hosts allow\"'])
|
||||
return out.split()
|
||||
|
||||
def _set_allow_hosts(self, server, hosts, share_name):
|
||||
value = "\"" + ' '.join(hosts) + "\""
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
|
||||
'\"hosts allow\"', value])
|
||||
|
||||
@staticmethod
|
||||
def _get_share_group_name_from_export_location(export_location):
|
||||
if '/' in export_location and '\\' in export_location:
|
||||
pass
|
||||
elif export_location.startswith('\\\\'):
|
||||
return export_location.split('\\')[-1]
|
||||
elif export_location.startswith('//'):
|
||||
return export_location.split('/')[-1]
|
||||
|
||||
msg = _("Got incorrect CIFS export location '%s'.") % export_location
|
||||
raise exception.InvalidShare(reason=msg)
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
self._verify_server_has_public_address(server)
|
||||
group_name = self._get_share_group_name_from_export_location(
|
||||
old_export_location)
|
||||
data = dict(ip=server['public_address'], share=group_name)
|
||||
return ['\\\\%(ip)s\\%(share)s' % data]
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
# Get name of group that contains share data on CIFS server
|
||||
group_name = self._get_share_group_name_from_export_location(
|
||||
export_location)
|
||||
|
||||
# Get parameter 'path' from group that belongs to current share
|
||||
(out, __) = self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'getparm', group_name, 'path'])
|
||||
|
||||
# Remove special symbols from response and return path
|
||||
return out.strip()
|
||||
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
allowed_hosts = " ".join(self._get_allow_hosts(server, share_name))
|
||||
|
||||
backup_exports = [
|
||||
'echo', "'%s'" % allowed_hosts, '| sudo tee', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, backup_exports)
|
||||
self._set_allow_hosts(server, [], share_name)
|
||||
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
(exports, __) = self._ssh_exec(server, ['cat', maintenance_file])
|
||||
self._set_allow_hosts(server, exports.split(), share_name)
|
||||
self._ssh_exec(server, ['sudo rm -f', maintenance_file])
|
||||
|
444
manila/share/drivers/helpers.py
Normal file
444
manila/share/drivers/helpers.py
Normal file
@ -0,0 +1,444 @@
|
||||
# Copyright 2015 Mirantis 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 re
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LW
|
||||
from manila import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class NASHelperBase(object):
|
||||
"""Interface to work with share."""
|
||||
|
||||
def __init__(self, execute, ssh_execute, config_object):
|
||||
self.configuration = config_object
|
||||
self._execute = execute
|
||||
self._ssh_exec = ssh_execute
|
||||
|
||||
def init_helper(self, server):
|
||||
pass
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create new export, delete old one if exists."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove export."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def configure_access(self, server, share_name):
|
||||
"""Configure server before allowing access."""
|
||||
pass
|
||||
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Allow access to the host."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Deny access to the host."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def _verify_server_has_public_address(server):
|
||||
if 'public_address' not in server:
|
||||
raise exception.ManilaException(
|
||||
_("Can not get 'public_address' for generation of export."))
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
"""Returns list of exports based on server info."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
"""Returns share path by its export location."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
"""Disables access to share to perform maintenance operations."""
|
||||
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
"""Enables access to share after maintenance operations were done."""
|
||||
|
||||
def _get_maintenance_file_path(self, share_name):
|
||||
return os.path.join(self.configuration.share_mount_path,
|
||||
"%s.maintenance" % share_name)
|
||||
|
||||
|
||||
def nfs_synchronized(f):
|
||||
|
||||
def wrapped_func(self, *args, **kwargs):
|
||||
key = "nfs-%s" % args[0]["instance_id"]
|
||||
|
||||
@utils.synchronized(key)
|
||||
def source_func(self, *args, **kwargs):
|
||||
return f(self, *args, **kwargs)
|
||||
|
||||
return source_func(self, *args, **kwargs)
|
||||
|
||||
return wrapped_func
|
||||
|
||||
|
||||
class NFSHelper(NASHelperBase):
|
||||
"""Interface to work with share."""
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create new export, delete old one if exists."""
|
||||
return ':'.join([server['public_address'],
|
||||
os.path.join(
|
||||
self.configuration.share_mount_path, share_name)])
|
||||
|
||||
def init_helper(self, server):
|
||||
try:
|
||||
self._ssh_exec(server, ['sudo', 'exportfs'])
|
||||
except exception.ProcessExecutionError as e:
|
||||
if 'command not found' in e.stderr:
|
||||
raise exception.ManilaException(
|
||||
_('NFS server is not installed on %s')
|
||||
% server['instance_id'])
|
||||
LOG.error(e.stderr)
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove export."""
|
||||
|
||||
@nfs_synchronized
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Allow access to the host."""
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
if access_type != 'ip':
|
||||
msg = _('only ip access type allowed')
|
||||
raise exception.InvalidShareAccess(reason=msg)
|
||||
|
||||
# check if presents in export
|
||||
out, err = self._ssh_exec(server, ['sudo', 'exportfs'])
|
||||
out = re.search(
|
||||
re.escape(local_path) + '[\s\n]*' + re.escape(access_to), out)
|
||||
if out is not None:
|
||||
raise exception.ShareAccessExists(access_type=access_type,
|
||||
access=access_to)
|
||||
self._ssh_exec(
|
||||
server,
|
||||
['sudo', 'exportfs', '-o', '%s,no_subtree_check' % access_level,
|
||||
':'.join([access_to, local_path])])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
@nfs_synchronized
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Deny access to the host."""
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
self._ssh_exec(server, ['sudo', 'exportfs', '-u',
|
||||
':'.join([access['access_to'], local_path])])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
def _sync_nfs_temp_and_perm_files(self, server):
|
||||
"""Sync changes of exports with permanent NFS config file.
|
||||
|
||||
This is required to ensure, that after share server reboot, exports
|
||||
still exist.
|
||||
"""
|
||||
sync_cmd = [
|
||||
'sudo', 'cp', const.NFS_EXPORTS_FILE_TEMP, const.NFS_EXPORTS_FILE
|
||||
]
|
||||
self._ssh_exec(server, sync_cmd)
|
||||
self._ssh_exec(server, ['sudo', 'exportfs', '-a'])
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
self._verify_server_has_public_address(server)
|
||||
path = old_export_location.split(':')[-1]
|
||||
return [':'.join([server['public_address'], path])]
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
return export_location.split(':')[-1]
|
||||
|
||||
@nfs_synchronized
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
backup_exports = [
|
||||
'cat', const.NFS_EXPORTS_FILE,
|
||||
'| grep', share_name,
|
||||
'| sudo tee', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, backup_exports)
|
||||
|
||||
local_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
self._ssh_exec(server, ['sudo', 'exportfs', '-u', local_path])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
|
||||
@nfs_synchronized
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
restore_exports = [
|
||||
'cat', maintenance_file,
|
||||
'| sudo tee -a', const.NFS_EXPORTS_FILE,
|
||||
'&& sudo exportfs -r',
|
||||
'&& sudo rm -f', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, restore_exports)
|
||||
|
||||
|
||||
class CIFSHelperIPAccess(NASHelperBase):
|
||||
"""Manage shares in samba server by net conf tool.
|
||||
|
||||
Class provides functionality to operate with CIFS shares.
|
||||
Samba server should be configured to use registry as configuration
|
||||
backend to allow dynamically share managements. This class allows
|
||||
to define access to shares by IPs with RW access level.
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
super(CIFSHelperIPAccess, self).__init__(*args)
|
||||
self.export_format = '\\\\%s\\%s'
|
||||
self.parameters = {
|
||||
'browseable': 'yes',
|
||||
'\"create mask\"': '0755',
|
||||
'\"hosts deny\"': '0.0.0.0/0', # deny all by default
|
||||
'\"hosts allow\"': '127.0.0.1',
|
||||
'\"read only\"': 'no',
|
||||
}
|
||||
|
||||
def init_helper(self, server):
|
||||
# This is smoke check that we have required dependency
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'list'])
|
||||
|
||||
def create_export(self, server, share_name, recreate=False):
|
||||
"""Create share at samba server."""
|
||||
share_path = os.path.join(self.configuration.share_mount_path,
|
||||
share_name)
|
||||
create_cmd = [
|
||||
'sudo', 'net', 'conf', 'addshare', share_name, share_path,
|
||||
'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
try:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'showshare', share_name, ])
|
||||
except exception.ProcessExecutionError as parent_e:
|
||||
# Share does not exist, create it
|
||||
try:
|
||||
self._ssh_exec(server, create_cmd)
|
||||
except Exception:
|
||||
# If we get here, then it will be useful
|
||||
# to log parent exception too.
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(parent_e)
|
||||
else:
|
||||
# Share exists
|
||||
if recreate:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'delshare', share_name, ])
|
||||
self._ssh_exec(server, create_cmd)
|
||||
else:
|
||||
msg = _('Share section %s already defined.') % share_name
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
|
||||
for param, value in self.parameters.items():
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm',
|
||||
share_name, param, value])
|
||||
|
||||
return self.export_format % (server['public_address'], share_name)
|
||||
|
||||
def remove_export(self, server, share_name):
|
||||
"""Remove share definition from samba server."""
|
||||
try:
|
||||
self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'delshare', share_name])
|
||||
except exception.ProcessExecutionError as e:
|
||||
LOG.warning(_LW("Caught error trying delete share: %(error)s, try"
|
||||
"ing delete it forcibly."), {'error': e.stderr})
|
||||
self._ssh_exec(server, ['sudo', 'smbcontrol', 'all', 'close-share',
|
||||
share_name])
|
||||
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Add access for share."""
|
||||
if access_type != 'ip':
|
||||
reason = _('Only ip access type allowed.')
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
if access_level != const.ACCESS_LEVEL_RW:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
|
||||
hosts = self._get_allow_hosts(server, share_name)
|
||||
if access_to in hosts:
|
||||
raise exception.ShareAccessExists(
|
||||
access_type=access_type, access=access_to)
|
||||
hosts.append(access_to)
|
||||
self._set_allow_hosts(server, hosts, share_name)
|
||||
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Remove access for share."""
|
||||
access_to, access_level = access['access_to'], access['access_level']
|
||||
if access_level != const.ACCESS_LEVEL_RW:
|
||||
return
|
||||
try:
|
||||
hosts = self._get_allow_hosts(server, share_name)
|
||||
if access_to in hosts:
|
||||
# Access rule can be in error state, if so
|
||||
# it can be absent in rules, hence - skip removal.
|
||||
hosts.remove(access_to)
|
||||
self._set_allow_hosts(server, hosts, share_name)
|
||||
except exception.ProcessExecutionError:
|
||||
if not force:
|
||||
raise
|
||||
|
||||
def _get_allow_hosts(self, server, share_name):
|
||||
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf', 'getparm',
|
||||
share_name, '\"hosts allow\"'])
|
||||
return out.split()
|
||||
|
||||
def _set_allow_hosts(self, server, hosts, share_name):
|
||||
value = "\"" + ' '.join(hosts) + "\""
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
|
||||
'\"hosts allow\"', value])
|
||||
|
||||
@staticmethod
|
||||
def _get_share_group_name_from_export_location(export_location):
|
||||
if '/' in export_location and '\\' in export_location:
|
||||
pass
|
||||
elif export_location.startswith('\\\\'):
|
||||
return export_location.split('\\')[-1]
|
||||
elif export_location.startswith('//'):
|
||||
return export_location.split('/')[-1]
|
||||
|
||||
msg = _("Got incorrect CIFS export location '%s'.") % export_location
|
||||
raise exception.InvalidShare(reason=msg)
|
||||
|
||||
def get_exports_for_share(self, server, old_export_location):
|
||||
self._verify_server_has_public_address(server)
|
||||
group_name = self._get_share_group_name_from_export_location(
|
||||
old_export_location)
|
||||
data = dict(ip=server['public_address'], share=group_name)
|
||||
return ['\\\\%(ip)s\\%(share)s' % data]
|
||||
|
||||
def get_share_path_by_export_location(self, server, export_location):
|
||||
# Get name of group that contains share data on CIFS server
|
||||
group_name = self._get_share_group_name_from_export_location(
|
||||
export_location)
|
||||
|
||||
# Get parameter 'path' from group that belongs to current share
|
||||
(out, __) = self._ssh_exec(
|
||||
server, ['sudo', 'net', 'conf', 'getparm', group_name, 'path'])
|
||||
|
||||
# Remove special symbols from response and return path
|
||||
return out.strip()
|
||||
|
||||
def disable_access_for_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
allowed_hosts = " ".join(self._get_allow_hosts(server, share_name))
|
||||
|
||||
backup_exports = [
|
||||
'echo', "'%s'" % allowed_hosts, '| sudo tee', maintenance_file
|
||||
]
|
||||
self._ssh_exec(server, backup_exports)
|
||||
self._set_allow_hosts(server, [], share_name)
|
||||
|
||||
def restore_access_after_maintenance(self, server, share_name):
|
||||
maintenance_file = self._get_maintenance_file_path(share_name)
|
||||
(exports, __) = self._ssh_exec(server, ['cat', maintenance_file])
|
||||
self._set_allow_hosts(server, exports.split(), share_name)
|
||||
self._ssh_exec(server, ['sudo rm -f', maintenance_file])
|
||||
|
||||
|
||||
class CIFSHelperUserAccess(CIFSHelperIPAccess):
|
||||
"""Manage shares in samba server by net conf tool.
|
||||
|
||||
Class provides functionality to operate with CIFS shares.
|
||||
Samba server should be configured to use registry as configuration
|
||||
backend to allow dynamically share managements. This class allows
|
||||
to define access to shares by usernames with either RW or RO access levels.
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
super(CIFSHelperUserAccess, self).__init__(*args)
|
||||
self.export_format = '//%s/%s'
|
||||
self.parameters = {
|
||||
'browseable': 'yes',
|
||||
'create mask': '0755',
|
||||
'hosts allow': '0.0.0.0/0',
|
||||
'read only': 'no',
|
||||
}
|
||||
|
||||
def allow_access(self, server, share_name, access_type, access_level,
|
||||
access_to):
|
||||
"""Add to allow hosts additional access rule."""
|
||||
if access_type != 'user':
|
||||
reason = _('Only user access type allowed.')
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
all_users = self._get_valid_users(server, share_name)
|
||||
|
||||
if access_to in all_users:
|
||||
raise exception.ShareAccessExists(
|
||||
access_type=access_type, access=access_to)
|
||||
|
||||
user_list = self._get_valid_users(server, share_name, access_level)
|
||||
user_list.append(access_to)
|
||||
self._set_valid_users(server, user_list, share_name, access_level)
|
||||
|
||||
def deny_access(self, server, share_name, access, force=False):
|
||||
"""Remove from allow hosts permit rule."""
|
||||
access_to, access_level = access['access_to'], access['access_level']
|
||||
users = self._get_valid_users(server, share_name, access_level,
|
||||
force=force)
|
||||
if access_to in users:
|
||||
users.remove(access_to)
|
||||
self._set_valid_users(server, users, share_name, access_level)
|
||||
|
||||
def _get_valid_users(self, server, share_name, access_level=None,
|
||||
force=True):
|
||||
if not access_level:
|
||||
all_users_list = []
|
||||
for param in ['valid users', 'read list']:
|
||||
out = ""
|
||||
try:
|
||||
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf',
|
||||
'getparm', share_name,
|
||||
param])
|
||||
out = out.replace("\"", "")
|
||||
except exception.ProcessExecutionError:
|
||||
if not force:
|
||||
raise
|
||||
all_users_list += out.split()
|
||||
return all_users_list
|
||||
|
||||
param = self._get_conf_param(access_level)
|
||||
try:
|
||||
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf',
|
||||
'getparm', share_name, param])
|
||||
out = out.replace("\"", "")
|
||||
return out.split()
|
||||
except exception.ProcessExecutionError:
|
||||
if not force:
|
||||
raise
|
||||
return []
|
||||
|
||||
def _get_conf_param(self, access_level):
|
||||
if access_level == const.ACCESS_LEVEL_RW:
|
||||
return 'valid users'
|
||||
if access_level == const.ACCESS_LEVEL_RO:
|
||||
return 'read list'
|
||||
|
||||
def _set_valid_users(self, server, users, share_name, access_level):
|
||||
value = "\"" + ' '.join(users) + "\""
|
||||
param = self._get_conf_param(access_level)
|
||||
self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
|
||||
param, value])
|
332
manila/share/drivers/lvm.py
Normal file
332
manila/share/drivers/lvm.py
Normal file
@ -0,0 +1,332 @@
|
||||
# Copyright 2012 NetApp
|
||||
# Copyright 2016 Mirantis 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.
|
||||
"""
|
||||
LVM Driver for shares.
|
||||
|
||||
"""
|
||||
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LE
|
||||
from manila.i18n import _LI
|
||||
from manila.i18n import _LW
|
||||
from manila.share import driver
|
||||
from manila.share.drivers import generic
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
share_opts = [
|
||||
cfg.StrOpt('lvm_share_export_root',
|
||||
default='$state_path/mnt',
|
||||
help='Base folder where exported shares are located.'),
|
||||
cfg.StrOpt('lvm_share_export_ip',
|
||||
help='IP to be added to export string.'),
|
||||
cfg.IntOpt('lvm_share_mirrors',
|
||||
default=0,
|
||||
help='If set, create LVMs with multiple mirrors. Note that '
|
||||
'this requires lvm_mirrors + 2 PVs with available space.'),
|
||||
cfg.StrOpt('lvm_share_volume_group',
|
||||
default='lvm-shares',
|
||||
help='Name for the VG that will contain exported shares.'),
|
||||
cfg.ListOpt('lvm_share_helpers',
|
||||
default=[
|
||||
'CIFS=manila.share.drivers.helpers.CIFSHelperUserAccess',
|
||||
'NFS=manila.share.drivers.helpers.NFSHelper',
|
||||
],
|
||||
help='Specify list of share export helpers.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(share_opts)
|
||||
CONF.register_opts(generic.share_opts)
|
||||
|
||||
|
||||
class LVMMixin(driver.ExecuteMixin):
|
||||
def check_for_setup_error(self):
|
||||
"""Returns an error if prerequisites aren't met."""
|
||||
out, err = self._execute('sudo', 'vgs', '--noheadings', '-o', 'name')
|
||||
volume_groups = out.split()
|
||||
if self.configuration.lvm_share_volume_group not in volume_groups:
|
||||
msg = (_("share volume group %s doesn't exist")
|
||||
% self.configuration.lvm_share_volume_group)
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
if not self.configuration.lvm_share_export_ip:
|
||||
msg = (_("share_export_ip isn't specified"))
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
def _allocate_container(self, share):
|
||||
sizestr = '%sG' % share['size']
|
||||
cmd = ['lvcreate', '-L', sizestr, '-n', share['name'],
|
||||
self.configuration.lvm_share_volume_group]
|
||||
if self.configuration.lvm_share_mirrors:
|
||||
cmd += ['-m', self.configuration.lvm_share_mirrors, '--nosync']
|
||||
terras = int(sizestr[:-1]) / 1024.0
|
||||
if terras >= 1.5:
|
||||
rsize = int(2 ** math.ceil(math.log(terras) / math.log(2)))
|
||||
# NOTE(vish): Next power of two for region size. See:
|
||||
# http://red.ht/U2BPOD
|
||||
cmd += ['-R', six.text_type(rsize)]
|
||||
|
||||
self._try_execute(*cmd, run_as_root=True)
|
||||
device_name = self._get_local_path(share)
|
||||
self._execute('mkfs.%s' % self.configuration.share_volume_fstype,
|
||||
device_name, run_as_root=True)
|
||||
|
||||
def _extend_container(self, share, device_name, size):
|
||||
cmd = ['lvextend', '-L', '%sG' % size, '-n', device_name]
|
||||
self._try_execute(*cmd, run_as_root=True)
|
||||
|
||||
def _deallocate_container(self, share_name):
|
||||
"""Deletes a logical volume for share."""
|
||||
try:
|
||||
self._try_execute('lvremove', '-f', "%s/%s" %
|
||||
(self.configuration.lvm_share_volume_group,
|
||||
share_name), run_as_root=True)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
if "not found" not in exc.stderr:
|
||||
LOG.exception(_LE("Error deleting volume"))
|
||||
raise
|
||||
LOG.warning(_LW("Volume not found: %s") % exc.stderr)
|
||||
|
||||
def create_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Creates a snapshot."""
|
||||
orig_lv_name = "%s/%s" % (self.configuration.lvm_share_volume_group,
|
||||
snapshot['share_name'])
|
||||
self._try_execute(
|
||||
'lvcreate', '-L', '%sG' % snapshot['share']['size'],
|
||||
'--name', snapshot['name'],
|
||||
'--snapshot', orig_lv_name, run_as_root=True)
|
||||
|
||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Deletes a snapshot."""
|
||||
self._deallocate_container(snapshot['name'])
|
||||
|
||||
|
||||
class LVMShareDriver(LVMMixin, driver.ShareDriver):
|
||||
"""Executes commands relating to Shares."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Do initialization."""
|
||||
super(LVMShareDriver, self).__init__([False], *args, **kwargs)
|
||||
self.configuration.append_config_values(share_opts)
|
||||
self.configuration.append_config_values(generic.share_opts)
|
||||
self.configuration.share_mount_path = (
|
||||
self.configuration.lvm_share_export_root)
|
||||
self._helpers = None
|
||||
self.backend_name = self.configuration.safe_get(
|
||||
'share_backend_name') or 'LVM'
|
||||
# Set of parameters used for compatibility with
|
||||
# Generic driver's helpers.
|
||||
self.share_server = {
|
||||
'public_address': self.configuration.lvm_share_export_ip,
|
||||
'instance_id': self.backend_name,
|
||||
}
|
||||
|
||||
def _ssh_exec_as_root(self, server, command):
|
||||
kwargs = {}
|
||||
if 'sudo' in command:
|
||||
kwargs['run_as_root'] = True
|
||||
command.remove('sudo')
|
||||
return self._execute(*command, **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the volume driver does while starting."""
|
||||
super(LVMShareDriver, self).do_setup(context)
|
||||
self._setup_helpers()
|
||||
|
||||
def _setup_helpers(self):
|
||||
"""Initializes protocol-specific NAS drivers."""
|
||||
self._helpers = {}
|
||||
for helper_str in self.configuration.lvm_share_helpers:
|
||||
share_proto, _, import_str = helper_str.partition('=')
|
||||
helper = importutils.import_class(import_str)
|
||||
# TODO(rushiagr): better way to handle configuration
|
||||
# instead of just passing to the helper
|
||||
self._helpers[share_proto.upper()] = helper(
|
||||
self._execute, self._ssh_exec_as_root, self.configuration)
|
||||
|
||||
def _get_local_path(self, share):
|
||||
# The escape characters are expected by the device mapper.
|
||||
escaped_group = (
|
||||
self.configuration.lvm_share_volume_group.replace('-', '--'))
|
||||
escaped_name = share['name'].replace('-', '--')
|
||||
return "/dev/mapper/%s-%s" % (escaped_group, escaped_name)
|
||||
|
||||
def _update_share_stats(self):
|
||||
"""Retrieve stats info from share volume group."""
|
||||
data = {
|
||||
'share_backend_name': self.backend_name,
|
||||
'storage_protocol': 'NFS_CIFS',
|
||||
'reserved_percentage':
|
||||
self.configuration.reserved_share_percentage,
|
||||
'consistency_group_support': None,
|
||||
'snapshot_support': True,
|
||||
'driver_name': 'LVMShareDriver',
|
||||
'pools': self.get_share_server_pools()
|
||||
}
|
||||
super(LVMShareDriver, self)._update_share_stats(data)
|
||||
|
||||
def get_share_server_pools(self, share_server=None):
|
||||
out, err = self._execute('sudo', 'vgs',
|
||||
self.configuration.lvm_share_volume_group,
|
||||
'--rows')
|
||||
total_size = re.findall("VSize\s[0-9.]+g", out)[0][6:-1]
|
||||
free_size = re.findall("VFree\s[0-9.]+g", out)[0][6:-1]
|
||||
return [{
|
||||
'pool_name': 'lvm-single-pool',
|
||||
'total_capacity_gb': float(total_size),
|
||||
'free_capacity_gb': float(free_size),
|
||||
'reserved_percentage': 0,
|
||||
}, ]
|
||||
|
||||
def create_share(self, context, share, share_server=None):
|
||||
self._allocate_container(share)
|
||||
# create file system
|
||||
device_name = self._get_local_path(share)
|
||||
location = self._get_helper(share).create_export(self.share_server,
|
||||
share['name'])
|
||||
self._mount_device(share, device_name)
|
||||
return location
|
||||
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None):
|
||||
"""Is called to create share from snapshot."""
|
||||
self._allocate_container(share)
|
||||
device_name = self._get_local_path(snapshot)
|
||||
self._copy_volume(device_name, self._get_local_path(share),
|
||||
share['size'])
|
||||
location = self._get_helper(share).create_export(self.share_server,
|
||||
share['name'])
|
||||
self._mount_device(share, device_name)
|
||||
return location
|
||||
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
self._remove_export(context, share)
|
||||
self._delete_share(context, share)
|
||||
self._deallocate_container(share['name'])
|
||||
|
||||
def _remove_export(self, ctx, share):
|
||||
"""Removes an access rules for a share."""
|
||||
mount_path = self._get_mount_path(share)
|
||||
if os.path.exists(mount_path):
|
||||
# umount, may be busy
|
||||
try:
|
||||
self._execute('umount', '-f', mount_path, run_as_root=True)
|
||||
except exception.ProcessExecutionError as exc:
|
||||
if 'device is busy' in six.text_type(exc):
|
||||
raise exception.ShareBusyException(reason=share['name'])
|
||||
else:
|
||||
LOG.info(_LI('Unable to umount: %s'), exc)
|
||||
# remove dir
|
||||
try:
|
||||
os.rmdir(mount_path)
|
||||
except OSError:
|
||||
LOG.warning(_LI('Unable to delete %s'), mount_path)
|
||||
|
||||
def ensure_share(self, ctx, share, share_server=None):
|
||||
"""Ensure that storage are mounted and exported."""
|
||||
device_name = self._get_local_path(share)
|
||||
self._mount_device(share, device_name)
|
||||
self._get_helper(share).create_export(self.share_server, share['name'],
|
||||
recreate=True)
|
||||
|
||||
def _delete_share(self, ctx, share):
|
||||
"""Delete a share."""
|
||||
try:
|
||||
self._get_helper(share).remove_export(self.share_server,
|
||||
share['name'])
|
||||
except exception.ProcessExecutionError:
|
||||
LOG.warning(_LI("Can't remove share %r"), share['id'])
|
||||
except exception.InvalidShare as exc:
|
||||
LOG.warning(exc.message)
|
||||
|
||||
def allow_access(self, ctx, share, access, share_server=None):
|
||||
"""Allow access to the share."""
|
||||
self._get_helper(share).allow_access(self.share_server, share['name'],
|
||||
access['access_type'],
|
||||
access['access_level'],
|
||||
access['access_to'])
|
||||
|
||||
def deny_access(self, ctx, share, access, share_server=None):
|
||||
"""Deny access to the share."""
|
||||
self._get_helper(share).deny_access(self.share_server, share['name'],
|
||||
access)
|
||||
|
||||
def _get_helper(self, share):
|
||||
if share['share_proto'].lower().startswith('nfs'):
|
||||
return self._helpers['NFS']
|
||||
elif share['share_proto'].lower().startswith('cifs'):
|
||||
return self._helpers['CIFS']
|
||||
else:
|
||||
raise exception.InvalidShare(reason='Wrong share protocol')
|
||||
|
||||
def _mount_device(self, share, device_name):
|
||||
"""Mount LVM share and ignore if already mounted."""
|
||||
mount_path = self._get_mount_path(share)
|
||||
self._execute('mkdir', '-p', mount_path)
|
||||
try:
|
||||
self._execute('mount', device_name, mount_path,
|
||||
run_as_root=True, check_exit_code=True)
|
||||
self._execute('chmod', '777', mount_path,
|
||||
run_as_root=True, check_exit_code=True)
|
||||
except exception.ProcessExecutionError:
|
||||
out, err = self._execute('mount', '-l', run_as_root=True)
|
||||
if device_name in out:
|
||||
LOG.warning(_LW("%s is already mounted"), device_name)
|
||||
else:
|
||||
raise
|
||||
return mount_path
|
||||
|
||||
def _unmount_device(self, share):
|
||||
mount_path = self._get_mount_path(share)
|
||||
self._execute('umount', mount_path, run_as_root=True)
|
||||
self._execute('rmdir', mount_path, run_as_root=True)
|
||||
|
||||
def _get_mount_path(self, share):
|
||||
"""Returns path where share is mounted."""
|
||||
return os.path.join(self.configuration.share_mount_path,
|
||||
share['name'])
|
||||
|
||||
def _copy_volume(self, srcstr, deststr, size_in_g):
|
||||
# Use O_DIRECT to avoid thrashing the system buffer cache
|
||||
extra_flags = ['iflag=direct', 'oflag=direct']
|
||||
|
||||
# Check whether O_DIRECT is supported
|
||||
try:
|
||||
self._execute('dd', 'count=0', 'if=%s' % srcstr, 'of=%s' % deststr,
|
||||
*extra_flags, run_as_root=True)
|
||||
except exception.ProcessExecutionError:
|
||||
extra_flags = []
|
||||
|
||||
# Perform the copy
|
||||
self._execute('dd', 'if=%s' % srcstr, 'of=%s' % deststr,
|
||||
'count=%d' % (size_in_g * 1024), 'bs=1M',
|
||||
*extra_flags, run_as_root=True)
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
device_name = self._get_local_path(share)
|
||||
self._extend_container(share, device_name, new_size)
|
||||
self._execute('resize2fs', device_name, run_as_root=True)
|
@ -20,13 +20,13 @@ from oslo_log import log
|
||||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LI
|
||||
from manila.share.drivers import generic
|
||||
from manila.share.drivers import helpers
|
||||
from manila.share.drivers.windows import windows_utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class WindowsSMBHelper(generic.NASHelperBase):
|
||||
class WindowsSMBHelper(helpers.NASHelperBase):
|
||||
_SHARE_ACCESS_RIGHT_MAP = {
|
||||
constants.ACCESS_LEVEL_RW: "Change",
|
||||
constants.ACCESS_LEVEL_RO: "Read"}
|
||||
|
@ -34,6 +34,7 @@ def set_defaults(conf):
|
||||
_POLICY_PATH = os.path.abspath(os.path.join(CONF.state_path,
|
||||
'manila/tests/policy.json'))
|
||||
opts.set_defaults(conf, policy_file=_POLICY_PATH)
|
||||
_safe_set_of_opts(conf, 'share_export_ip', '0.0.0.0')
|
||||
_safe_set_of_opts(conf, 'service_instance_user', 'fake_user')
|
||||
_API_PASTE_PATH = os.path.abspath(os.path.join(CONF.state_path,
|
||||
'etc/manila/api-paste.ini'))
|
||||
|
@ -36,7 +36,6 @@ from manila import test
|
||||
from manila.tests import fake_compute
|
||||
from manila.tests import fake_service_instance
|
||||
from manila.tests import fake_share
|
||||
from manila.tests import fake_utils
|
||||
from manila.tests import fake_volume
|
||||
from manila import utils
|
||||
from manila import volume
|
||||
@ -2062,484 +2061,3 @@ class GenericDriverEnsureServerTestCase(test.TestCase):
|
||||
def test_share_servers_are_handled_server_not_provided(self):
|
||||
self.assertRaises(
|
||||
exception.ManilaException, fake, self.dhss_true, self._context)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class NFSHelperTestCase(test.TestCase):
|
||||
"""Test case for NFS helper of generic driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(NFSHelperTestCase, self).setUp()
|
||||
fake_utils.stub_out_utils_execute(self)
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._ssh_exec = mock.Mock(return_value=('', ''))
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
self._helper = generic.NFSHelper(self._execute, self._ssh_exec,
|
||||
self.fake_conf)
|
||||
ip = '10.254.0.3'
|
||||
self.server = fake_compute.FakeServer(
|
||||
ip=ip, public_address=ip, instance_id='fake_instance_id')
|
||||
self.share_name = 'fake_share_name'
|
||||
|
||||
def test_create_export(self):
|
||||
ret = self._helper.create_export(self.server, self.share_name)
|
||||
expected_location = ':'.join([self.server['public_address'],
|
||||
os.path.join(CONF.share_mount_path,
|
||||
self.share_name)])
|
||||
self.assertEqual(expected_location, ret)
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
|
||||
def test_allow_access(self, data):
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
self._helper.allow_access(
|
||||
self.server, self.share_name, 'ip', data, '10.0.0.2')
|
||||
local_path = os.path.join(CONF.share_mount_path, self.share_name)
|
||||
self._ssh_exec.assert_has_calls([
|
||||
mock.call(self.server, ['sudo', 'exportfs']),
|
||||
mock.call(self.server, ['sudo', 'exportfs', '-o',
|
||||
'%s,no_subtree_check' % data,
|
||||
':'.join(['10.0.0.2', local_path])])
|
||||
])
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server)
|
||||
|
||||
def test_allow_access_no_ip(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess,
|
||||
self._helper.allow_access,
|
||||
self.server, self.share_name,
|
||||
'fake_type', 'fake_level', 'fake_rule')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
|
||||
def test_deny_access(self, data):
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
local_path = os.path.join(CONF.share_mount_path, self.share_name)
|
||||
access = dict(
|
||||
access_to='10.0.0.2', access_type='ip', access_level=data)
|
||||
self._helper.deny_access(self.server, self.share_name, access)
|
||||
export_string = ':'.join(['10.0.0.2', local_path])
|
||||
expected_exec = ['sudo', 'exportfs', '-u', export_string]
|
||||
self._ssh_exec.assert_called_once_with(self.server, expected_exec)
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server)
|
||||
|
||||
def test_sync_nfs_temp_and_perm_files(self):
|
||||
self._helper._sync_nfs_temp_and_perm_files(self.server)
|
||||
self._helper._ssh_exec.assert_called_once_with(self.server, mock.ANY)
|
||||
|
||||
@ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.88:/foo/quuz')
|
||||
def test_get_exports_for_share(self, export_location):
|
||||
server = dict(public_address='1.2.3.4')
|
||||
|
||||
result = self._helper.get_exports_for_share(server, export_location)
|
||||
|
||||
path = export_location.split(':')[-1]
|
||||
self.assertEqual([':'.join([server['public_address'], path])], result)
|
||||
|
||||
@ddt.data(
|
||||
{'public_address_with_suffix': 'foo'},
|
||||
{'with_prefix_public_address': 'bar'},
|
||||
{'with_prefix_public_address_and_with_suffix': 'quuz'}, {})
|
||||
def test_get_exports_for_share_with_error(self, server):
|
||||
export_location = '1.2.3.4:/foo/bar'
|
||||
|
||||
self.assertRaises(
|
||||
exception.ManilaException,
|
||||
self._helper.get_exports_for_share, server, export_location)
|
||||
|
||||
@ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar')
|
||||
def test_get_share_path_by_export_location(self, export_location):
|
||||
result = self._helper.get_share_path_by_export_location(
|
||||
dict(), export_location)
|
||||
|
||||
self.assertEqual('/foo/bar', result)
|
||||
|
||||
def test_disable_access_for_maintenance(self):
|
||||
fake_maintenance_path = "fake.path"
|
||||
share_mount_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path, self.share_name)
|
||||
self.mock_object(self._helper, '_ssh_exec')
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
|
||||
self._helper.disable_access_for_maintenance(
|
||||
self.server, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server,
|
||||
['cat', const.NFS_EXPORTS_FILE,
|
||||
'| grep', self.share_name,
|
||||
'| sudo tee', fake_maintenance_path]
|
||||
)
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server,
|
||||
['sudo', 'exportfs', '-u', share_mount_path]
|
||||
)
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server
|
||||
)
|
||||
|
||||
def test_restore_access_after_maintenance(self):
|
||||
fake_maintenance_path = "fake.path"
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
self.mock_object(self._helper, '_ssh_exec')
|
||||
|
||||
self._helper.restore_access_after_maintenance(
|
||||
self.server, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server,
|
||||
['cat', fake_maintenance_path,
|
||||
'| sudo tee -a', const.NFS_EXPORTS_FILE,
|
||||
'&& sudo exportfs -r', '&& sudo rm -f',
|
||||
fake_maintenance_path]
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CIFSHelperTestCase(test.TestCase):
|
||||
"""Test case for CIFS helper of generic driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(CIFSHelperTestCase, self).setUp()
|
||||
self.server_details = {'instance_id': 'fake',
|
||||
'public_address': '1.2.3.4', }
|
||||
self.share_name = 'fake_share_name'
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._ssh_exec = mock.Mock(return_value=('', ''))
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
self._helper = generic.CIFSHelper(self._execute, self._ssh_exec,
|
||||
self.fake_conf)
|
||||
self.access = dict(
|
||||
access_level=const.ACCESS_LEVEL_RW,
|
||||
access_type='ip',
|
||||
access_to='1.1.1.1')
|
||||
|
||||
def test_init_helper(self):
|
||||
self._helper.init_helper(self.server_details)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'list'],
|
||||
)
|
||||
|
||||
def test_create_export_share_does_not_exist(self):
|
||||
def fake_ssh_exec(*args, **kwargs):
|
||||
if 'showshare' in args[1]:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return ('', '')
|
||||
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=fake_ssh_exec))
|
||||
|
||||
ret = self._helper.create_export(self.server_details, self.share_name)
|
||||
expected_location = '\\\\%s\\%s' % (
|
||||
self.server_details['public_address'], self.share_name)
|
||||
self.assertEqual(expected_location, ret)
|
||||
share_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
self.share_name)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
[
|
||||
'sudo', 'net', 'conf', 'addshare', self.share_name,
|
||||
share_path, 'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
),
|
||||
mock.call(self.server_details, mock.ANY),
|
||||
])
|
||||
|
||||
def test_create_export_share_exist_recreate_true(self):
|
||||
ret = self._helper.create_export(self.server_details, self.share_name,
|
||||
recreate=True)
|
||||
expected_location = '\\\\%s\\%s' % (
|
||||
self.server_details['public_address'], self.share_name)
|
||||
self.assertEqual(expected_location, ret)
|
||||
share_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
self.share_name)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
[
|
||||
'sudo', 'net', 'conf', 'addshare', self.share_name,
|
||||
share_path, 'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
),
|
||||
mock.call(self.server_details, mock.ANY),
|
||||
])
|
||||
|
||||
def test_create_export_share_exist_recreate_false(self):
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self._helper.create_export,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
recreate=False,
|
||||
)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
])
|
||||
|
||||
def test_remove_export(self):
|
||||
self._helper.remove_export(self.server_details, self.share_name)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name],
|
||||
)
|
||||
|
||||
def test_remove_export_forcibly(self):
|
||||
delshare_command = ['sudo', 'net', 'conf', 'delshare', self.share_name]
|
||||
|
||||
def fake_ssh_exec(*args, **kwargs):
|
||||
if delshare_command == args[1]:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return ('', '')
|
||||
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=fake_ssh_exec))
|
||||
|
||||
self._helper.remove_export(self.server_details, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name],
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'smbcontrol', 'all', 'close-share', self.share_name],
|
||||
),
|
||||
])
|
||||
|
||||
def test_allow_access_ip_exist(self):
|
||||
hosts = [self.access['access_to'], ]
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareAccessExists,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
self.access['access_type'],
|
||||
self.access['access_level'],
|
||||
self.access['access_to'])
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_allow_access_ip_does_not_exist(self):
|
||||
hosts = []
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.allow_access(
|
||||
self.server_details, self.share_name,
|
||||
self.access['access_type'], self.access['access_level'],
|
||||
self.access['access_to'])
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, hosts, self.share_name)
|
||||
|
||||
def test_allow_access_wrong_type(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name, 'fake', const.ACCESS_LEVEL_RW, '1.1.1.1')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
|
||||
def test_allow_access_wrong_access_level(self, data):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccessLevel,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name, 'ip', data, '1.1.1.1')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
|
||||
def test_deny_access_unsupported_access_level(self, data):
|
||||
access = dict(access_to='1.1.1.1', access_level=data)
|
||||
self.mock_object(self._helper, '_get_allow_hosts')
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(self.server_details, self.share_name, access)
|
||||
|
||||
self.assertFalse(self._helper._get_allow_hosts.called)
|
||||
self.assertFalse(self._helper._set_allow_hosts.called)
|
||||
|
||||
def test_deny_access_list_has_value(self):
|
||||
hosts = [self.access['access_to'], ]
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, [], self.share_name)
|
||||
|
||||
def test_deny_access_list_does_not_have_value(self):
|
||||
hosts = []
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_deny_access_force(self):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_get_allow_hosts',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()),
|
||||
)
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access, force=True)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_deny_access_not_force(self):
|
||||
def raise_process_execution_error(*args, **kwargs):
|
||||
raise exception.ProcessExecutionError()
|
||||
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(side_effect=raise_process_execution_error))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.assertRaises(
|
||||
exception.ProcessExecutionError,
|
||||
self._helper.deny_access,
|
||||
self.server_details, self.share_name, self.access)
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
@ddt.data(
|
||||
'', '1.2.3.4:/nfs/like/export', '/1.2.3.4/foo', '\\1.2.3.4\\foo',
|
||||
'//1.2.3.4\\mixed_slashes_and_backslashes_one',
|
||||
'\\\\1.2.3.4/mixed_slashes_and_backslashes_two')
|
||||
def test__get_share_group_name_from_export_location(self, export_location):
|
||||
self.assertRaises(
|
||||
exception.InvalidShare,
|
||||
self._helper._get_share_group_name_from_export_location,
|
||||
export_location)
|
||||
|
||||
@ddt.data('//5.6.7.8/foo', '\\\\5.6.7.8\\foo')
|
||||
def test_get_exports_for_share(self, export_location):
|
||||
server = dict(public_address='1.2.3.4')
|
||||
self.mock_object(
|
||||
self._helper, '_get_share_group_name_from_export_location',
|
||||
mock.Mock(side_effect=(
|
||||
self._helper._get_share_group_name_from_export_location)))
|
||||
|
||||
result = self._helper.get_exports_for_share(server, export_location)
|
||||
|
||||
expected_export_location = ['\\\\%s\\foo' % server['public_address']]
|
||||
self.assertEqual(expected_export_location, result)
|
||||
self._helper._get_share_group_name_from_export_location.\
|
||||
assert_called_once_with(export_location)
|
||||
|
||||
@ddt.data(
|
||||
{'public_address_with_suffix': 'foo'},
|
||||
{'with_prefix_public_address': 'bar'},
|
||||
{'with_prefix_public_address_and_with_suffix': 'quuz'}, {})
|
||||
def test_get_exports_for_share_with_exception(self, server):
|
||||
export_location = '1.2.3.4:/foo/bar'
|
||||
|
||||
self.assertRaises(
|
||||
exception.ManilaException,
|
||||
self._helper.get_exports_for_share, server, export_location)
|
||||
|
||||
@ddt.data('//5.6.7.8/foo', '\\\\5.6.7.8\\foo')
|
||||
def test_get_share_path_by_export_location(self, export_location):
|
||||
fake_path = ' /bar/quuz\n '
|
||||
fake_server = dict()
|
||||
self.mock_object(
|
||||
self._helper, '_ssh_exec',
|
||||
mock.Mock(return_value=(fake_path, 'fake')))
|
||||
self.mock_object(
|
||||
self._helper, '_get_share_group_name_from_export_location',
|
||||
mock.Mock(side_effect=(
|
||||
self._helper._get_share_group_name_from_export_location)))
|
||||
|
||||
result = self._helper.get_share_path_by_export_location(
|
||||
fake_server, export_location)
|
||||
|
||||
self.assertEqual('/bar/quuz', result)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
fake_server, ['sudo', 'net', 'conf', 'getparm', 'foo', 'path'])
|
||||
self._helper._get_share_group_name_from_export_location.\
|
||||
assert_called_once_with(export_location)
|
||||
|
||||
def test_disable_access_for_maintenance(self):
|
||||
allowed_hosts = ['test', 'test2']
|
||||
maintenance_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
"%s.maintenance" % self.share_name)
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=allowed_hosts))
|
||||
|
||||
self._helper.disable_access_for_maintenance(
|
||||
self.server_details, self.share_name)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, [], self.share_name)
|
||||
valid_cmd = ['echo', "'test test2'", '| sudo tee', maintenance_path]
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details, valid_cmd)
|
||||
|
||||
def test_restore_access_after_maintenance(self):
|
||||
fake_maintenance_path = "test.path"
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=[("fake fake2", 0), "fake"]))
|
||||
|
||||
self._helper.restore_access_after_maintenance(
|
||||
self.server_details, self.share_name)
|
||||
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, ['fake', 'fake2'], self.share_name)
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server_details, ['cat', fake_maintenance_path])
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server_details, ['sudo rm -f', fake_maintenance_path])
|
||||
|
765
manila/tests/share/drivers/test_helpers.py
Normal file
765
manila/tests/share/drivers/test_helpers.py
Normal file
@ -0,0 +1,765 @@
|
||||
# Copyright 2015 Mirantis 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.common import constants as const
|
||||
from manila import exception
|
||||
import manila.share.configuration
|
||||
from manila.share.drivers import helpers
|
||||
from manila import test
|
||||
from manila.tests import fake_compute
|
||||
from manila.tests import fake_utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class NFSHelperTestCase(test.TestCase):
|
||||
"""Test case for NFS helper."""
|
||||
|
||||
def setUp(self):
|
||||
super(NFSHelperTestCase, self).setUp()
|
||||
fake_utils.stub_out_utils_execute(self)
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._ssh_exec = mock.Mock(return_value=('', ''))
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
self._helper = helpers.NFSHelper(self._execute, self._ssh_exec,
|
||||
self.fake_conf)
|
||||
ip = '10.254.0.3'
|
||||
self.server = fake_compute.FakeServer(
|
||||
ip=ip, public_address=ip, instance_id='fake_instance_id')
|
||||
self.share_name = 'fake_share_name'
|
||||
|
||||
def test_create_export(self):
|
||||
ret = self._helper.create_export(self.server, self.share_name)
|
||||
expected_location = ':'.join([self.server['public_address'],
|
||||
os.path.join(CONF.share_mount_path,
|
||||
self.share_name)])
|
||||
self.assertEqual(expected_location, ret)
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
|
||||
def test_allow_access(self, data):
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
self._helper.allow_access(
|
||||
self.server, self.share_name, 'ip', data, '10.0.0.2')
|
||||
local_path = os.path.join(CONF.share_mount_path, self.share_name)
|
||||
self._ssh_exec.assert_has_calls([
|
||||
mock.call(self.server, ['sudo', 'exportfs']),
|
||||
mock.call(self.server, ['sudo', 'exportfs', '-o',
|
||||
'%s,no_subtree_check' % data,
|
||||
':'.join(['10.0.0.2', local_path])])
|
||||
])
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server)
|
||||
|
||||
def test_allow_access_no_ip(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess,
|
||||
self._helper.allow_access,
|
||||
self.server, self.share_name,
|
||||
'fake_type', 'fake_level', 'fake_rule')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
|
||||
def test_deny_access(self, data):
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
local_path = os.path.join(CONF.share_mount_path, self.share_name)
|
||||
access = dict(
|
||||
access_to='10.0.0.2', access_type='ip', access_level=data)
|
||||
self._helper.deny_access(self.server, self.share_name, access)
|
||||
export_string = ':'.join(['10.0.0.2', local_path])
|
||||
expected_exec = ['sudo', 'exportfs', '-u', export_string]
|
||||
self._ssh_exec.assert_called_once_with(self.server, expected_exec)
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server)
|
||||
|
||||
def test_sync_nfs_temp_and_perm_files(self):
|
||||
self._helper._sync_nfs_temp_and_perm_files(self.server)
|
||||
self._helper._ssh_exec.assert_has_calls(
|
||||
[mock.call(self.server, mock.ANY) for i in range(1)])
|
||||
|
||||
@ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.88:/foo/quuz')
|
||||
def test_get_exports_for_share(self, export_location):
|
||||
server = dict(public_address='1.2.3.4')
|
||||
|
||||
result = self._helper.get_exports_for_share(server, export_location)
|
||||
|
||||
path = export_location.split(':')[-1]
|
||||
self.assertEqual([':'.join([server['public_address'], path])], result)
|
||||
|
||||
@ddt.data(
|
||||
{'public_address_with_suffix': 'foo'},
|
||||
{'with_prefix_public_address': 'bar'},
|
||||
{'with_prefix_public_address_and_with_suffix': 'quuz'}, {})
|
||||
def test_get_exports_for_share_with_error(self, server):
|
||||
export_location = '1.2.3.4:/foo/bar'
|
||||
|
||||
self.assertRaises(
|
||||
exception.ManilaException,
|
||||
self._helper.get_exports_for_share, server, export_location)
|
||||
|
||||
@ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar')
|
||||
def test_get_share_path_by_export_location(self, export_location):
|
||||
result = self._helper.get_share_path_by_export_location(
|
||||
dict(), export_location)
|
||||
|
||||
self.assertEqual('/foo/bar', result)
|
||||
|
||||
def test_disable_access_for_maintenance(self):
|
||||
fake_maintenance_path = "fake.path"
|
||||
share_mount_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path, self.share_name)
|
||||
self.mock_object(self._helper, '_ssh_exec')
|
||||
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
|
||||
self._helper.disable_access_for_maintenance(
|
||||
self.server, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server,
|
||||
['cat', const.NFS_EXPORTS_FILE,
|
||||
'| grep', self.share_name,
|
||||
'| sudo tee', fake_maintenance_path]
|
||||
)
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server,
|
||||
['sudo', 'exportfs', '-u', share_mount_path]
|
||||
)
|
||||
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
|
||||
self.server
|
||||
)
|
||||
|
||||
def test_restore_access_after_maintenance(self):
|
||||
fake_maintenance_path = "fake.path"
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
self.mock_object(self._helper, '_ssh_exec')
|
||||
|
||||
self._helper.restore_access_after_maintenance(
|
||||
self.server, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server,
|
||||
['cat', fake_maintenance_path,
|
||||
'| sudo tee -a', const.NFS_EXPORTS_FILE,
|
||||
'&& sudo exportfs -r', '&& sudo rm -f',
|
||||
fake_maintenance_path]
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CIFSHelperIPAccessTestCase(test.TestCase):
|
||||
"""Test case for CIFS helper with IP access."""
|
||||
|
||||
def setUp(self):
|
||||
super(CIFSHelperIPAccessTestCase, self).setUp()
|
||||
self.server_details = {'instance_id': 'fake',
|
||||
'public_address': '1.2.3.4', }
|
||||
self.share_name = 'fake_share_name'
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._ssh_exec = mock.Mock(return_value=('', ''))
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
self._helper = helpers.CIFSHelperIPAccess(self._execute,
|
||||
self._ssh_exec,
|
||||
self.fake_conf)
|
||||
self.access = dict(
|
||||
access_level=const.ACCESS_LEVEL_RW,
|
||||
access_type='ip',
|
||||
access_to='1.1.1.1')
|
||||
|
||||
def test_init_helper(self):
|
||||
self._helper.init_helper(self.server_details)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'list'],
|
||||
)
|
||||
|
||||
def test_create_export_share_does_not_exist(self):
|
||||
def fake_ssh_exec(*args, **kwargs):
|
||||
if 'showshare' in args[1]:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return ('', '')
|
||||
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=fake_ssh_exec))
|
||||
|
||||
ret = self._helper.create_export(self.server_details, self.share_name)
|
||||
expected_location = '\\\\%s\\%s' % (
|
||||
self.server_details['public_address'], self.share_name)
|
||||
self.assertEqual(expected_location, ret)
|
||||
share_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
self.share_name)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
[
|
||||
'sudo', 'net', 'conf', 'addshare', self.share_name,
|
||||
share_path, 'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
),
|
||||
])
|
||||
|
||||
def test_create_export_share_exist_recreate_true(self):
|
||||
ret = self._helper.create_export(self.server_details, self.share_name,
|
||||
recreate=True)
|
||||
expected_location = '\\\\%s\\%s' % (
|
||||
self.server_details['public_address'], self.share_name)
|
||||
self.assertEqual(expected_location, ret)
|
||||
share_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
self.share_name)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name, ]
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
[
|
||||
'sudo', 'net', 'conf', 'addshare', self.share_name,
|
||||
share_path, 'writeable=y', 'guest_ok=y',
|
||||
]
|
||||
),
|
||||
])
|
||||
|
||||
def test_create_export_share_exist_recreate_false(self):
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self._helper.create_export,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
recreate=False,
|
||||
)
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'showshare', self.share_name, ]
|
||||
),
|
||||
])
|
||||
|
||||
def test_remove_export(self):
|
||||
self._helper.remove_export(self.server_details, self.share_name)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name],
|
||||
)
|
||||
|
||||
def test_remove_export_forcibly(self):
|
||||
delshare_command = ['sudo', 'net', 'conf', 'delshare', self.share_name]
|
||||
|
||||
def fake_ssh_exec(*args, **kwargs):
|
||||
if delshare_command == args[1]:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return ('', '')
|
||||
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=fake_ssh_exec))
|
||||
|
||||
self._helper.remove_export(self.server_details, self.share_name)
|
||||
|
||||
self._helper._ssh_exec.assert_has_calls([
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'delshare', self.share_name],
|
||||
),
|
||||
mock.call(
|
||||
self.server_details,
|
||||
['sudo', 'smbcontrol', 'all', 'close-share', self.share_name],
|
||||
),
|
||||
])
|
||||
|
||||
def test_allow_access_ip_exist(self):
|
||||
hosts = [self.access['access_to'], ]
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareAccessExists,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
self.access['access_type'],
|
||||
self.access['access_level'],
|
||||
self.access['access_to'])
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_allow_access_ip_does_not_exist(self):
|
||||
hosts = []
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.allow_access(
|
||||
self.server_details, self.share_name,
|
||||
self.access['access_type'], self.access['access_level'],
|
||||
self.access['access_to'])
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, hosts, self.share_name)
|
||||
|
||||
def test_allow_access_wrong_type(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name, 'fake', const.ACCESS_LEVEL_RW, '1.1.1.1')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
|
||||
def test_allow_access_wrong_access_level(self, data):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccessLevel,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name, 'ip', data, '1.1.1.1')
|
||||
|
||||
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
|
||||
def test_deny_access_unsupported_access_level(self, data):
|
||||
access = dict(access_to='1.1.1.1', access_level=data)
|
||||
self.mock_object(self._helper, '_get_allow_hosts')
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(self.server_details, self.share_name, access)
|
||||
|
||||
self.assertFalse(self._helper._get_allow_hosts.called)
|
||||
self.assertFalse(self._helper._set_allow_hosts.called)
|
||||
|
||||
def test_deny_access_list_has_value(self):
|
||||
hosts = [self.access['access_to'], ]
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access)
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, [], self.share_name)
|
||||
|
||||
def test_deny_access_list_does_not_have_value(self):
|
||||
hosts = []
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=hosts))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_deny_access_force(self):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_get_allow_hosts',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()),
|
||||
)
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, self.access, force=True)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
def test_deny_access_not_force(self):
|
||||
def raise_process_execution_error(*args, **kwargs):
|
||||
raise exception.ProcessExecutionError()
|
||||
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(side_effect=raise_process_execution_error))
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.assertRaises(
|
||||
exception.ProcessExecutionError,
|
||||
self._helper.deny_access,
|
||||
self.server_details, self.share_name, self.access)
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_has_calls([])
|
||||
|
||||
@ddt.data(
|
||||
'', '1.2.3.4:/nfs/like/export', '/1.2.3.4/foo', '\\1.2.3.4\\foo',
|
||||
'//1.2.3.4\\mixed_slashes_and_backslashes_one',
|
||||
'\\\\1.2.3.4/mixed_slashes_and_backslashes_two')
|
||||
def test__get_share_group_name_from_export_location(self, export_location):
|
||||
self.assertRaises(
|
||||
exception.InvalidShare,
|
||||
self._helper._get_share_group_name_from_export_location,
|
||||
export_location)
|
||||
|
||||
@ddt.data('//5.6.7.8/foo', '\\\\5.6.7.8\\foo')
|
||||
def test_get_exports_for_share(self, export_location):
|
||||
server = dict(public_address='1.2.3.4')
|
||||
self.mock_object(
|
||||
self._helper, '_get_share_group_name_from_export_location',
|
||||
mock.Mock(side_effect=(
|
||||
self._helper._get_share_group_name_from_export_location)))
|
||||
|
||||
result = self._helper.get_exports_for_share(server, export_location)
|
||||
|
||||
expected_export_location = ['\\\\%s\\foo' % server['public_address']]
|
||||
self.assertEqual(expected_export_location, result)
|
||||
self._helper._get_share_group_name_from_export_location.\
|
||||
assert_called_once_with(export_location)
|
||||
|
||||
@ddt.data(
|
||||
{'public_address_with_suffix': 'foo'},
|
||||
{'with_prefix_public_address': 'bar'},
|
||||
{'with_prefix_public_address_and_with_suffix': 'quuz'}, {})
|
||||
def test_get_exports_for_share_with_exception(self, server):
|
||||
export_location = '1.2.3.4:/foo/bar'
|
||||
|
||||
self.assertRaises(
|
||||
exception.ManilaException,
|
||||
self._helper.get_exports_for_share, server, export_location)
|
||||
|
||||
@ddt.data('//5.6.7.8/foo', '\\\\5.6.7.8\\foo')
|
||||
def test_get_share_path_by_export_location(self, export_location):
|
||||
fake_path = ' /bar/quuz\n '
|
||||
fake_server = dict()
|
||||
self.mock_object(
|
||||
self._helper, '_ssh_exec',
|
||||
mock.Mock(return_value=(fake_path, 'fake')))
|
||||
self.mock_object(
|
||||
self._helper, '_get_share_group_name_from_export_location',
|
||||
mock.Mock(side_effect=(
|
||||
self._helper._get_share_group_name_from_export_location)))
|
||||
|
||||
result = self._helper.get_share_path_by_export_location(
|
||||
fake_server, export_location)
|
||||
|
||||
self.assertEqual('/bar/quuz', result)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
fake_server, ['sudo', 'net', 'conf', 'getparm', 'foo', 'path'])
|
||||
self._helper._get_share_group_name_from_export_location.\
|
||||
assert_called_once_with(export_location)
|
||||
|
||||
def test_disable_access_for_maintenance(self):
|
||||
allowed_hosts = ['test', 'test2']
|
||||
maintenance_path = os.path.join(
|
||||
self._helper.configuration.share_mount_path,
|
||||
"%s.maintenance" % self.share_name)
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.mock_object(self._helper, '_get_allow_hosts',
|
||||
mock.Mock(return_value=allowed_hosts))
|
||||
|
||||
self._helper.disable_access_for_maintenance(
|
||||
self.server_details, self.share_name)
|
||||
|
||||
self._helper._get_allow_hosts.assert_called_once_with(
|
||||
self.server_details, self.share_name)
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, [], self.share_name)
|
||||
valid_cmd = ['echo', "'test test2'", '| sudo tee', maintenance_path]
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details, valid_cmd)
|
||||
|
||||
def test_restore_access_after_maintenance(self):
|
||||
fake_maintenance_path = "test.path"
|
||||
self.mock_object(self._helper, '_set_allow_hosts')
|
||||
self.mock_object(self._helper, '_get_maintenance_file_path',
|
||||
mock.Mock(return_value=fake_maintenance_path))
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=[("fake fake2", 0), "fake"]))
|
||||
|
||||
self._helper.restore_access_after_maintenance(
|
||||
self.server_details, self.share_name)
|
||||
|
||||
self._helper._set_allow_hosts.assert_called_once_with(
|
||||
self.server_details, ['fake', 'fake2'], self.share_name)
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server_details, ['cat', fake_maintenance_path])
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server_details, ['sudo rm -f', fake_maintenance_path])
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CIFSHelperUserAccessTestCase(test.TestCase):
|
||||
"""Test case for CIFS helper with user access."""
|
||||
access_rw = dict(
|
||||
access_level=const.ACCESS_LEVEL_RW,
|
||||
access_type='user',
|
||||
access_to='manila-user')
|
||||
access_ro = dict(
|
||||
access_level=const.ACCESS_LEVEL_RO,
|
||||
access_type='user',
|
||||
access_to='manila-user')
|
||||
|
||||
def setUp(self):
|
||||
super(CIFSHelperUserAccessTestCase, self).setUp()
|
||||
self.server_details = {'instance_id': 'fake',
|
||||
'public_address': '1.2.3.4', }
|
||||
self.share_name = 'fake_share_name'
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._ssh_exec = mock.Mock(return_value=('', ''))
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
self._helper = helpers.CIFSHelperUserAccess(
|
||||
self._execute, self._ssh_exec, self.fake_conf)
|
||||
|
||||
@ddt.data('ip', 'cert', 'fake')
|
||||
def test_allow_access_wrong_type(self, wrong_access_type):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
wrong_access_type,
|
||||
const.ACCESS_LEVEL_RW,
|
||||
'1.1.1.1')
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_allow_access_ro_rule_does_not_exist(self, access):
|
||||
users = ['user1', 'user2']
|
||||
self.mock_object(self._helper, '_get_valid_users',
|
||||
mock.Mock(return_value=users))
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self._helper.allow_access(
|
||||
self.server_details, self.share_name,
|
||||
access['access_type'], access['access_level'],
|
||||
access['access_to'])
|
||||
self.assertEqual(
|
||||
[mock.call(self.server_details, self.share_name),
|
||||
mock.call(self.server_details, self.share_name,
|
||||
access['access_level'])],
|
||||
self._helper._get_valid_users.call_args_list)
|
||||
self._helper._set_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
users,
|
||||
self.share_name,
|
||||
access['access_level'])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_allow_access_ro_rule_exists(self, access):
|
||||
users = ['user1', 'user2', 'manila-user']
|
||||
self.mock_object(self._helper, '_get_valid_users',
|
||||
mock.Mock(return_value=users))
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareAccessExists,
|
||||
self._helper.allow_access,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_type'],
|
||||
access['access_level'],
|
||||
access['access_to'])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_deny_access_list_has_value(self, access):
|
||||
users = ['user1', 'user2', 'manila-user']
|
||||
self.mock_object(self._helper, '_get_valid_users',
|
||||
mock.Mock(return_value=users))
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, access)
|
||||
self._helper._get_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_level'],
|
||||
force=False)
|
||||
self._helper._set_valid_users.assert_called_once_with(
|
||||
self.server_details, ['user1', 'user2'], self.share_name,
|
||||
access['access_level'])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_deny_access_list_does_not_have_value(self, access):
|
||||
users = []
|
||||
self.mock_object(self._helper, '_get_valid_users',
|
||||
mock.Mock(return_value=users))
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, access)
|
||||
self._helper._get_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_level'],
|
||||
force=False)
|
||||
self._helper._set_valid_users.assert_has_calls([])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_deny_access_force_access_exists(self, access):
|
||||
users = ['user1', 'user2', 'manila-user']
|
||||
self.mock_object(self._helper, '_get_valid_users',
|
||||
mock.Mock(return_value=users))
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, access, force=True)
|
||||
self._helper._get_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_level'],
|
||||
force=True)
|
||||
self._helper._set_valid_users.assert_called_once_with(
|
||||
self.server_details, ['user1', 'user2'], self.share_name,
|
||||
access['access_level'])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_deny_access_force_access_does_not_exist(self, access):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_get_valid_users',
|
||||
mock.Mock(return_value=[]),
|
||||
)
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self._helper.deny_access(
|
||||
self.server_details, self.share_name, access, force=True)
|
||||
|
||||
self._helper._get_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_level'],
|
||||
force=True)
|
||||
self._helper._set_valid_users.assert_has_calls([])
|
||||
|
||||
@ddt.data(access_rw, access_ro)
|
||||
def test_deny_access_force_exc(self, access):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_get_valid_users',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()),
|
||||
)
|
||||
self.mock_object(self._helper, '_set_valid_users')
|
||||
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._helper.deny_access,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access,
|
||||
force=True)
|
||||
self._helper._get_valid_users.assert_called_once_with(
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
access['access_level'],
|
||||
force=True)
|
||||
|
||||
def test_get_conf_param_rw(self):
|
||||
result = self._helper._get_conf_param(const.ACCESS_LEVEL_RW)
|
||||
self.assertEqual('valid users', result)
|
||||
|
||||
def test_get_conf_param_ro(self):
|
||||
result = self._helper._get_conf_param(const.ACCESS_LEVEL_RO)
|
||||
self.assertEqual('read list', result)
|
||||
|
||||
@ddt.data(False, True)
|
||||
def test_get_valid_users(self, force):
|
||||
users = ("\"manila-user\" \"user1\" \"user2\"", None)
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(return_value=users))
|
||||
result = self._helper._get_valid_users(self.server_details,
|
||||
self.share_name,
|
||||
const.ACCESS_LEVEL_RW,
|
||||
force=force)
|
||||
self.assertEqual(['manila-user', 'user1', 'user2'], result)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
|
||||
|
||||
@ddt.data(False, True)
|
||||
def test_get_valid_users_access_level_none(self, force):
|
||||
def fake_ssh_exec(*args, **kwargs):
|
||||
if 'valid users' in args[1]:
|
||||
return ("\"user1\"", '')
|
||||
else:
|
||||
return ("\"user2\"", '')
|
||||
|
||||
self.mock_object(self._helper, '_ssh_exec',
|
||||
mock.Mock(side_effect=fake_ssh_exec))
|
||||
|
||||
result = self._helper._get_valid_users(self.server_details,
|
||||
self.share_name,
|
||||
force=force)
|
||||
self.assertEqual(['user1', 'user2'], result)
|
||||
for param in ['read list', 'valid users']:
|
||||
self._helper._ssh_exec.assert_any_call(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'getparm', self.share_name, param])
|
||||
|
||||
def test_get_valid_users_access_level_none_with_exc(self):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_ssh_exec',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()))
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._helper._get_valid_users,
|
||||
self.server_details,
|
||||
self.share_name,
|
||||
force=False)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
|
||||
|
||||
def test_get_valid_users_force_with_exc(self):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_ssh_exec',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()))
|
||||
result = self._helper._get_valid_users(self.server_details,
|
||||
self.share_name,
|
||||
const.ACCESS_LEVEL_RW)
|
||||
self.assertEqual([], result)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
|
||||
|
||||
def test_get_valid_users_not_force_with_exc(self):
|
||||
self.mock_object(
|
||||
self._helper,
|
||||
'_ssh_exec',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError()))
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._helper._get_valid_users, self.server_details,
|
||||
self.share_name, const.ACCESS_LEVEL_RW, force=False)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
|
||||
|
||||
def test_set_valid_users(self):
|
||||
self.mock_object(self._helper, '_ssh_exec', mock.Mock())
|
||||
self._helper._set_valid_users(self.server_details, ['user1', 'user2'],
|
||||
self.share_name, const.ACCESS_LEVEL_RW)
|
||||
self._helper._ssh_exec.assert_called_once_with(
|
||||
self.server_details,
|
||||
['sudo', 'net', 'conf', 'setparm', self.share_name,
|
||||
'valid users', '"user1 user2"'])
|
515
manila/tests/share/drivers/test_lvm.py
Normal file
515
manila/tests/share/drivers/test_lvm.py
Normal file
@ -0,0 +1,515 @@
|
||||
# Copyright 2012 NetApp
|
||||
# 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.
|
||||
"""Unit tests for the LVM driver module."""
|
||||
|
||||
import os
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share import configuration
|
||||
from manila.share.drivers import lvm
|
||||
from manila import test
|
||||
from manila.tests.db import fakes as db_fakes
|
||||
from manila.tests import fake_utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def fake_share(**kwargs):
|
||||
share = {
|
||||
'id': 'fakeid',
|
||||
'name': 'fakename',
|
||||
'size': 1,
|
||||
'share_proto': 'NFS',
|
||||
'export_location': '127.0.0.1:/mnt/nfs/volume-00002',
|
||||
}
|
||||
share.update(kwargs)
|
||||
return db_fakes.FakeModel(share)
|
||||
|
||||
|
||||
def fake_snapshot(**kwargs):
|
||||
snapshot = {
|
||||
'id': 'fakesnapshotid',
|
||||
'share_name': 'fakename',
|
||||
'share_id': 'fakeid',
|
||||
'name': 'fakesnapshotname',
|
||||
'share_proto': 'NFS',
|
||||
'export_location': '127.0.0.1:/mnt/nfs/volume-00002',
|
||||
'share': {'size': 1},
|
||||
}
|
||||
snapshot.update(kwargs)
|
||||
return db_fakes.FakeModel(snapshot)
|
||||
|
||||
|
||||
def fake_access(**kwargs):
|
||||
access = {
|
||||
'id': 'fakeaccid',
|
||||
'access_type': 'ip',
|
||||
'access_to': '10.0.0.2',
|
||||
'access_level': 'rw',
|
||||
'state': 'active',
|
||||
}
|
||||
access.update(kwargs)
|
||||
return db_fakes.FakeModel(access)
|
||||
|
||||
|
||||
class LVMShareDriverTestCase(test.TestCase):
|
||||
"""Tests LVMShareDriver."""
|
||||
|
||||
def setUp(self):
|
||||
super(LVMShareDriverTestCase, self).setUp()
|
||||
fake_utils.stub_out_utils_execute(self)
|
||||
self._context = context.get_admin_context()
|
||||
|
||||
CONF.set_default('lvm_share_volume_group', 'fakevg')
|
||||
CONF.set_default('lvm_share_export_ip', '10.0.0.1')
|
||||
CONF.set_default('driver_handles_share_servers', False)
|
||||
CONF.set_default('reserved_share_percentage', 50)
|
||||
|
||||
self._helper_cifs = mock.Mock()
|
||||
self._helper_nfs = mock.Mock()
|
||||
self.fake_conf = configuration.Configuration(None)
|
||||
self._db = mock.Mock()
|
||||
self._os = lvm.os = mock.Mock()
|
||||
self._os.path.join = os.path.join
|
||||
self._driver = lvm.LVMShareDriver(self._db,
|
||||
configuration=self.fake_conf)
|
||||
self._driver._helpers = {
|
||||
'CIFS': self._helper_cifs,
|
||||
'NFS': self._helper_nfs,
|
||||
}
|
||||
|
||||
self.share = fake_share()
|
||||
self.access = fake_access()
|
||||
self.snapshot = fake_snapshot()
|
||||
self.server = {
|
||||
'public_address': self.fake_conf.lvm_share_export_ip,
|
||||
'instance_id': 'LVM',
|
||||
}
|
||||
|
||||
# Used only to test compatibility with share manager
|
||||
self.share_server = "fake_share_server"
|
||||
|
||||
def tearDown(self):
|
||||
super(LVMShareDriverTestCase, self).tearDown()
|
||||
fake_utils.fake_execute_set_repliers([])
|
||||
fake_utils.fake_execute_clear_log()
|
||||
|
||||
def test_do_setup(self):
|
||||
CONF.set_default('lvm_share_helpers', ['NFS=fakenfs'])
|
||||
lvm.importutils = mock.Mock()
|
||||
lvm.importutils.import_class.return_value = self._helper_nfs
|
||||
self._driver.do_setup(self._context)
|
||||
lvm.importutils.import_class.assert_has_calls([
|
||||
mock.call('fakenfs')
|
||||
])
|
||||
|
||||
def test_check_for_setup_error(self):
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
return '\n fake1\n fakevg\n fake2\n', ''
|
||||
|
||||
expected_exec = [
|
||||
'sudo vgs --noheadings -o name',
|
||||
]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
self._driver.check_for_setup_error()
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_check_for_setup_error_no_vg(self):
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
return '\n fake0\n fake1\n fake2\n', ''
|
||||
|
||||
fake_utils.fake_execute_set_repliers([('sudo vgs --noheadings -o name',
|
||||
exec_runner)])
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self._driver.check_for_setup_error)
|
||||
|
||||
def test_check_for_setup_error_no_export_ip(self):
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
return '\n fake1\n fakevg\n fake2\n', ''
|
||||
|
||||
fake_utils.fake_execute_set_repliers([('sudo vgs --noheadings -o name',
|
||||
exec_runner)])
|
||||
CONF.set_default('lvm_share_export_ip', None)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self._driver.check_for_setup_error)
|
||||
|
||||
def test_local_path_normal(self):
|
||||
share = fake_share(name='fake_sharename')
|
||||
CONF.set_default('lvm_share_volume_group', 'fake_vg')
|
||||
ret = self._driver._get_local_path(share)
|
||||
self.assertEqual('/dev/mapper/fake_vg-fake_sharename', ret)
|
||||
|
||||
def test_local_path_escapes(self):
|
||||
share = fake_share(name='fake-sharename')
|
||||
CONF.set_default('lvm_share_volume_group', 'fake-vg')
|
||||
ret = self._driver._get_local_path(share)
|
||||
self.assertEqual('/dev/mapper/fake--vg-fake--sharename', ret)
|
||||
|
||||
def test_create_share(self):
|
||||
self._helper_nfs.create_export.return_value = 'fakelocation'
|
||||
self._driver._mount_device = mock.Mock()
|
||||
ret = self._driver.create_share(self._context, self.share,
|
||||
self.share_server)
|
||||
CONF.set_default('lvm_share_mirrors', 0)
|
||||
self._driver._mount_device.assert_called_with(
|
||||
self.share, '/dev/mapper/fakevg-fakename')
|
||||
expected_exec = [
|
||||
'lvcreate -L 1G -n fakename fakevg',
|
||||
'mkfs.ext4 /dev/mapper/fakevg-fakename',
|
||||
]
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertEqual('fakelocation', ret)
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
CONF.set_default('lvm_share_mirrors', 0)
|
||||
self._driver._mount_device = mock.Mock()
|
||||
snapshot_instance = {
|
||||
'snapshot_id': 'fakesnapshotid',
|
||||
'name': 'fakename'
|
||||
}
|
||||
mount_share = '/dev/mapper/fakevg-fakename'
|
||||
mount_snapshot = '/dev/mapper/fakevg-fakename'
|
||||
self._helper_nfs.create_export.return_value = 'fakelocation'
|
||||
self._driver.create_share_from_snapshot(self._context,
|
||||
self.share,
|
||||
snapshot_instance,
|
||||
self.share_server)
|
||||
|
||||
self._driver._mount_device.assert_called_with(self.share,
|
||||
mount_snapshot)
|
||||
expected_exec = [
|
||||
'lvcreate -L 1G -n fakename fakevg',
|
||||
'mkfs.ext4 /dev/mapper/fakevg-fakename',
|
||||
("dd count=0 if=%s of=%s iflag=direct oflag=direct" %
|
||||
(mount_snapshot, mount_share)),
|
||||
("dd if=%s of=%s count=1024 bs=1M iflag=direct oflag=direct" %
|
||||
(mount_snapshot, mount_share)),
|
||||
]
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_create_share_mirrors(self):
|
||||
|
||||
share = fake_share(size='2048')
|
||||
CONF.set_default('lvm_share_mirrors', 2)
|
||||
self._helper_nfs.create_export.return_value = 'fakelocation'
|
||||
self._driver._mount_device = mock.Mock()
|
||||
ret = self._driver.create_share(self._context, share,
|
||||
self.share_server)
|
||||
self._driver._mount_device.assert_called_with(
|
||||
share, '/dev/mapper/fakevg-fakename')
|
||||
expected_exec = [
|
||||
'lvcreate -L 2048G -n fakename fakevg -m 2 --nosync -R 2',
|
||||
'mkfs.ext4 /dev/mapper/fakevg-fakename',
|
||||
]
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertEqual('fakelocation', ret)
|
||||
|
||||
def test_deallocate_container(self):
|
||||
expected_exec = ['lvremove -f fakevg/fakename']
|
||||
self._driver._deallocate_container(self.share['name'])
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_deallocate_container_error(self):
|
||||
def _fake_exec(*args, **kwargs):
|
||||
raise exception.ProcessExecutionError(stderr="error")
|
||||
|
||||
self.mock_object(self._driver, '_try_execute', _fake_exec)
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._driver._deallocate_container,
|
||||
self.share['name'])
|
||||
|
||||
def test_deallocate_container_not_found_error(self):
|
||||
def _fake_exec(*args, **kwargs):
|
||||
raise exception.ProcessExecutionError(stderr="not found")
|
||||
|
||||
self.mock_object(self._driver, '_try_execute', _fake_exec)
|
||||
self._driver._deallocate_container(self.share['name'])
|
||||
|
||||
@mock.patch.object(lvm.LVMShareDriver, '_update_share_stats', mock.Mock())
|
||||
def test_get_share_stats(self):
|
||||
with mock.patch.object(self._driver, '_stats', mock.Mock) as stats:
|
||||
self.assertEqual(stats, self._driver.get_share_stats())
|
||||
self.assertFalse(self._driver._update_share_stats.called)
|
||||
|
||||
@mock.patch.object(lvm.LVMShareDriver, '_update_share_stats', mock.Mock())
|
||||
def test_get_share_stats_refresh(self):
|
||||
with mock.patch.object(self._driver, '_stats', mock.Mock) as stats:
|
||||
self.assertEqual(stats,
|
||||
self._driver.get_share_stats(refresh=True))
|
||||
self._driver._update_share_stats.assert_called_once_with()
|
||||
|
||||
def test_remove_export(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self._os.path.exists.return_value = True
|
||||
|
||||
self._driver._remove_export(self._context, self.share)
|
||||
|
||||
expected_exec = [
|
||||
"umount -f %s" % (mount_path,),
|
||||
]
|
||||
|
||||
self._os.path.exists.assert_called_with(mount_path)
|
||||
self._os.rmdir.assert_called_with(mount_path)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_remove_export_is_busy_error(self):
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
raise exception.ProcessExecutionError(stderr='device is busy')
|
||||
self._os.path.exists.return_value = True
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
expected_exec = [
|
||||
"umount -f %s" % (mount_path),
|
||||
]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
|
||||
self.assertRaises(exception.ShareBusyException,
|
||||
self._driver._remove_export, self._context,
|
||||
self.share)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_remove_export_error(self):
|
||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
||||
raise exception.ProcessExecutionError(stderr='fake error')
|
||||
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
expected_exec = [
|
||||
"umount -f %s" % (mount_path),
|
||||
]
|
||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
||||
self._os.path.exists.return_value = True
|
||||
self._driver._remove_export(self._context, self.share)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_remove_export_rmdir_error(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self._os.path.exists.return_value = True
|
||||
self.mock_object(self._os, 'rmdir', mock.Mock(side_effect=OSError))
|
||||
|
||||
self._driver._remove_export(self._context, self.share)
|
||||
|
||||
expected_exec = [
|
||||
"umount -f %s" % (mount_path,),
|
||||
]
|
||||
|
||||
self._os.path.exists.assert_called_with(mount_path)
|
||||
self._os.rmdir.assert_called_with(mount_path)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self._driver.create_snapshot(self._context, self.snapshot,
|
||||
self.share_server)
|
||||
expected_exec = [
|
||||
("lvcreate -L 1G --name fakesnapshotname --snapshot "
|
||||
"%s/fakename" % (CONF.lvm_share_volume_group,)),
|
||||
]
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_ensure_share(self):
|
||||
device_name = '/dev/mapper/fakevg-fakename'
|
||||
with mock.patch.object(self._driver,
|
||||
'_mount_device',
|
||||
mock.Mock(return_value='fake_location')):
|
||||
self._driver.ensure_share(self._context, self.share,
|
||||
self.share_server)
|
||||
self._driver._mount_device.assert_called_with(self.share,
|
||||
device_name)
|
||||
self._helper_nfs.create_export.assert_called_once_with(
|
||||
self.server, self.share['name'], recreate=True)
|
||||
|
||||
def test_delete_share(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self._helper_nfs.remove_export(mount_path, self.share['name'])
|
||||
self._driver._delete_share(self._context, self.share)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
expected_exec = ['lvremove -f fakevg/fakesnapshotname']
|
||||
self._driver.delete_snapshot(self._context, self.snapshot,
|
||||
self.share_server)
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
|
||||
def test_delete_share_invalid_share(self):
|
||||
self._driver._get_helper = mock.Mock(
|
||||
side_effect=exception.InvalidShare(reason='fake'))
|
||||
self._driver.delete_share(self._context, self.share, self.share_server)
|
||||
|
||||
def test_delete_share_process_execution_error(self):
|
||||
self.mock_object(
|
||||
self._helper_nfs,
|
||||
'remove_export',
|
||||
mock.Mock(side_effect=exception.ProcessExecutionError))
|
||||
|
||||
self._driver._delete_share(self._context, self.share)
|
||||
self._helper_nfs.remove_export.assert_called_once_with(
|
||||
self.server,
|
||||
self.share['name'])
|
||||
|
||||
def test_allow_access(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self._helper_nfs.allow_access(mount_path,
|
||||
self.share['name'],
|
||||
self.access['access_type'],
|
||||
self.access['access_to'])
|
||||
self._driver.allow_access(self._context, self.share, self.access,
|
||||
self.share_server)
|
||||
|
||||
def test_deny_access(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self._helper_nfs.deny_access(mount_path,
|
||||
self.share['name'],
|
||||
self.access['access_type'],
|
||||
self.access['access_to'])
|
||||
self._driver.deny_access(self._context, self.share, self.access,
|
||||
self.share_server)
|
||||
|
||||
def test_mount_device(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
ret = self._driver._mount_device(self.share, 'fakedevice')
|
||||
expected_exec = [
|
||||
"mkdir -p %s" % (mount_path,),
|
||||
"mount fakedevice %s" % (mount_path,),
|
||||
"chmod 777 %s" % (mount_path,),
|
||||
]
|
||||
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||
self.assertEqual(mount_path, ret)
|
||||
|
||||
def test_mount_device_already(self):
|
||||
def exec_runner(*args, **kwargs):
|
||||
if 'mount' in args and '-l' not in args:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return 'fakedevice', ''
|
||||
|
||||
self.mock_object(self._driver, '_execute', exec_runner)
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
|
||||
ret = self._driver._mount_device(self.share, 'fakedevice')
|
||||
self.assertEqual(mount_path, ret)
|
||||
|
||||
def test_mount_device_error(self):
|
||||
def exec_runner(*args, **kwargs):
|
||||
if 'mount' in args and '-l' not in args:
|
||||
raise exception.ProcessExecutionError()
|
||||
else:
|
||||
return 'fake', ''
|
||||
|
||||
self.mock_object(self._driver, '_execute', exec_runner)
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self._driver._mount_device, self.share, 'fakedevice')
|
||||
|
||||
def test_get_helper(self):
|
||||
share_cifs = fake_share(share_proto='CIFS')
|
||||
share_nfs = fake_share(share_proto='NFS')
|
||||
share_fake = fake_share(share_proto='FAKE')
|
||||
self.assertEqual(self._driver._get_helper(share_cifs),
|
||||
self._helper_cifs)
|
||||
self.assertEqual(self._driver._get_helper(share_nfs),
|
||||
self._helper_nfs)
|
||||
self.assertRaises(exception.InvalidShare, self._driver._get_helper,
|
||||
share_fake)
|
||||
|
||||
def _get_mount_path(self, share):
|
||||
return os.path.join(CONF.lvm_share_export_root, share['name'])
|
||||
|
||||
def test_unmount_device(self):
|
||||
mount_path = self._get_mount_path(self.share)
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self._driver._unmount_device(self.share)
|
||||
self._driver._execute.assert_any_call('umount', mount_path,
|
||||
run_as_root=True)
|
||||
self._driver._execute.assert_any_call('rmdir', mount_path,
|
||||
run_as_root=True)
|
||||
|
||||
def test_extend_share(self):
|
||||
local_path = self._driver._get_local_path(self.share)
|
||||
self.mock_object(self._driver, '_extend_container')
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self._driver.extend_share(self.share, 3)
|
||||
self._driver._extend_container.assert_called_once_with(self.share,
|
||||
local_path, 3)
|
||||
self._driver._execute.assert_called_once_with('resize2fs', local_path,
|
||||
run_as_root=True)
|
||||
|
||||
def test_ssh_exec_as_root(self):
|
||||
command = ['fake_command']
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self._driver._ssh_exec_as_root('fake_server', command)
|
||||
self._driver._execute.assert_called_once_with('fake_command')
|
||||
|
||||
def test_ssh_exec_as_root_with_sudo(self):
|
||||
command = ['sudo', 'fake_command']
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self._driver._ssh_exec_as_root('fake_server', command)
|
||||
self._driver._execute.assert_called_once_with('fake_command',
|
||||
run_as_root=True)
|
||||
|
||||
def test_extend_container(self):
|
||||
self.mock_object(self._driver, '_try_execute')
|
||||
self._driver._extend_container(self.share, 'device_name', 3)
|
||||
self._driver._try_execute.assert_called_once_with(
|
||||
'lvextend',
|
||||
'-L',
|
||||
'3G',
|
||||
'-n',
|
||||
'device_name',
|
||||
run_as_root=True)
|
||||
|
||||
def test_get_share_server_pools(self):
|
||||
expected_result = [{
|
||||
'pool_name': 'lvm-single-pool',
|
||||
'total_capacity_gb': 33,
|
||||
'free_capacity_gb': 22,
|
||||
'reserved_percentage': 0,
|
||||
}, ]
|
||||
self.mock_object(
|
||||
self._driver,
|
||||
'_execute',
|
||||
mock.Mock(return_value=("VSize 33g VFree 22g", None)))
|
||||
|
||||
self.assertEqual(expected_result,
|
||||
self._driver.get_share_server_pools())
|
||||
|
||||
def test_copy_volume_error(self):
|
||||
def _fake_exec(*args, **kwargs):
|
||||
if 'count=0' in args:
|
||||
raise exception.ProcessExecutionError()
|
||||
|
||||
self.mock_object(self._driver, '_execute',
|
||||
mock.Mock(side_effect=_fake_exec))
|
||||
self._driver._copy_volume('src', 'dest', 1)
|
||||
self._driver._execute.assert_any_call('dd', 'count=0', 'if=src',
|
||||
'of=dest', 'iflag=direct',
|
||||
'oflag=direct', run_as_root=True)
|
||||
self._driver._execute.assert_any_call('dd', 'if=src', 'of=dest',
|
||||
'count=1024', 'bs=1M',
|
||||
run_as_root=True)
|
||||
|
||||
def test_update_share_stats(self):
|
||||
self.mock_object(self._driver, 'get_share_server_pools',
|
||||
mock.Mock(return_value='test-pool'))
|
||||
|
||||
self._driver._update_share_stats()
|
||||
self.assertEqual('LVM', self._driver._stats['share_backend_name'])
|
||||
self.assertEqual('NFS_CIFS', self._driver._stats['storage_protocol'])
|
||||
self.assertEqual(50, self._driver._stats['reserved_percentage'])
|
||||
self.assertEqual(None,
|
||||
self._driver._stats['consistency_group_support'])
|
||||
self.assertEqual(True, self._driver._stats['snapshot_support'])
|
||||
self.assertEqual('LVMShareDriver', self._driver._stats['driver_name'])
|
||||
self.assertEqual('test-pool', self._driver._stats['pools'])
|
Loading…
x
Reference in New Issue
Block a user