Remove LVM driver
Support of LVM driver is planned to be dropped as it is duplicates functionality from Cinder. Default driver is Generic driver, that uses Cinder. Changes: - removed LVM driver - removed related stuff to LVM driver Partially implements blueprint remove-lvm-driver Change-Id: If2df592db082fdc5d609a5ce644106fee7d0583d
This commit is contained in:
parent
b8f511e8c9
commit
dd4bd5c487
@ -2,39 +2,6 @@
|
|||||||
# This file should be owned by (and only-writeable by) the root user
|
# This file should be owned by (and only-writeable by) the root user
|
||||||
|
|
||||||
[Filters]
|
[Filters]
|
||||||
# manila/share/drivers/lvm.py: 'mkfs.ext4', '/dev/mapper/%s'
|
|
||||||
mkfs.ext4: CommandFilter, /sbin/mkfs.ext4, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'exportfs', ...
|
|
||||||
exportfs: CommandFilter, /usr/sbin/exportfs, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'smbd', '-s', '%s', '-D'
|
|
||||||
smbd: CommandFilter, /usr/sbin/smbd, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'umount', '-f', '%s'
|
|
||||||
umount: CommandFilter, /bin/umount, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'mount', '/dev/mapper/%s', '%s'
|
|
||||||
mount: CommandFilter, /bin/mount, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'chmod', '777', '%s'
|
|
||||||
chmod: CommandFilter, /bin/chmod, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'chown', 'nobody', '-R', '%s'
|
|
||||||
chown: CommandFilter, /bin/chown, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'pkill', '-HUP', 'smbd'
|
|
||||||
pkill: CommandFilter, /usr/bin/pkill, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'smbcontrol', 'all', 'close-share', '%s'
|
|
||||||
smbcontrol: CommandFilter, /usr/bin/smbcontrol, root
|
|
||||||
|
|
||||||
# manila/share/drivers/lvm.py: 'net', 'conf', 'addshare', '%s', '%s', 'writeable=y', 'guest_ok=y
|
|
||||||
# manila/share/drivers/lvm.py: 'net', 'conf', 'delshare', '%s'
|
|
||||||
# manila/share/drivers/lvm.py: 'net', 'conf', 'setparm', '%s', '%s', '%s'
|
|
||||||
# manila/share/drivers/lvm.py: 'net', 'conf', 'getparm', '%s', 'hosts allow'
|
|
||||||
net: CommandFilter, /usr/bin/net, root
|
|
||||||
|
|
||||||
# manila/share/drivers/glusterfs.py: 'mkdir', '%s'
|
# manila/share/drivers/glusterfs.py: 'mkdir', '%s'
|
||||||
mkdir: CommandFilter, /usr/bin/mkdir, root
|
mkdir: CommandFilter, /usr/bin/mkdir, root
|
||||||
|
|
||||||
|
@ -28,10 +28,6 @@ lvdisplay: CommandFilter, lvdisplay, root
|
|||||||
# manila/volume/driver.py: 'iscsiadm', '-m', 'node', '-T', ...
|
# manila/volume/driver.py: 'iscsiadm', '-m', 'node', '-T', ...
|
||||||
iscsiadm: CommandFilter, iscsiadm, root
|
iscsiadm: CommandFilter, iscsiadm, root
|
||||||
|
|
||||||
# manila/volume/drivers/lvm.py: 'shred', '-n3'
|
|
||||||
# manila/volume/drivers/lvm.py: 'shred', '-n0', '-z', '-s%dMiB'
|
|
||||||
shred: CommandFilter, shred, root
|
|
||||||
|
|
||||||
#manila/volume/.py: utils.temporary_chown(path, 0), ...
|
#manila/volume/.py: utils.temporary_chown(path, 0), ...
|
||||||
chown: CommandFilter, chown, root
|
chown: CommandFilter, chown, root
|
||||||
|
|
||||||
|
@ -1505,7 +1505,7 @@ msgstr ""
|
|||||||
msgid "Error in gluster volume set: %s"
|
msgid "Error in gluster volume set: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: manila/share/drivers/glusterfs.py:144 manila/share/drivers/lvm.py:306
|
#: manila/share/drivers/glusterfs.py:144
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%s is already mounted"
|
msgid "%s is already mounted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1524,39 +1524,6 @@ msgstr ""
|
|||||||
msgid "GlusterFS control mount is not available"
|
msgid "GlusterFS control mount is not available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:85
|
|
||||||
#, python-format
|
|
||||||
msgid "share volume group %s doesn't exist"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:89
|
|
||||||
msgid "share_export_ip isn't specified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:145
|
|
||||||
#, python-format
|
|
||||||
msgid "Error deleting volume: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:147
|
|
||||||
#, python-format
|
|
||||||
msgid "Volume not found: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:186
|
|
||||||
#, python-format
|
|
||||||
msgid "Error retrieving volume status: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:561
|
|
||||||
#, python-format
|
|
||||||
msgid "Share section %r already defined."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/lvm.py:591
|
|
||||||
msgid "only ip access type allowed"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: manila/share/drivers/service_instance.py:130
|
#: manila/share/drivers/service_instance.py:130
|
||||||
msgid "Service instance user is not specified"
|
msgid "Service instance user is not specified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -24,14 +24,14 @@ This module allows support for setting configurations either from default
|
|||||||
or from a particular CONF group, to be able to set multiple configurations
|
or from a particular CONF group, to be able to set multiple configurations
|
||||||
for a given set of values.
|
for a given set of values.
|
||||||
|
|
||||||
For instance, two lvm configurations can be set by naming them in groups as
|
For instance, two generic configurations can be set by naming them in groups as
|
||||||
|
|
||||||
[lvm1]
|
[generic1]
|
||||||
volume_group=lvm-group-1
|
share_backend_name=generic-backend-1
|
||||||
...
|
...
|
||||||
|
|
||||||
[lvm2]
|
[generic2]
|
||||||
volume_group=lvm-group-2
|
share_backend_name=generic-backend-2
|
||||||
...
|
...
|
||||||
|
|
||||||
And the configuration group name will be passed in so that all calls to
|
And the configuration group name will be passed in so that all calls to
|
||||||
|
@ -1,620 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
LVM Driver for shares.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
from manila import exception
|
|
||||||
from manila.openstack.common import importutils
|
|
||||||
from manila.openstack.common import log as logging
|
|
||||||
from manila.share import driver
|
|
||||||
from manila import utils
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
share_opts = [
|
|
||||||
cfg.StrOpt('share_export_root',
|
|
||||||
default='$state_path/mnt',
|
|
||||||
help='Base folder where exported shares are located'),
|
|
||||||
cfg.StrOpt('share_export_ip',
|
|
||||||
default=None,
|
|
||||||
help='IP to be added to export string'),
|
|
||||||
cfg.StrOpt('smb_config_path',
|
|
||||||
default='$state_path/smb.conf',
|
|
||||||
help="Path to smb config"),
|
|
||||||
cfg.IntOpt('share_lvm_mirrors',
|
|
||||||
default=0,
|
|
||||||
help='If set, create lvms with multiple mirrors. Note that '
|
|
||||||
'this requires lvm_mirrors + 2 pvs with available space'),
|
|
||||||
cfg.StrOpt('share_volume_group',
|
|
||||||
default='stack-shares',
|
|
||||||
help='Name for the VG that will contain exported shares'),
|
|
||||||
cfg.ListOpt('share_lvm_helpers',
|
|
||||||
default=[
|
|
||||||
'CIFS=manila.share.drivers.lvm.CIFSNetConfHelper',
|
|
||||||
'NFS=manila.share.drivers.lvm.NFSHelper',
|
|
||||||
],
|
|
||||||
help='Specify list of share export helpers.'),
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(share_opts)
|
|
||||||
|
|
||||||
|
|
||||||
class LVMShareDriver(driver.ExecuteMixin, driver.ShareDriver):
|
|
||||||
"""Executes commands relating to Shares."""
|
|
||||||
|
|
||||||
def __init__(self, db, *args, **kwargs):
|
|
||||||
"""Do initialization."""
|
|
||||||
super(LVMShareDriver, self).__init__(*args, **kwargs)
|
|
||||||
self.db = db
|
|
||||||
self.configuration.append_config_values(share_opts)
|
|
||||||
self._helpers = None
|
|
||||||
self.backend_name = self.configuration.safe_get(
|
|
||||||
'share_backend_name') or 'LVM'
|
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
|
||||||
"""Returns an error if prerequisites aren't met."""
|
|
||||||
out, err = self._execute('vgs', '--noheadings', '-o', 'name',
|
|
||||||
run_as_root=True)
|
|
||||||
volume_groups = out.split()
|
|
||||||
if self.configuration.share_volume_group not in volume_groups:
|
|
||||||
msg = (_("share volume group %s doesn't exist")
|
|
||||||
% self.configuration.share_volume_group)
|
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
|
||||||
if not self.configuration.share_export_ip:
|
|
||||||
msg = (_("share_export_ip isn't specified"))
|
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
|
||||||
|
|
||||||
def do_setup(self, context):
|
|
||||||
"""Any initialization the volume driver does while starting."""
|
|
||||||
super(LVMShareDriver, self).do_setup(context)
|
|
||||||
self._setup_helpers()
|
|
||||||
for helper in self._helpers.values():
|
|
||||||
helper.init()
|
|
||||||
|
|
||||||
def _setup_helpers(self):
|
|
||||||
"""Initializes protocol-specific NAS drivers."""
|
|
||||||
self._helpers = {}
|
|
||||||
for helper_str in self.configuration.share_lvm_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.configuration)
|
|
||||||
|
|
||||||
def _local_path(self, share):
|
|
||||||
# NOTE(vish): stops deprecation warning
|
|
||||||
escaped_group = \
|
|
||||||
self.configuration.share_volume_group.replace('-', '--')
|
|
||||||
escaped_name = share['name'].replace('-', '--')
|
|
||||||
return "/dev/mapper/%s-%s" % (escaped_group, escaped_name)
|
|
||||||
|
|
||||||
def _allocate_container(self, share):
|
|
||||||
sizestr = '%sG' % share['size']
|
|
||||||
cmd = ['lvcreate', '-L', sizestr, '-n', share['name'],
|
|
||||||
self.configuration.share_volume_group]
|
|
||||||
if self.configuration.share_lvm_mirrors:
|
|
||||||
cmd += ['-m', self.configuration.share_lvm_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', str(rsize)]
|
|
||||||
|
|
||||||
self._try_execute(*cmd, run_as_root=True)
|
|
||||||
device_name = self._local_path(share)
|
|
||||||
self._execute('mkfs.ext4', device_name, run_as_root=True)
|
|
||||||
|
|
||||||
def _deallocate_container(self, share_name):
|
|
||||||
"""Deletes a logical volume for share."""
|
|
||||||
# zero out old volumes to prevent data leaking between users
|
|
||||||
# TODO(ja): reclaiming space should be done lazy and low priority
|
|
||||||
try:
|
|
||||||
self._try_execute('lvremove', '-f', "%s/%s" %
|
|
||||||
(self.configuration.share_volume_group,
|
|
||||||
share_name),
|
|
||||||
run_as_root=True)
|
|
||||||
except exception.ProcessExecutionError as exc:
|
|
||||||
if "not found" not in exc.stderr:
|
|
||||||
LOG.error(_("Error deleting volume: %s") % exc.stderr)
|
|
||||||
raise
|
|
||||||
LOG.error(_("Volume not found: %s") % exc.stderr)
|
|
||||||
|
|
||||||
def get_share_stats(self, refresh=False):
|
|
||||||
"""Get share status.
|
|
||||||
|
|
||||||
If 'refresh' is True, run update the stats first.
|
|
||||||
"""
|
|
||||||
if refresh:
|
|
||||||
self._update_share_status()
|
|
||||||
|
|
||||||
return self._stats
|
|
||||||
|
|
||||||
def _update_share_status(self):
|
|
||||||
"""Retrieve status info from share volume group."""
|
|
||||||
|
|
||||||
LOG.debug("Updating share status")
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
# Note(zhiteng): These information are driver/backend specific,
|
|
||||||
# each driver may define these values in its own config options
|
|
||||||
# or fetch from driver specific configuration file.
|
|
||||||
data["share_backend_name"] = self.backend_name
|
|
||||||
data["vendor_name"] = 'Open Source'
|
|
||||||
data["driver_version"] = '1.0'
|
|
||||||
# TODO(rushiagr): Pick storage_protocol from the helper used.
|
|
||||||
data["storage_protocol"] = 'NFS_CIFS'
|
|
||||||
|
|
||||||
data['total_capacity_gb'] = 0
|
|
||||||
data['free_capacity_gb'] = 0
|
|
||||||
data['reserved_percentage'] = \
|
|
||||||
self.configuration.reserved_share_percentage
|
|
||||||
data['QoS_support'] = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
out, err = self._execute('vgs', '--noheadings', '--nosuffix',
|
|
||||||
'--unit=G', '-o', 'name,size,free',
|
|
||||||
self.configuration.share_volume_group,
|
|
||||||
run_as_root=True)
|
|
||||||
except exception.ProcessExecutionError as exc:
|
|
||||||
LOG.error(_("Error retrieving volume status: %s") % exc.stderr)
|
|
||||||
out = False
|
|
||||||
|
|
||||||
if out:
|
|
||||||
share = out.split()
|
|
||||||
data['total_capacity_gb'] = float(share[1])
|
|
||||||
data['free_capacity_gb'] = float(share[2])
|
|
||||||
|
|
||||||
self._stats = data
|
|
||||||
|
|
||||||
def create_share(self, context, share, share_server=None):
|
|
||||||
self._allocate_container(share)
|
|
||||||
# create file system
|
|
||||||
device_name = self._local_path(share)
|
|
||||||
mount_path = self._get_mount_path(share)
|
|
||||||
location = self._get_helper(share).create_export(mount_path,
|
|
||||||
share['name'])
|
|
||||||
self._mount_device(share, device_name)
|
|
||||||
# TODO(rushiagr): what is the provider_location? realy needed?
|
|
||||||
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._local_path(snapshot)
|
|
||||||
self._copy_volume(device_name, self._local_path(share),
|
|
||||||
snapshot['share_size'])
|
|
||||||
mount_path = self._get_mount_path(share)
|
|
||||||
location = self._get_helper(share).create_export(mount_path,
|
|
||||||
share['name'])
|
|
||||||
self._mount_device(share, device_name)
|
|
||||||
# TODO(rushiagr): what is the provider_location? realy needed?
|
|
||||||
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 str(exc):
|
|
||||||
raise exception.ShareIsBusy(share_name=share['name'])
|
|
||||||
else:
|
|
||||||
LOG.info('Unable to umount: %s', exc)
|
|
||||||
# remove dir
|
|
||||||
try:
|
|
||||||
os.rmdir(mount_path)
|
|
||||||
except OSError:
|
|
||||||
LOG.info('Unable to delete %s', mount_path)
|
|
||||||
|
|
||||||
def create_snapshot(self, context, snapshot, share_server=None):
|
|
||||||
"""Creates a snapshot."""
|
|
||||||
orig_lv_name = "%s/%s" % (self.configuration.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 ensure_share(self, ctx, share, share_server=None):
|
|
||||||
"""Ensure that storage are mounted and exported."""
|
|
||||||
device_name = self._local_path(share)
|
|
||||||
location = self._mount_device(share, device_name)
|
|
||||||
self._get_helper(share).create_export(location, share['name'],
|
|
||||||
recreate=True)
|
|
||||||
|
|
||||||
def _delete_share(self, ctx, share):
|
|
||||||
"""Delete a share."""
|
|
||||||
try:
|
|
||||||
location = self._get_mount_path(share)
|
|
||||||
self._get_helper(share).remove_export(location, share['name'])
|
|
||||||
except exception.ProcessExecutionError:
|
|
||||||
LOG.info("Can't remove share %r" % share['id'])
|
|
||||||
except exception.InvalidShare as exc:
|
|
||||||
LOG.info(exc.message)
|
|
||||||
|
|
||||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
|
||||||
"""Deletes a snapshot."""
|
|
||||||
self._deallocate_container(snapshot['name'])
|
|
||||||
|
|
||||||
def allow_access(self, ctx, share, access, share_server=None):
|
|
||||||
"""Allow access to the share."""
|
|
||||||
location = self._get_mount_path(share)
|
|
||||||
self._get_helper(share).allow_access(location, share['name'],
|
|
||||||
access['access_type'],
|
|
||||||
access['access_to'])
|
|
||||||
|
|
||||||
def deny_access(self, ctx, share, access, share_server=None):
|
|
||||||
"""Allow access to the share."""
|
|
||||||
location = self._get_mount_path(share)
|
|
||||||
self._get_helper(share).deny_access(location, share['name'],
|
|
||||||
access['access_type'],
|
|
||||||
access['access_to'])
|
|
||||||
|
|
||||||
def _get_helper(self, share):
|
|
||||||
if share['share_proto'].startswith('NFS'):
|
|
||||||
return self._helpers['NFS']
|
|
||||||
elif share['share_proto'].startswith('CIFS'):
|
|
||||||
return self._helpers['CIFS']
|
|
||||||
else:
|
|
||||||
raise exception.InvalidShare(reason='Wrong share type')
|
|
||||||
|
|
||||||
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 as exc:
|
|
||||||
if 'already mounted' in exc.stderr:
|
|
||||||
LOG.warn(_("%s is already mounted"), device_name)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
return mount_path
|
|
||||||
|
|
||||||
def _get_mount_path(self, share):
|
|
||||||
"""Returns path where share is mounted."""
|
|
||||||
return os.path.join(self.configuration.share_export_root,
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class NASHelperBase(object):
|
|
||||||
"""Interface to work with share."""
|
|
||||||
|
|
||||||
def __init__(self, execute, config_object):
|
|
||||||
self.configuration = config_object
|
|
||||||
self._execute = execute
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_export(self, local_path, share_name, recreate=False):
|
|
||||||
"""Create new export, delete old one if exists."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def remove_export(self, local_path, share_name):
|
|
||||||
"""Remove export."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def allow_access(self, local_path, share_name, access_type, access):
|
|
||||||
"""Allow access to the host."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def deny_access(self, local_path, share_name, access_type, access,
|
|
||||||
force=False):
|
|
||||||
"""Deny access to the host."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class NFSHelper(NASHelperBase):
|
|
||||||
"""Interface to work with share."""
|
|
||||||
|
|
||||||
def __init__(self, execute, config_object):
|
|
||||||
super(NFSHelper, self).__init__(execute, config_object)
|
|
||||||
try:
|
|
||||||
self._execute('exportfs', check_exit_code=True,
|
|
||||||
run_as_root=True)
|
|
||||||
except exception.ProcessExecutionError:
|
|
||||||
raise exception.Error('NFS server not found')
|
|
||||||
|
|
||||||
def create_export(self, local_path, share_name, recreate=False):
|
|
||||||
"""Create new export, delete old one if exists."""
|
|
||||||
return ':'.join([self.configuration.share_export_ip, local_path])
|
|
||||||
|
|
||||||
def remove_export(self, local_path, share_name):
|
|
||||||
"""Remove export."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def allow_access(self, local_path, share_name, access_type, access):
|
|
||||||
"""Allow access to the host"""
|
|
||||||
if access_type != 'ip':
|
|
||||||
reason = 'only ip access type allowed'
|
|
||||||
raise exception.InvalidShareAccess(reason)
|
|
||||||
# check if presents in export
|
|
||||||
out, _ = self._execute('exportfs', run_as_root=True)
|
|
||||||
out = re.search(re.escape(local_path) + '[\s\n]*' + re.escape(access),
|
|
||||||
out)
|
|
||||||
if out is not None:
|
|
||||||
raise exception.ShareAccessExists(access_type=access_type,
|
|
||||||
access=access)
|
|
||||||
|
|
||||||
self._execute('exportfs', '-o', 'rw,no_subtree_check',
|
|
||||||
':'.join([access, local_path]), run_as_root=True,
|
|
||||||
check_exit_code=True)
|
|
||||||
|
|
||||||
def deny_access(self, local_path, share_name, access_type, access,
|
|
||||||
force=False):
|
|
||||||
"""Deny access to the host."""
|
|
||||||
self._execute('exportfs', '-u', ':'.join([access, local_path]),
|
|
||||||
run_as_root=True, check_exit_code=False)
|
|
||||||
|
|
||||||
|
|
||||||
class CIFSHelper(NASHelperBase):
|
|
||||||
"""Class provides functionality to operate with cifs shares"""
|
|
||||||
|
|
||||||
def __init__(self, execute, config_object):
|
|
||||||
"""Store executor and configuration path."""
|
|
||||||
super(CIFSHelper, self).__init__(execute, config_object)
|
|
||||||
self.config = self.configuration.smb_config_path
|
|
||||||
self.test_config = "%s_" % (self.config,)
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
"""Initialize environment."""
|
|
||||||
self._recreate_config()
|
|
||||||
self._ensure_daemon_started()
|
|
||||||
|
|
||||||
def create_export(self, local_path, share_name, recreate=False):
|
|
||||||
"""Create new export, delete old one if exists."""
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
parser.read(self.config)
|
|
||||||
# delete old one
|
|
||||||
if parser.has_section(share_name):
|
|
||||||
if recreate:
|
|
||||||
parser.remove_section(share_name)
|
|
||||||
else:
|
|
||||||
raise exception.Error('Section exists')
|
|
||||||
# Create new one
|
|
||||||
parser.add_section(share_name)
|
|
||||||
parser.set(share_name, 'path', local_path)
|
|
||||||
parser.set(share_name, 'browseable', 'yes')
|
|
||||||
parser.set(share_name, 'guest ok', 'yes')
|
|
||||||
parser.set(share_name, 'read only', 'no')
|
|
||||||
parser.set(share_name, 'writable', 'yes')
|
|
||||||
parser.set(share_name, 'create mask', '0755')
|
|
||||||
parser.set(share_name, 'hosts deny', '0.0.0.0/0') # denying all ips
|
|
||||||
parser.set(share_name, 'hosts allow', '127.0.0.1')
|
|
||||||
# NOTE(rushiagr): ensure that local_path dir is existing
|
|
||||||
if not os.path.exists(local_path):
|
|
||||||
os.makedirs(local_path)
|
|
||||||
self._execute('chown', 'nobody', '-R', local_path, run_as_root=True)
|
|
||||||
self._update_config(parser)
|
|
||||||
return '//%s/%s' % (self.configuration.share_export_ip, share_name)
|
|
||||||
|
|
||||||
def remove_export(self, local_path, share_name):
|
|
||||||
"""Remove export."""
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
parser.read(self.config)
|
|
||||||
# delete old one
|
|
||||||
if parser.has_section(share_name):
|
|
||||||
parser.remove_section(share_name)
|
|
||||||
self._update_config(parser)
|
|
||||||
self._execute('smbcontrol', 'all', 'close-share', share_name,
|
|
||||||
run_as_root=True)
|
|
||||||
|
|
||||||
def allow_access(self, local_path, share_name, access_type, access):
|
|
||||||
"""Allow access to the host."""
|
|
||||||
if access_type != 'ip':
|
|
||||||
reason = 'only ip access type allowed'
|
|
||||||
raise exception.InvalidShareAccess(reason)
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
parser.read(self.config)
|
|
||||||
|
|
||||||
hosts = parser.get(share_name, 'hosts allow')
|
|
||||||
if access in hosts.split():
|
|
||||||
raise exception.ShareAccessExists(access_type=access_type,
|
|
||||||
access=access)
|
|
||||||
hosts += ' %s' % (access,)
|
|
||||||
parser.set(share_name, 'hosts allow', hosts)
|
|
||||||
self._update_config(parser)
|
|
||||||
|
|
||||||
def deny_access(self, local_path, share_name, access_type, access,
|
|
||||||
force=False):
|
|
||||||
"""Deny access to the host."""
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
try:
|
|
||||||
parser.read(self.config)
|
|
||||||
hosts = parser.get(share_name, 'hosts allow')
|
|
||||||
hosts = hosts.replace(' %s' % (access,), '', 1)
|
|
||||||
parser.set(share_name, 'hosts allow', hosts)
|
|
||||||
self._update_config(parser)
|
|
||||||
except ConfigParser.NoSectionError:
|
|
||||||
if not force:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _ensure_daemon_started(self):
|
|
||||||
"""
|
|
||||||
FYI: smbd starts at least two processes.
|
|
||||||
"""
|
|
||||||
out, _ = self._execute(*'ps -C smbd -o args='.split(),
|
|
||||||
check_exit_code=False)
|
|
||||||
processes = [process.strip() for process in out.split('\n')
|
|
||||||
if process.strip()]
|
|
||||||
|
|
||||||
cmd = 'smbd -s %s -D' % (self.config,)
|
|
||||||
|
|
||||||
running = False
|
|
||||||
for process in processes:
|
|
||||||
if not process.endswith(cmd):
|
|
||||||
# alternatively exit
|
|
||||||
raise exception.Error('smbd already started with wrong config')
|
|
||||||
running = True
|
|
||||||
|
|
||||||
if not running:
|
|
||||||
self._execute(*cmd.split(), run_as_root=True)
|
|
||||||
|
|
||||||
def _recreate_config(self):
|
|
||||||
"""create new SAMBA configuration file."""
|
|
||||||
if os.path.exists(self.config):
|
|
||||||
os.unlink(self.config)
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
parser.add_section('global')
|
|
||||||
parser.set('global', 'security', 'user')
|
|
||||||
parser.set('global', 'server string', '%h server (Samba, Openstack)')
|
|
||||||
|
|
||||||
self._update_config(parser, restart=False)
|
|
||||||
|
|
||||||
def _update_config(self, parser, restart=True):
|
|
||||||
"""Check if new configuration is correct and save it."""
|
|
||||||
# Check that configuration is correct
|
|
||||||
with open(self.test_config, 'w') as fp:
|
|
||||||
parser.write(fp)
|
|
||||||
self._execute('testparm', '-s', self.test_config,
|
|
||||||
check_exit_code=True)
|
|
||||||
# save it
|
|
||||||
with open(self.config, 'w') as fp:
|
|
||||||
parser.write(fp)
|
|
||||||
# restart daemon if necessary
|
|
||||||
if restart:
|
|
||||||
self._execute(*'pkill -HUP smbd'.split(), run_as_root=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CIFSNetConfHelper(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. There are two ways
|
|
||||||
to done that, one of them is to add specific parameter in the
|
|
||||||
global configuration section at smb.conf:
|
|
||||||
|
|
||||||
[global]
|
|
||||||
include = registry
|
|
||||||
|
|
||||||
For more inforation see smb.conf(5).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def create_export(self, local_path, share_name, recreate=False):
|
|
||||||
"""Create share at samba server."""
|
|
||||||
create_cmd = ('net', 'conf', 'addshare', share_name, local_path,
|
|
||||||
'writeable=y', 'guest_ok=y')
|
|
||||||
try:
|
|
||||||
self._execute(*create_cmd, run_as_root=True)
|
|
||||||
except exception.ProcessExecutionError as e:
|
|
||||||
if 'already exists' in e.stderr:
|
|
||||||
if recreate:
|
|
||||||
self._execute('net', 'conf', 'delshare', share_name,
|
|
||||||
run_as_root=True)
|
|
||||||
self._execute(*create_cmd, run_as_root=True)
|
|
||||||
else:
|
|
||||||
msg = _('Share section %r already defined.') % (share_name)
|
|
||||||
raise exception.ShareBackendException(msg=msg)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
parameters = {
|
|
||||||
'browseable': 'yes',
|
|
||||||
'create mask': '0755',
|
|
||||||
'hosts deny': '0.0.0.0/0', # deny all
|
|
||||||
'hosts allow': '127.0.0.1',
|
|
||||||
}
|
|
||||||
for name, value in parameters.items():
|
|
||||||
self._execute('net', 'conf', 'setparm', share_name, name, value,
|
|
||||||
run_as_root=True)
|
|
||||||
return '//%s/%s' % (self.configuration.share_export_ip, share_name)
|
|
||||||
|
|
||||||
def remove_export(self, local_path, share_name):
|
|
||||||
"""Remove share definition from samba server."""
|
|
||||||
try:
|
|
||||||
self._execute('net', 'conf', 'delshare', share_name,
|
|
||||||
run_as_root=True)
|
|
||||||
except exception.ProcessExecutionError as e:
|
|
||||||
if 'SBC_ERR_NO_SUCH_SERVICE' not in e.stderr:
|
|
||||||
raise
|
|
||||||
self._execute('smbcontrol', 'all', 'close-share', share_name,
|
|
||||||
run_as_root=True)
|
|
||||||
|
|
||||||
def allow_access(self, local_path, share_name, access_type, access):
|
|
||||||
"""Add to allow hosts additional access rule."""
|
|
||||||
if access_type != 'ip':
|
|
||||||
reason = _('only ip access type allowed')
|
|
||||||
raise exception.InvalidShareAccess(reason=reason)
|
|
||||||
|
|
||||||
hosts = self._get_allow_hosts(share_name)
|
|
||||||
if access in hosts:
|
|
||||||
raise exception.ShareAccessExists(access_type=access_type,
|
|
||||||
access=access)
|
|
||||||
hosts.append(access)
|
|
||||||
self._set_allow_hosts(hosts, share_name)
|
|
||||||
|
|
||||||
def deny_access(self, local_path, share_name, access_type, access,
|
|
||||||
force=False):
|
|
||||||
"""Remove from allow hosts permit rule."""
|
|
||||||
try:
|
|
||||||
hosts = self._get_allow_hosts(share_name)
|
|
||||||
hosts.remove(access)
|
|
||||||
self._set_allow_hosts(hosts, share_name)
|
|
||||||
except exception.ProcessExecutionError as e:
|
|
||||||
if not ('does not exist' in e.stdout and force):
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _get_allow_hosts(self, share_name):
|
|
||||||
(out, _) = self._execute('net', 'conf', 'getparm', share_name,
|
|
||||||
'hosts allow', run_as_root=True)
|
|
||||||
return out.split()
|
|
||||||
|
|
||||||
def _set_allow_hosts(self, hosts, share_name):
|
|
||||||
value = ' '.join(hosts)
|
|
||||||
self._execute('net', 'conf', 'setparm', share_name, 'hosts allow',
|
|
||||||
value, run_as_root=True)
|
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
**Related Flags**
|
**Related Flags**
|
||||||
|
|
||||||
:share_driver: Used by :class:`ShareManager`. Defaults to
|
:share_driver: Used by :class:`ShareManager`.
|
||||||
:class:`manila.share.drivers.lvm.LVMShareDriver`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from manila.common import constants
|
from manila.common import constants
|
||||||
@ -39,7 +38,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
share_manager_opts = [
|
share_manager_opts = [
|
||||||
cfg.StrOpt('share_driver',
|
cfg.StrOpt('share_driver',
|
||||||
default='manila.share.drivers.lvm.LVMShareDriver',
|
default='manila.share.drivers.generic.GenericShareDriver',
|
||||||
help='Driver to use for share creation'),
|
help='Driver to use for share creation'),
|
||||||
cfg.BoolOpt('delete_share_server_with_last_share',
|
cfg.BoolOpt('delete_share_server_with_last_share',
|
||||||
default=False,
|
default=False,
|
||||||
|
@ -31,7 +31,6 @@ def set_defaults(conf):
|
|||||||
_safe_set_of_opts(conf, 'connection', "sqlite://", group='database')
|
_safe_set_of_opts(conf, 'connection', "sqlite://", group='database')
|
||||||
_safe_set_of_opts(conf, 'sqlite_synchronous', False)
|
_safe_set_of_opts(conf, 'sqlite_synchronous', False)
|
||||||
_safe_set_of_opts(conf, 'policy_file', _POLICY_PATH)
|
_safe_set_of_opts(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')
|
_safe_set_of_opts(conf, 'service_instance_user', 'fake_user')
|
||||||
_safe_set_of_opts(conf, 'api_paste_config', _API_PASTE_PATH)
|
_safe_set_of_opts(conf, 'api_paste_config', _API_PASTE_PATH)
|
||||||
_safe_set_of_opts(conf, 'share_driver',
|
_safe_set_of_opts(conf, 'share_driver',
|
||||||
|
@ -13,17 +13,21 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from manila.openstack.common import log as logging
|
from manila.openstack.common import log as logging
|
||||||
from manila.share.drivers import lvm
|
from manila.share.drivers import generic
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FakeShareDriver(lvm.LVMShareDriver):
|
class FakeShareDriver(generic.GenericShareDriver):
|
||||||
"""Logs calls instead of executing."""
|
"""Logs calls instead of executing."""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(FakeShareDriver, self).__init__(execute=self.fake_execute,
|
super(FakeShareDriver, self).__init__(execute=self.fake_execute,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
def do_setup(self, context):
|
||||||
|
"""Fake setup of Generic driver."""
|
||||||
|
pass
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
def check_for_setup_error(self):
|
||||||
"""No setup necessary in fake mode."""
|
"""No setup necessary in fake mode."""
|
||||||
pass
|
pass
|
||||||
|
@ -36,7 +36,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
fake_context = context.RequestContext('user', 'project')
|
fake_context = context.RequestContext('user', 'project')
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
'share_type': {'name': 'LVM_NFS'},
|
'share_type': {'name': 'NFS'},
|
||||||
'share_id': ['fake-id1'],
|
'share_id': ['fake-id1'],
|
||||||
}
|
}
|
||||||
self.assertRaises(exception.NoValidHost, sched.schedule_create_share,
|
self.assertRaises(exception.NoValidHost, sched.schedule_create_share,
|
||||||
@ -60,7 +60,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
fake_context = context.RequestContext('user', 'project')
|
fake_context = context.RequestContext('user', 'project')
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
'share_type': {'name': 'LVM_NFS'},
|
'share_type': {'name': 'NFS'},
|
||||||
'share_id': ['fake-id1'],
|
'share_id': ['fake-id1'],
|
||||||
}
|
}
|
||||||
self.assertRaises(exception.NoValidHost, sched.schedule_create_share,
|
self.assertRaises(exception.NoValidHost, sched.schedule_create_share,
|
||||||
@ -77,7 +77,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
is_admin=True)
|
is_admin=True)
|
||||||
fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic)
|
fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic)
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'share_type': {'name': 'LVM_NFS'},
|
'share_type': {'name': 'NFS'},
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
}
|
}
|
||||||
weighed_host = sched._schedule_share(fake_context, request_spec, {})
|
weighed_host = sched._schedule_share(fake_context, request_spec, {})
|
||||||
@ -99,7 +99,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
self.flags(scheduler_max_attempts=1)
|
self.flags(scheduler_max_attempts=1)
|
||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'volume_type': {'name': 'LVM_iSCSI'},
|
'volume_type': {'name': 'iSCSI'},
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
}
|
}
|
||||||
filter_properties = {}
|
filter_properties = {}
|
||||||
@ -113,7 +113,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
self.flags(scheduler_max_attempts=2)
|
self.flags(scheduler_max_attempts=2)
|
||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'volume_type': {'name': 'LVM_iSCSI'},
|
'volume_type': {'name': 'iSCSI'},
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
}
|
}
|
||||||
filter_properties = {}
|
filter_properties = {}
|
||||||
@ -127,7 +127,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
self.flags(scheduler_max_attempts=2)
|
self.flags(scheduler_max_attempts=2)
|
||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'volume_type': {'name': 'LVM_iSCSI'},
|
'volume_type': {'name': 'iSCSI'},
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
}
|
}
|
||||||
retry = dict(num_attempts=1)
|
retry = dict(num_attempts=1)
|
||||||
@ -142,7 +142,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
self.flags(scheduler_max_attempts=2)
|
self.flags(scheduler_max_attempts=2)
|
||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'volume_type': {'name': 'LVM_iSCSI'},
|
'volume_type': {'name': 'iSCSI'},
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
}
|
}
|
||||||
retry = dict(num_attempts=2)
|
retry = dict(num_attempts=2)
|
||||||
|
@ -1,682 +0,0 @@
|
|||||||
# 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 NFS driver module."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from manila import context
|
|
||||||
from manila.db.sqlalchemy import models
|
|
||||||
from manila import exception
|
|
||||||
from manila.openstack.common import importutils
|
|
||||||
from manila.openstack.common import log as logging
|
|
||||||
from manila.share.configuration 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_size': 1,
|
|
||||||
'share_proto': 'NFS',
|
|
||||||
'export_location': '127.0.0.1:/mnt/nfs/volume-00002',
|
|
||||||
}
|
|
||||||
snapshot.update(kwargs)
|
|
||||||
return db_fakes.FakeModel(snapshot)
|
|
||||||
|
|
||||||
|
|
||||||
def fake_access(**kwargs):
|
|
||||||
access = {
|
|
||||||
'id': 'fakeaccid',
|
|
||||||
'access_type': 'ip',
|
|
||||||
'access_to': '10.0.0.2',
|
|
||||||
'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.stubs)
|
|
||||||
self._execute = fake_utils.fake_execute
|
|
||||||
self._context = context.get_admin_context()
|
|
||||||
|
|
||||||
CONF.set_default('share_volume_group', 'fakevg')
|
|
||||||
CONF.set_default('share_export_ip', '10.0.0.1')
|
|
||||||
|
|
||||||
self._helper_cifs = mock.Mock()
|
|
||||||
self._helper_nfs = mock.Mock()
|
|
||||||
self.fake_conf = 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,
|
|
||||||
execute=self._execute,
|
|
||||||
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()
|
|
||||||
|
|
||||||
# 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('share_lvm_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 = [
|
|
||||||
'vgs --noheadings -o name',
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
|
||||||
ret = self._driver.check_for_setup_error()
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
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([('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([('vgs --noheadings -o name',
|
|
||||||
exec_runner)])
|
|
||||||
CONF.set_default('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('share_volume_group', 'fake_vg')
|
|
||||||
ret = self._driver._local_path(share)
|
|
||||||
self.assertEqual(ret, '/dev/mapper/fake_vg-fake_sharename')
|
|
||||||
|
|
||||||
def test_local_path_escapes(self):
|
|
||||||
share = fake_share(name='fake-sharename')
|
|
||||||
CONF.set_default('share_volume_group', 'fake-vg')
|
|
||||||
ret = self._driver._local_path(share)
|
|
||||||
self.assertEqual(ret, '/dev/mapper/fake--vg-fake--sharename')
|
|
||||||
|
|
||||||
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('share_lvm_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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, 'fakelocation')
|
|
||||||
|
|
||||||
def test_create_share_from_snapshot(self):
|
|
||||||
CONF.set_default('share_lvm_mirrors', 0)
|
|
||||||
self._driver._mount_device = mock.Mock()
|
|
||||||
mount_share = '/dev/mapper/fakevg-fakename'
|
|
||||||
mount_snapshot = '/dev/mapper/fakevg-fakesnapshotname'
|
|
||||||
self._helper_nfs.create_export.return_value = 'fakelocation'
|
|
||||||
mount_path = self._get_mount_path(self.share)
|
|
||||||
|
|
||||||
ret = self._driver.create_share_from_snapshot(self._context,
|
|
||||||
self.share,
|
|
||||||
self.snapshot,
|
|
||||||
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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
def test_create_share_mirrors(self):
|
|
||||||
|
|
||||||
share = fake_share(size='2048')
|
|
||||||
CONF.set_default('share_lvm_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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, 'fakelocation')
|
|
||||||
|
|
||||||
def test_deallocate_container(self):
|
|
||||||
expected_exec = ['lvremove -f fakevg/fakename']
|
|
||||||
ret = self._driver._deallocate_container(self.share['name'])
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
def test_get_share_stats(self):
|
|
||||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
|
||||||
return '\n fakevg 5.38 4.30\n', ''
|
|
||||||
|
|
||||||
expected_exec = [
|
|
||||||
'vgs --noheadings --nosuffix --unit=G -o name,size,free fakevg',
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
|
||||||
CONF.set_default('reserved_share_percentage', 1)
|
|
||||||
ret = self._driver.get_share_stats(refresh=True)
|
|
||||||
expected_ret = {
|
|
||||||
'share_backend_name': 'LVM',
|
|
||||||
'vendor_name': 'Open Source',
|
|
||||||
'driver_version': '1.0',
|
|
||||||
'storage_protocol': 'NFS_CIFS',
|
|
||||||
'total_capacity_gb': 5.38,
|
|
||||||
'free_capacity_gb': 4.30,
|
|
||||||
'reserved_percentage': 1,
|
|
||||||
'QoS_support': False,
|
|
||||||
}
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, expected_ret)
|
|
||||||
|
|
||||||
def test_get_share_stats_error(self):
|
|
||||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
|
||||||
raise exception.ProcessExecutionError()
|
|
||||||
|
|
||||||
expected_exec = [
|
|
||||||
'vgs --noheadings --nosuffix --unit=G -o name,size,free fakevg',
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
|
||||||
CONF.set_default('reserved_share_percentage', 1)
|
|
||||||
ret = self._driver.get_share_stats(refresh=True)
|
|
||||||
expected_ret = {
|
|
||||||
'share_backend_name': 'LVM',
|
|
||||||
'vendor_name': 'Open Source',
|
|
||||||
'driver_version': '1.0',
|
|
||||||
'storage_protocol': 'NFS_CIFS',
|
|
||||||
'total_capacity_gb': 0,
|
|
||||||
'free_capacity_gb': 0,
|
|
||||||
'reserved_percentage': 1,
|
|
||||||
'QoS_support': False,
|
|
||||||
}
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, expected_ret)
|
|
||||||
|
|
||||||
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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
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.ShareIsBusy, self._driver._remove_export,
|
|
||||||
self._context, self.share)
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
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.share_volume_group,)),
|
|
||||||
]
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
def test_ensure_share(self):
|
|
||||||
device_name = '/dev/mapper/fakevg-fakename'
|
|
||||||
location = 'fake_location'
|
|
||||||
with mock.patch.object(self._driver,
|
|
||||||
'_mount_device',
|
|
||||||
mock.Mock(return_value=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(
|
|
||||||
location, 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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
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_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(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, mount_path)
|
|
||||||
|
|
||||||
def test_mount_device_already(self):
|
|
||||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stderr='already mounted')
|
|
||||||
|
|
||||||
mount_path = self._get_mount_path(self.share)
|
|
||||||
expected_exec = [
|
|
||||||
"mkdir -p %s" % (mount_path,),
|
|
||||||
"mount fakedevice %s" % (mount_path,),
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[1], exec_runner)])
|
|
||||||
ret = self._driver._mount_device(self.share, 'fakedevice')
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
self.assertEqual(ret, mount_path)
|
|
||||||
|
|
||||||
def test_mount_device_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 = [
|
|
||||||
"mkdir -p %s" % (mount_path,),
|
|
||||||
"mount fakedevice %s" % (mount_path,),
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[1], 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,
|
|
||||||
fake_share(share_proto='FAKE'))
|
|
||||||
|
|
||||||
def _get_mount_path(self, share):
|
|
||||||
return os.path.join(CONF.share_export_root, share['name'])
|
|
||||||
|
|
||||||
|
|
||||||
class NFSHelperTestCase(test.TestCase):
|
|
||||||
"""Test case for NFS driver."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(NFSHelperTestCase, self).setUp()
|
|
||||||
fake_utils.stub_out_utils_execute(self.stubs)
|
|
||||||
CONF.set_default('share_export_ip', '127.0.0.1')
|
|
||||||
self._execute = fake_utils.fake_execute
|
|
||||||
self.fake_conf = Configuration(None)
|
|
||||||
self._helper = lvm.NFSHelper(self._execute, self.fake_conf)
|
|
||||||
fake_utils.fake_execute_clear_log()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super(NFSHelperTestCase, self).tearDown()
|
|
||||||
fake_utils.fake_execute_set_repliers([])
|
|
||||||
fake_utils.fake_execute_clear_log()
|
|
||||||
|
|
||||||
def test_failed_init(self):
|
|
||||||
self._execute = mock.Mock(side_effect=exception.ProcessExecutionError)
|
|
||||||
self.assertRaises(exception.Error, lvm.NFSHelper.__init__,
|
|
||||||
self._helper, self._execute, self.fake_conf)
|
|
||||||
|
|
||||||
def test_create_export(self):
|
|
||||||
ret = self._helper.create_export('/opt/nfs', 'volume-00001')
|
|
||||||
expected_location = '%s:/opt/nfs' % CONF.share_export_ip
|
|
||||||
self.assertEqual(ret, expected_location)
|
|
||||||
|
|
||||||
def test_remove_export(self):
|
|
||||||
self._helper.remove_export('/opt/nfs', 'volume-00001')
|
|
||||||
|
|
||||||
def test_allow_access(self):
|
|
||||||
self._helper.allow_access('/opt/nfs', 'volume-00001', 'ip', '10.0.0.*')
|
|
||||||
|
|
||||||
export_string = '10.0.0.*:/opt/nfs'
|
|
||||||
expected_exec = [
|
|
||||||
'exportfs',
|
|
||||||
'exportfs -o rw,no_subtree_check %s' % export_string,
|
|
||||||
]
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
def test_allow_access_no_ip(self):
|
|
||||||
self.assertRaises(exception.InvalidShareAccess,
|
|
||||||
self._helper.allow_access, '/opt/nfs', 'share0',
|
|
||||||
'fake', 'fakerule')
|
|
||||||
|
|
||||||
def test_allow_access_negative(self):
|
|
||||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
|
||||||
return '\n/opt/nfs\t\t10.0.0.*\n', ''
|
|
||||||
|
|
||||||
fake_utils.fake_execute_set_repliers([('exportfs', exec_runner)])
|
|
||||||
self.assertRaises(exception.ShareAccessExists,
|
|
||||||
self._helper.allow_access,
|
|
||||||
'/opt/nfs', 'volume-00001', 'ip', '10.0.0.*')
|
|
||||||
|
|
||||||
def test_deny_access(self):
|
|
||||||
self._helper.deny_access('/opt/nfs', 'volume-00001', 'ip', '10.0.0.*')
|
|
||||||
export_string = '10.0.0.*:/opt/nfs'
|
|
||||||
expected_exec = ['exportfs -u %s' % export_string]
|
|
||||||
self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
|
|
||||||
|
|
||||||
|
|
||||||
class CIFSNetConfHelperTestCase(test.TestCase):
|
|
||||||
"""Test case for CIFS driver with net conf management."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(CIFSNetConfHelperTestCase, self).setUp()
|
|
||||||
fake_utils.stub_out_utils_execute(self.stubs)
|
|
||||||
CONF.set_default('share_export_ip', '127.0.0.1')
|
|
||||||
self.share = fake_share()
|
|
||||||
self._execute = fake_utils.fake_execute
|
|
||||||
self.fake_conf = Configuration(None)
|
|
||||||
self._helper = lvm.CIFSNetConfHelper(self._execute, self.fake_conf)
|
|
||||||
fake_utils.fake_execute_clear_log()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super(CIFSNetConfHelperTestCase, self).tearDown()
|
|
||||||
fake_utils.fake_execute_set_repliers([])
|
|
||||||
fake_utils.fake_execute_clear_log()
|
|
||||||
|
|
||||||
def test_create_export(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._execute = mock.Mock()
|
|
||||||
parameters = {
|
|
||||||
'browseable': 'yes',
|
|
||||||
'create mask': '0755',
|
|
||||||
'hosts deny': '0.0.0.0/0',
|
|
||||||
'hosts allow': '127.0.0.1',
|
|
||||||
}
|
|
||||||
ret = self._helper.create_export('fakelocalpath', share_name)
|
|
||||||
calls = [mock.call('net', 'conf', 'addshare', share_name,
|
|
||||||
'fakelocalpath', 'writeable=y', 'guest_ok=y',
|
|
||||||
run_as_root=True)]
|
|
||||||
for name, value in parameters.items():
|
|
||||||
calls.append(mock.call('net', 'conf', 'setparm', share_name, name,
|
|
||||||
value, run_as_root=True))
|
|
||||||
self._helper._execute.assert_has_calls(calls)
|
|
||||||
expected_ret = "//127.0.0.1/%s" % (share_name,)
|
|
||||||
self.assertEqual(ret, expected_ret)
|
|
||||||
|
|
||||||
def test_create_export_already_exists(self):
|
|
||||||
def exec_runner(*ignore_args, **ignore_kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stderr='already exists')
|
|
||||||
|
|
||||||
expected_exec = [
|
|
||||||
"net conf addshare %s %s writeable=y guest_ok=y" % (
|
|
||||||
self.share['name'],
|
|
||||||
'fakelocalpath',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
|
|
||||||
self.assertRaises(exception.ShareBackendException,
|
|
||||||
self._helper.create_export, 'fakelocalpath',
|
|
||||||
self.share['name'])
|
|
||||||
|
|
||||||
def test_create_export_recreate(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error():
|
|
||||||
raise exception.ProcessExecutionError(stderr="already exists")
|
|
||||||
execute_return_values = [raise_exec_error, '']
|
|
||||||
parameters = {
|
|
||||||
'browseable': 'yes',
|
|
||||||
'create mask': '0755',
|
|
||||||
'hosts deny': '0.0.0.0/0',
|
|
||||||
'hosts allow': '127.0.0.1',
|
|
||||||
}
|
|
||||||
execute_return_values.extend([''] * len(parameters))
|
|
||||||
self._helper._execute = mock.Mock(side_effect=execute_return_values)
|
|
||||||
ret = self._helper.create_export('fakelocalpath', share_name,
|
|
||||||
recreate=True)
|
|
||||||
expected_ret = "//127.0.0.1/%s" % (share_name,)
|
|
||||||
calls = [mock.call('net', 'conf', 'setparm', share_name, name,
|
|
||||||
value, run_as_root=True) for
|
|
||||||
name, value in parameters.items()]
|
|
||||||
self._helper._execute.assert_has_calls(calls)
|
|
||||||
self.assertEqual(ret, expected_ret)
|
|
||||||
|
|
||||||
def test_create_export_error(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error(*args, **kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stderr="fake_stderr")
|
|
||||||
|
|
||||||
self._helper._execute = mock.Mock(
|
|
||||||
side_effect=raise_exec_error)
|
|
||||||
self.assertRaises(exception.ProcessExecutionError,
|
|
||||||
self._helper.create_export, 'fakelocalpath',
|
|
||||||
share_name)
|
|
||||||
|
|
||||||
def test_remove_export(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._execute = mock.Mock()
|
|
||||||
self._helper.remove_export('fakelocalpath', share_name)
|
|
||||||
self._helper._execute.assert_called_with('smbcontrol', 'all',
|
|
||||||
'close-share', share_name,
|
|
||||||
run_as_root=True)
|
|
||||||
|
|
||||||
def test_remove_export_no_such_service(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def exec_return(*args, **kwargs):
|
|
||||||
if 'net' in args:
|
|
||||||
raise exception.ProcessExecutionError(
|
|
||||||
stderr='SBC_ERR_NO_SUCH_SERVICE')
|
|
||||||
|
|
||||||
self._helper._execute = mock.Mock(side_effect=exec_return)
|
|
||||||
self._helper.remove_export('fakelocalpath', share_name)
|
|
||||||
self._helper._execute.assert_called_with(
|
|
||||||
'smbcontrol', 'all', 'close-share', share_name, run_as_root=True)
|
|
||||||
|
|
||||||
def test_remove_export_error(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error(*args, **kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stderr="fake_stderr")
|
|
||||||
self._helper._execute = mock.Mock(
|
|
||||||
side_effect=raise_exec_error)
|
|
||||||
|
|
||||||
self.assertRaises(exception.ProcessExecutionError,
|
|
||||||
self._helper.remove_export, 'fakelocalpath',
|
|
||||||
share_name)
|
|
||||||
|
|
||||||
def test_allow_access(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(return_value=['127.0.0.1',
|
|
||||||
'10.0.0.1'])
|
|
||||||
self._helper._set_allow_hosts = mock.Mock()
|
|
||||||
self._helper.allow_access('fakelocalpath', share_name, 'ip',
|
|
||||||
'10.0.0.2')
|
|
||||||
self._helper._set_allow_hosts.assert_called_with(
|
|
||||||
['127.0.0.1', '10.0.0.1', '10.0.0.2'], share_name)
|
|
||||||
|
|
||||||
def test_allow_access_exists(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(return_value=['127.0.0.1',
|
|
||||||
'10.0.0.1'])
|
|
||||||
self.assertRaises(exception.ShareAccessExists,
|
|
||||||
self._helper.allow_access, 'fakelocalpath',
|
|
||||||
share_name, 'ip', '10.0.0.1')
|
|
||||||
|
|
||||||
def test_allow_access_wrong_type(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self.assertRaises(exception.InvalidShareAccess,
|
|
||||||
self._helper.allow_access, 'fakelocalpath',
|
|
||||||
share_name, 'fake', 'fake access')
|
|
||||||
|
|
||||||
def test_deny_access(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(return_value=['127.0.0.1',
|
|
||||||
'10.0.0.1'])
|
|
||||||
self._helper._set_allow_hosts = mock.Mock()
|
|
||||||
self._helper.deny_access('fakelocalpath', share_name, 'ip',
|
|
||||||
'10.0.0.1')
|
|
||||||
self._helper._set_allow_hosts.assert_called_with(
|
|
||||||
['127.0.0.1'], share_name)
|
|
||||||
|
|
||||||
def test_deny_access_not_exists(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error(*args, **kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stdout="does not exist")
|
|
||||||
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(side_effect=raise_exec_error)
|
|
||||||
self.assertRaises(exception.ProcessExecutionError,
|
|
||||||
self._helper.deny_access, 'fakelocalpath',
|
|
||||||
share_name, 'ip', '10.0.0.1')
|
|
||||||
|
|
||||||
def test_deny_access_not_exists_force(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error(*args, **kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stdout="does not exist")
|
|
||||||
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(side_effect=raise_exec_error)
|
|
||||||
self._helper.deny_access('fakelocalpath', share_name, 'ip', '10.0.0.1',
|
|
||||||
force=True)
|
|
||||||
|
|
||||||
def test_deny_access_error(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
|
|
||||||
def raise_exec_error(*args, **kwargs):
|
|
||||||
raise exception.ProcessExecutionError(stdout="fake out")
|
|
||||||
|
|
||||||
self._helper._get_allow_hosts = mock.Mock(side_effect=raise_exec_error)
|
|
||||||
self.assertRaises(exception.ProcessExecutionError,
|
|
||||||
self._helper.deny_access, 'fakelocalpath',
|
|
||||||
share_name, 'ip', '10.0.0.1')
|
|
||||||
|
|
||||||
def test_get_allow_hosts(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._execute = mock.Mock(return_value=(
|
|
||||||
'127.0.0.1 10.0.0.1', ''))
|
|
||||||
ret = self._helper._get_allow_hosts(share_name)
|
|
||||||
self.assertEqual(ret, ['127.0.0.1', '10.0.0.1'])
|
|
||||||
|
|
||||||
def test_set_allow_hosts(self):
|
|
||||||
share_name = self.share['name']
|
|
||||||
self._helper._execute = mock.Mock()
|
|
||||||
self._helper._set_allow_hosts(['127.0.0.1', '10.0.0.1'], share_name)
|
|
||||||
self._helper._execute.assert_called_with(
|
|
||||||
'net', 'conf', 'setparm', share_name, 'hosts allow',
|
|
||||||
'127.0.0.1 10.0.0.1', run_as_root=True)
|
|
Loading…
Reference in New Issue
Block a user