Merge "Container driver"
This commit is contained in:
commit
2f047eb25a
@ -208,6 +208,19 @@ elif [[ "$DRIVER" == "dummy" ]]; then
|
||||
iniset $TEMPEST_CONFIG share multitenancy_enabled True
|
||||
iniset $TEMPEST_CONFIG share create_networks_when_multitenancy_enabled False
|
||||
iniset $TEMPEST_CONFIG share multi_backend True
|
||||
elif [[ "$DRIVER" == "container" ]]; then
|
||||
MANILA_TEMPEST_CONCURRENCY=1
|
||||
RUN_MANILA_CG_TESTS=False
|
||||
RUN_MANILA_MANAGE_TESTS=False
|
||||
iniset $TEMPEST_CONFIG share run_migration_tests False
|
||||
iniset $TEMPEST_CONFIG share run_quota_tests False
|
||||
iniset $TEMPEST_CONFIG share run_replication_tests False
|
||||
iniset $TEMPEST_CONFIG share run_shrink_tests False
|
||||
iniset $TEMPEST_CONFIG share run_snapshot_tests False
|
||||
iniset $TEMPEST_CONFIG share capability_storage_protocol 'CIFS'
|
||||
iniset $TEMPEST_CONFIG share enable_protocols 'cifs'
|
||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols ''
|
||||
fi
|
||||
|
||||
# Enable consistency group tests
|
||||
|
@ -135,6 +135,10 @@ elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
||||
# the build timeout for ZFS on the gate.
|
||||
echo "MANILA_REPLICA_STATE_UPDATE_INTERVAL=60" >> $localrc_path
|
||||
echo "MANILA_ZFSONLINUX_USE_SSH=True" >> $localrc_path
|
||||
elif [[ "$DRIVER" == "container" ]]; then
|
||||
echo "SHARE_DRIVER=manila.share.drivers.container.driver.ContainerShareDriver" >> $localrc_path
|
||||
echo "SHARE_BACKING_FILE_SIZE=32000M" >> $localrc_path
|
||||
echo "MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS='snapshot_support=false'" >> $localrc_path
|
||||
fi
|
||||
|
||||
echo "MANILA_SERVICE_IMAGE_ENABLED=$MANILA_SERVICE_IMAGE_ENABLED" >> $localrc_path
|
||||
|
@ -271,6 +271,18 @@ function create_manila_service_keypair {
|
||||
}
|
||||
|
||||
|
||||
function is_driver_enabled {
|
||||
driver_name=$1
|
||||
for BE in ${MANILA_ENABLED_BACKENDS//,/ }; do
|
||||
share_driver=$(iniget $MANILA_CONF $BE share_driver)
|
||||
if [ "$share_driver" == "$driver_name" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
# create_service_share_servers - creates service Nova VMs, one per generic
|
||||
# driver, and only if it is configured to mode without handling of share servers.
|
||||
function create_service_share_servers {
|
||||
@ -443,7 +455,13 @@ function create_default_share_type {
|
||||
|
||||
local type_exists=$( manila type-list | grep " $MANILA_DEFAULT_SHARE_TYPE " )
|
||||
if [[ -z $type_exists ]]; then
|
||||
manila type-create $MANILA_DEFAULT_SHARE_TYPE $driver_handles_share_servers
|
||||
local command_args="$MANILA_DEFAULT_SHARE_TYPE $driver_handles_share_servers"
|
||||
#if is_driver_enabled $MANILA_CONTAINER_DRIVER; then
|
||||
# # TODO(aovchinnikov): Remove this condition when Container driver supports
|
||||
# # snapshots
|
||||
# command_args="$command_args --snapshot_support false"
|
||||
#fi
|
||||
manila type-create $command_args
|
||||
fi
|
||||
if [[ $MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS ]]; then
|
||||
manila type-key $MANILA_DEFAULT_SHARE_TYPE set $MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS
|
||||
@ -451,6 +469,25 @@ function create_default_share_type {
|
||||
}
|
||||
|
||||
|
||||
# configure_backing_file - Set up backing file for LVM
|
||||
function configure_backing_file {
|
||||
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
|
||||
mkdir -p /tmp/shares
|
||||
}
|
||||
|
||||
# init_manila - Initializes database and creates manila dir if absent
|
||||
function init_manila {
|
||||
|
||||
@ -480,21 +517,15 @@ function init_manila {
|
||||
#
|
||||
# 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
|
||||
configure_backing_file
|
||||
fi
|
||||
elif [ "$SHARE_DRIVER" == $MANILA_CONTAINER_DRIVER ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
SHARE_GROUP=$MANILA_CONTAINER_VOLUME_GROUP_NAME
|
||||
iniset $MANILA_CONF DEFAULT neutron_host_id $(hostname)
|
||||
configure_backing_file
|
||||
fi
|
||||
|
||||
elif [ "$SHARE_DRIVER" == "manila.share.drivers.zfsonlinux.driver.ZFSonLinuxShareDriver" ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
mkdir -p $MANILA_ZFSONLINUX_BACKEND_FILES_CONTAINER_DIR
|
||||
@ -599,6 +630,20 @@ function install_manila {
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
elif [ "$SHARE_DRIVER" == $MANILA_CONTAINER_DRIVER ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
echo "m-shr service is enabled"
|
||||
if is_ubuntu; then
|
||||
echo "Installing docker...."
|
||||
install_docker_ubuntu
|
||||
echo "Importing docker image"
|
||||
import_docker_service_image_ubuntu
|
||||
else
|
||||
echo "Manila Devstack plugin does not support Container Driver on "\
|
||||
" distros other than Ubuntu."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# install manila-ui if horizon is enabled
|
||||
@ -696,6 +741,58 @@ function update_tempest {
|
||||
fi
|
||||
}
|
||||
|
||||
function install_docker_ubuntu {
|
||||
sudo apt-get update
|
||||
install_package apparmor
|
||||
install_package docker.io
|
||||
}
|
||||
|
||||
function download_image {
|
||||
local image_url=$1
|
||||
|
||||
local image image_fname
|
||||
|
||||
image_fname=`basename "$image_url"`
|
||||
if [[ $image_url != file* ]]; then
|
||||
# Downloads the image (uec ami+akistyle), then extracts it.
|
||||
if [[ ! -f $FILES/$image_fname || "$(stat -c "%s" $FILES/$image_fname)" = "0" ]]; then
|
||||
wget --progress=dot:giga -c $image_url -O $FILES/$image_fname
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Not found: $image_url"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
image="$FILES/${image_fname}"
|
||||
else
|
||||
# File based URL (RFC 1738): ``file://host/path``
|
||||
# Remote files are not considered here.
|
||||
# unix: ``file:///home/user/path/file``
|
||||
# windows: ``file:///C:/Documents%20and%20Settings/user/path/file``
|
||||
image=$(echo $image_url | sed "s/^file:\/\///g")
|
||||
if [[ ! -f $image || "$(stat -c "%s" $image)" == "0" ]]; then
|
||||
echo "Not found: $image_url"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function import_docker_service_image_ubuntu {
|
||||
GZIPPED_IMG_NAME=`basename "$MANILA_DOCKER_IMAGE_URL"`
|
||||
IMG_NAME_LOAD=${GZIPPED_IMG_NAME%.*}
|
||||
LOCAL_IMG_NAME=${IMG_NAME_LOAD%.*}
|
||||
if [[ "$(sudo docker images -q $LOCAL_IMG_NAME)" == "" ]]; then
|
||||
download_image $MANILA_DOCKER_IMAGE_URL
|
||||
# Import image in Docker
|
||||
gzip -d $FILES/$GZIPPED_IMG_NAME
|
||||
sudo docker load --input $FILES/$IMG_NAME_LOAD
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_docker_service_image {
|
||||
sudo docker rmi $MANILA_DOCKER_IMAGE_ALIAS
|
||||
}
|
||||
|
||||
|
||||
function install_libraries {
|
||||
if [ $(trueorfalse False MANILA_MULTI_BACKEND) == True ]; then
|
||||
if [ $(trueorfalse True RUN_MANILA_MIGRATION_TESTS) == True ]; then
|
||||
|
@ -158,6 +158,14 @@ MANILA_ZFSONLINUX_SSH_USERNAME=${MANILA_ZFSONLINUX_SSH_USERNAME:-$STACK_USER}
|
||||
# Manila will consider replication feature as disabled for ZFSonLinux share driver.
|
||||
MANILA_ZFSONLINUX_REPLICATION_DOMAIN=${MANILA_ZFSONLINUX_REPLICATION_DOMAIN:-"ZFSonLinux"}
|
||||
|
||||
# Container Driver
|
||||
MANILA_CONTAINER_DRIVER=${MANILA_CONTAINER_DRIVER:-"manila.share.drivers.container.driver.ContainerShareDriver"}
|
||||
MANILA_DOCKER_IMAGE_ALIAS=${MANILA_DOCKER_IMAGE_ALIAS:-"manila_docker_image"}
|
||||
MANILA_CONTAINER_VOLUME_GROUP_NAME=${MANILA_CONTAINER_VOLUME_GROUP_NAME:-"manila_docker_volumes"}
|
||||
# (aovchinnikov): This location is temporary and will be changed to a
|
||||
# permanent one as soon as possible.
|
||||
MANILA_DOCKER_IMAGE_URL=${MANILA_DOCKER_IMAGE_URL:-"https://github.com/a-ovchinnikov/manila-image-elements-lxd-images/releases/download/0.1.0/manila-docker-container.tar.gz"}
|
||||
|
||||
# Enable manila services
|
||||
# ----------------------
|
||||
# We have to add Manila to enabled services for screen_it to work
|
||||
|
127
doc/source/devref/container_driver.rst
Normal file
127
doc/source/devref/container_driver.rst
Normal file
@ -0,0 +1,127 @@
|
||||
..
|
||||
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.
|
||||
|
||||
Container Driver
|
||||
================
|
||||
|
||||
The Container driver provides a lightweight solution for share servers
|
||||
management. It allows to use Docker containers for hosting userspace
|
||||
shared file systems services.
|
||||
|
||||
|
||||
Supported operations
|
||||
--------------------
|
||||
|
||||
- Create CIFS share;
|
||||
- Delete CIFS share;
|
||||
- Allow user access to CIFS share;
|
||||
- Deny user access to CIFS share;
|
||||
- Extend CIFS share.
|
||||
|
||||
Restrictions
|
||||
------------
|
||||
|
||||
- Current implementation has been tested only on Ubuntu. Devstack plugin won't
|
||||
work on other distributions however it should be possible to install
|
||||
prerequisits and set the driver up manually;
|
||||
- The only supported protocol is CIFS;
|
||||
- The following features are not implemented:
|
||||
* Manage/unmanage share;
|
||||
* Shrink share;
|
||||
* Create/delete snapshots;
|
||||
* Create a share fron a snapshot;
|
||||
* Manage/unmanage snapshots.
|
||||
|
||||
Known problems
|
||||
--------------
|
||||
|
||||
- May demonstrate unstable behaviour when running concurrently. It is strongly
|
||||
suggested that the driver should be used with extreme care in cases
|
||||
other than building lightweight development and testing environments.
|
||||
|
||||
Setting up container driver with devstack
|
||||
=========================================
|
||||
|
||||
The driver could be set up via devstack. This requires the following update to
|
||||
local.conf:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
enable_plugin manila https://git.openstack.org/openstack/manila <ref>
|
||||
MANILA_BACKEND1_CONFIG_GROUP_NAME=london
|
||||
MANILA_SHARE_BACKEND1_NAME=LONDON
|
||||
MANILA_OPTGROUP_london_driver_handles_share_servers=True
|
||||
MANILA_OPTGROUP_london_neutron_host_id=<hostname>
|
||||
SHARE_DRIVER=manila.share.drivers.container.driver.ContainerShareDriver
|
||||
SHARE_BACKING_FILE_SIZE=<backing file size>
|
||||
MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS='snapshot_support=false'
|
||||
|
||||
where <ref> is change reference, which could be copied from gerrit web-interface,
|
||||
<hostname> is the name of the host with running neutron
|
||||
|
||||
|
||||
|
||||
Setting Container Driver Up Manually
|
||||
====================================
|
||||
|
||||
This section describes steps needed to be performed to set the driver up
|
||||
manually. The driver has been tested on Ubuntu 14.04, thus in case of
|
||||
any other distribution package names might differ.
|
||||
The following packages must be installed:
|
||||
- docker.io
|
||||
One can verify if the package is installed by issuing ``sudo docker info``
|
||||
command. In case of normal operation it should return docker usage statistics.
|
||||
In case it fails complaining on inaccessible socket try installing
|
||||
``apparmor``. Please note that docker usage requires superuser privileges.
|
||||
|
||||
After docker is successfully installed a docker image containing necessary
|
||||
packages must be provided. Currently such image could be downloaded from
|
||||
https://github.com/a-ovchinnikov/manila-image-elements-lxd-images/releases/download/0.1.0/manila-docker-container.tar.gz.
|
||||
The image has to be unpacked but not untarred. This could be achieved by
|
||||
running 'gzip -d <imagename>' command. Resulting tar-archive of the
|
||||
image could be uploaded to docker via
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo docker load --input <imagename.tar>
|
||||
|
||||
If the previous command finished successfully you will be able to see the image
|
||||
in the image list:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo docker images
|
||||
|
||||
The driver expects to find a folder /tmp/shares on the host where it is running
|
||||
as well as a logical volume group "manila_docker_volumes".
|
||||
|
||||
When installing the driver manually one must make sure that 'brctl' and
|
||||
'docker' commands are present in the /etc/manila/rootwrap.d/share.filters
|
||||
and could be executed as root.
|
||||
|
||||
Finally to use the driver one must add a backend to the config file
|
||||
containing the following settings:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
driver_handles_share_servers = True
|
||||
share_driver = manila.share.drivers.container.driver.ContainerShareDriver
|
||||
neutron_host_id = <hostname>
|
||||
|
||||
where <hostname> is the name of the host running neutron. (In case of single
|
||||
VM devstack it is VM's name).
|
||||
|
||||
After restarting manila services you should be able to use the driver.
|
@ -104,6 +104,7 @@ Share backends
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
container_driver
|
||||
zfs_on_linux_driver
|
||||
netapp_cluster_mode_driver
|
||||
emc_isilon_driver
|
||||
|
@ -35,6 +35,8 @@ Mapping of share drivers and share features support
|
||||
+========================================+=======================+=======================+==============+==============+========================+============================+==========================+
|
||||
| ZFSonLinux | M | N | M | M | M | M | N |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Container | N | \- | N | \- | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Generic (Cinder as back-end) | J | K | L | L | J | J | M |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N |
|
||||
@ -82,6 +84,8 @@ Mapping of share drivers and share access rules support
|
||||
+========================================+==============+================+============+==============+==============+================+============+============+
|
||||
| ZFSonLinux | NFS (M) | \- | \- | \- | NFS (M) | \- | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| Container | \- | CIFS (N) | \- | \- | \- | CIFS (N) | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | \- | NFS (K) | \- | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | \- | NFS (K) | CIFS (M) | \- | \- |
|
||||
@ -127,6 +131,8 @@ Mapping of share drivers and security services support
|
||||
+========================================+==================+=================+==================+
|
||||
| ZFSonLinux | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Container | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Generic (Cinder as back-end) | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| NetApp Clustered Data ONTAP | J | J | J |
|
||||
@ -172,6 +178,8 @@ Mapping of share drivers and common capabilities
|
||||
+========================================+===========+============+========+=============+===================+====================+=====+
|
||||
| ZFSonLinux | \- | M | M | M | M | \- | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
|
||||
| Container | N | \- | \- | \- | \- | N | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
|
||||
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
|
||||
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- |
|
||||
|
@ -149,3 +149,12 @@ ls: CommandFilter, ls, root
|
||||
|
||||
# manila/data/utils.py: 'touch', '--reference=%s', '%s'
|
||||
touch: CommandFilter, touch, root
|
||||
|
||||
# manila/share/drivers/container/container.py: docker <whatever>
|
||||
docker: CommandFilter, docker, root
|
||||
|
||||
# manila/share/drivers/container/container.py: brctl <whatever>
|
||||
brctl: CommandFilter, brctl, root
|
||||
|
||||
# manila/share/drivers/container/container.py: e2fsck <whatever>
|
||||
e2fsck: CommandFilter, e2fsck, root
|
||||
|
@ -39,6 +39,14 @@ neutron_single_network_plugin_opts = [
|
||||
deprecated_group='DEFAULT'),
|
||||
]
|
||||
|
||||
neutron_network_plugin_opts = [
|
||||
cfg.StrOpt(
|
||||
"neutron_host_id",
|
||||
help="Host ID to be used when creating neutron port. Hostname of "
|
||||
"a controller running Neutron should be used in a general case.",
|
||||
deprecated_group='DEFAULT'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ -51,6 +59,9 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
|
||||
self._neutron_api_args = args
|
||||
self._neutron_api_kwargs = kwargs
|
||||
self._label = kwargs.pop('label', 'user')
|
||||
CONF.register_opts(
|
||||
neutron_network_plugin_opts,
|
||||
group=self.neutron_api.config_group_name)
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
@ -114,11 +125,12 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
|
||||
self._delete_port(context, port)
|
||||
|
||||
def _create_port(self, context, share_server, share_network, device_owner):
|
||||
host_id = self.neutron_api.configuration.neutron_host_id
|
||||
port = self.neutron_api.create_port(
|
||||
share_network['project_id'],
|
||||
network_id=share_network['neutron_net_id'],
|
||||
subnet_id=share_network['neutron_subnet_id'],
|
||||
device_owner='manila:' + device_owner)
|
||||
device_owner='manila:' + device_owner, host_id=host_id)
|
||||
port_dict = {
|
||||
'id': port['id'],
|
||||
'share_server_id': share_server['id'],
|
||||
|
@ -51,6 +51,8 @@ import manila.service
|
||||
import manila.share.api
|
||||
import manila.share.driver
|
||||
import manila.share.drivers.cephfs.cephfs_native
|
||||
import manila.share.drivers.container.driver
|
||||
import manila.share.drivers.container.storage_helper
|
||||
import manila.share.drivers.emc.driver
|
||||
import manila.share.drivers.emc.plugins.isilon.isilon
|
||||
import manila.share.drivers.generic
|
||||
@ -99,6 +101,8 @@ _global_opt_lists = [
|
||||
manila.network.network_opts,
|
||||
manila.network.neutron.api.neutron_opts,
|
||||
manila.network.neutron.neutron_network_plugin.
|
||||
neutron_network_plugin_opts,
|
||||
manila.network.neutron.neutron_network_plugin.
|
||||
neutron_single_network_plugin_opts,
|
||||
manila.network.nova_network_plugin.nova_single_network_plugin_opts,
|
||||
manila.network.standalone_network_plugin.standalone_network_plugin_opts,
|
||||
@ -117,6 +121,8 @@ _global_opt_lists = [
|
||||
manila.share.driver.ssh_opts,
|
||||
manila.share.drivers_private_data.private_data_opts,
|
||||
manila.share.drivers.cephfs.cephfs_native.cephfs_native_opts,
|
||||
manila.share.drivers.container.driver.container_opts,
|
||||
manila.share.drivers.container.storage_helper.lv_opts,
|
||||
manila.share.drivers.emc.driver.EMC_NAS_OPTS,
|
||||
manila.share.drivers.generic.share_opts,
|
||||
manila.share.drivers.glusterfs.common.glusterfs_common_opts,
|
||||
|
0
manila/share/drivers/container/__init__.py
Normal file
0
manila/share/drivers/container/__init__.py
Normal file
79
manila/share/drivers/container/container_helper.py
Normal file
79
manila/share/drivers/container/container_helper.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LI
|
||||
from manila.share import driver
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DockerExecHelper(driver.ExecuteMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.configuration = kwargs.pop("configuration", None)
|
||||
super(DockerExecHelper, self).__init__(*args, **kwargs)
|
||||
self.init_execute_mixin()
|
||||
|
||||
def start_container(self, name=None):
|
||||
name = name or "".join(["manila_cifs_docker_container",
|
||||
str(uuid.uuid1()).replace("-", "_")])
|
||||
image_name = self.configuration.container_image_name
|
||||
LOG.debug("Starting container from image %s.", image_name)
|
||||
# (aovchinnikov): --privileged is required for both samba and
|
||||
# nfs-ganesha to actually allow access to shared folders.
|
||||
cmd = ["docker", "run", "-d", "-i", "-t", "--privileged",
|
||||
"--name=%s" % name, '-v', "/tmp/shares:/shares", image_name]
|
||||
result = self._inner_execute(cmd) or ['', 1]
|
||||
if result[1] != '':
|
||||
raise exception.ManilaException(
|
||||
_("Container %s has failed to start.") % name)
|
||||
LOG.info(_LI("A container has been successfully started! Its id is "
|
||||
"%s."), result[0].rstrip('\n'))
|
||||
|
||||
def stop_container(self, name):
|
||||
LOG.debug("Stopping container %s.", name)
|
||||
cmd = ["docker", "stop", name]
|
||||
result = self._inner_execute(cmd) or ['', 1]
|
||||
if result[1] != '':
|
||||
raise exception.ManilaException(
|
||||
_("Container %s has failed to stop properly.") % name)
|
||||
LOG.info(_LI("Container %s is successfully stopped."), name)
|
||||
|
||||
def execute(self, name=None, cmd=None):
|
||||
if name is None:
|
||||
raise exception.ManilaException(_("Container name not specified."))
|
||||
if cmd is None or (type(cmd) is not list):
|
||||
raise exception.ManilaException(_("Missing or malformed command."))
|
||||
LOG.debug("Executing inside a container %s.", name)
|
||||
cmd = ["docker", "exec", "-i", name] + cmd
|
||||
result = self._inner_execute(cmd)
|
||||
LOG.debug("Run result: %s.", str(result))
|
||||
return result
|
||||
|
||||
def _inner_execute(self, cmd):
|
||||
LOG.debug("Executing command: %s.", " ".join(cmd))
|
||||
try:
|
||||
result = self._execute(*cmd, run_as_root=True)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return None
|
||||
LOG.debug("Execution result: %s.", result)
|
||||
return result
|
282
manila/share/drivers/container/driver.py
Normal file
282
manila/share/drivers/container/driver.py
Normal file
@ -0,0 +1,282 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
"""Container Driver for shares.
|
||||
|
||||
This driver uses a container as a share server.
|
||||
Current implementation suggests that a container when started by Docker will
|
||||
be plugged into a Linux bridge. Also it is suggested that all interfaces
|
||||
willing to talk to each other reside in an OVS bridge."""
|
||||
|
||||
import re
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LI
|
||||
from manila.i18n import _LW
|
||||
from manila.share import driver
|
||||
from manila import utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
container_opts = [
|
||||
cfg.StrOpt("container_linux_bridge_name",
|
||||
default="docker0",
|
||||
required=True,
|
||||
help="Linux bridge used by container hypervisor to plug "
|
||||
"host-side veth to. It will be unplugged from here "
|
||||
"by the driver."),
|
||||
cfg.StrOpt("container_ovs_bridge_name",
|
||||
default="br-int",
|
||||
required=True,
|
||||
help="OVS bridge to use to plug a container to."),
|
||||
cfg.BoolOpt("container_cifs_guest_ok",
|
||||
default=True,
|
||||
help="Determines whether to allow guest access to CIFS share "
|
||||
"or not."),
|
||||
cfg.StrOpt("container_image_name",
|
||||
default="manila-docker-container",
|
||||
help="Image to be used for a container-based share server."),
|
||||
cfg.StrOpt("container_helper",
|
||||
default="manila.share.drivers.container.container_helper."
|
||||
"DockerExecHelper",
|
||||
help="Container helper which provides container-related "
|
||||
"operations to the driver."),
|
||||
cfg.StrOpt("container_protocol_helper",
|
||||
default="manila.share.drivers.container.protocol_helper."
|
||||
"DockerCIFSHelper",
|
||||
help="Helper which facilitates interaction with share server."),
|
||||
cfg.StrOpt("container_storage_helper",
|
||||
default="manila.share.drivers.container.storage_helper."
|
||||
"LVMHelper",
|
||||
help="Helper which facilitates interaction with storage "
|
||||
"solution used to actually store data. By default LVM "
|
||||
"is used to provide storage for a share."),
|
||||
]
|
||||
|
||||
|
||||
class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ContainerShareDriver, self).__init__([True], *args, **kwargs)
|
||||
self.configuration.append_config_values(container_opts)
|
||||
self.backend_name = self.configuration.safe_get(
|
||||
"share_backend_name") or "Docker"
|
||||
self.container = importutils.import_class(
|
||||
self.configuration.container_helper)(
|
||||
configuration=self.configuration)
|
||||
self.storage = importutils.import_class(
|
||||
self.configuration.container_storage_helper)(
|
||||
configuration=self.configuration)
|
||||
self._helpers = {}
|
||||
|
||||
def _get_helper(self, share):
|
||||
if share["share_proto"].upper() == "CIFS":
|
||||
helper = self._helpers.get("CIFS")
|
||||
if helper is not None:
|
||||
return helper(self.container,
|
||||
share=share,
|
||||
config=self.configuration)
|
||||
self._helpers["CIFS"] = importutils.import_class(
|
||||
self.configuration.container_protocol_helper)
|
||||
return self._helpers["CIFS"](self.container,
|
||||
share=share,
|
||||
config=self.configuration)
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=_("Wrong, unsupported or disabled protocol."))
|
||||
|
||||
def _update_share_stats(self):
|
||||
data = {
|
||||
'share_backend_name': self.backend_name,
|
||||
'storage_protocol': 'CIFS',
|
||||
'reserved_percentage':
|
||||
self.configuration.reserved_share_percentage,
|
||||
'consistency_group_support': None,
|
||||
'snapshot_support': False,
|
||||
'driver_name': 'ContainerShareDriver',
|
||||
'pools': self.storage.get_share_server_pools()
|
||||
}
|
||||
super(ContainerShareDriver, self)._update_share_stats(data)
|
||||
|
||||
def create_share(self, context, share, share_server=None):
|
||||
LOG.debug("Create share on server '%s'." % share_server["id"])
|
||||
server_id = self._get_container_name(share_server["id"])
|
||||
share_name = share.share_id
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["mkdir", "-m", "750", "/shares/%s" % share_name]
|
||||
)
|
||||
self.storage.provide_storage(share)
|
||||
location = self._get_helper(share).create_share(server_id)
|
||||
return location
|
||||
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
LOG.debug("Deleting share %(share)s on server '%(server)s'." %
|
||||
{"server": share_server["id"],
|
||||
"share": share.share_id})
|
||||
server_id = self._get_container_name(share_server["id"])
|
||||
self._get_helper(share).delete_share(server_id)
|
||||
|
||||
self.storage.remove_storage(share)
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["rm", "-fR", "/shares/%s" % share.share_id]
|
||||
)
|
||||
LOG.debug("Deletion of share %s is completed!", share.share_id)
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
self.storage.extend_share(share, new_size, share_server)
|
||||
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
pass
|
||||
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
delete_rules, share_server=None):
|
||||
server_id = self._get_container_name(share_server["id"])
|
||||
LOG.debug("Updating access to share %(share)s at "
|
||||
"share server %(share_server)s.",
|
||||
{"share_server": share_server["id"],
|
||||
"share": share.share_id})
|
||||
self._get_helper(share).update_access(server_id,
|
||||
access_rules, add_rules,
|
||||
delete_rules)
|
||||
|
||||
def get_network_allocations_number(self):
|
||||
return 1
|
||||
|
||||
def _get_container_name(self, server_id):
|
||||
return "manila_%s" % server_id.replace("-", "_")
|
||||
|
||||
def do_setup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def check_for_setup_error(self, *args, **kwargs):
|
||||
host_id = self.configuration.safe_get("neutron_host_id")
|
||||
neutron_class = importutils.import_class(
|
||||
'manila.network.neutron.neutron_network_plugin.'
|
||||
'NeutronNetworkPlugin'
|
||||
)
|
||||
actual_class = importutils.import_class(
|
||||
self.configuration.safe_get("network_api_class"))
|
||||
if host_id is None and issubclass(actual_class, neutron_class):
|
||||
msg = _("%s requires neutron_host_id to be "
|
||||
"specified.") % neutron_class
|
||||
raise exception.ManilaException(msg)
|
||||
elif host_id is None:
|
||||
LOG.warning(_LW("neutron_host_id is not specified. This driver "
|
||||
"might not work as expected without it."))
|
||||
|
||||
def _connect_to_network(self, server_id, network_info, host_veth):
|
||||
LOG.debug("Attempting to connect container to neutron network.")
|
||||
network_allocation = network_info['network_allocations'][0]
|
||||
port_address = network_allocation.ip_address
|
||||
port_mac = network_allocation.mac_address
|
||||
port_id = network_allocation.id
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["ifconfig", "eth0", port_address, "up"]
|
||||
)
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["ip", "link", "set", "dev", "eth0", "address", port_mac]
|
||||
)
|
||||
msg_helper = {
|
||||
'id': server_id, 'veth': host_veth,
|
||||
'lb': self.configuration.container_linux_bridge_name,
|
||||
'ovsb': self.configuration.container_ovs_bridge_name,
|
||||
'ip': port_address,
|
||||
'subnet': network_info['neutron_subnet_id'],
|
||||
}
|
||||
LOG.debug("Container %(id)s veth is %(veth)s.", msg_helper)
|
||||
LOG.debug("Removing %(veth)s from %(lb)s.", msg_helper)
|
||||
self._execute("brctl", "delif",
|
||||
self.configuration.container_linux_bridge_name,
|
||||
host_veth,
|
||||
run_as_root=True)
|
||||
|
||||
LOG.debug("Plugging %(veth)s into %(ovsb)s.", msg_helper)
|
||||
set_if = ['--', 'set', 'interface', host_veth]
|
||||
e_mac = set_if + ['external-ids:attached-mac="%s"' % port_mac]
|
||||
e_id = set_if + ['external-ids:iface-id="%s"' % port_id]
|
||||
e_status = set_if + ['external-ids:iface-status=active']
|
||||
e_mcid = set_if + ['external-ids:manila-container=%s' % server_id]
|
||||
self._execute("ovs-vsctl", "--", "add-port",
|
||||
self.configuration.container_ovs_bridge_name, host_veth,
|
||||
*(e_mac + e_id + e_status + e_mcid), run_as_root=True)
|
||||
LOG.debug("Now container %(id)s should be accessible from network "
|
||||
"%(subnet)s by address %(ip)s." % msg_helper)
|
||||
|
||||
def _teardown_server(self, *args, **kwargs):
|
||||
server_id = self._get_container_name(kwargs["server_details"]["id"])
|
||||
self.container.stop_container(server_id)
|
||||
interfaces = self._execute("ovs-vsctl", "list", "interface",
|
||||
run_as_root=True)[0]
|
||||
veths = re.findall("veth[0-9a-zA-Z]{7}", interfaces)
|
||||
manila_re = ("manila_[0-9a-f]{8}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{4}_"
|
||||
"[0-9a-f]{12}")
|
||||
for veth in veths:
|
||||
iface_data = self._execute("ovs-vsctl", "list", "interface", veth,
|
||||
run_as_root=True)[0]
|
||||
container_id = re.findall(manila_re, iface_data)
|
||||
if container_id == []:
|
||||
continue
|
||||
elif container_id[0] == server_id:
|
||||
LOG.debug("Deleting veth %s.", veth)
|
||||
self._execute("ovs-vsctl", "--", "del-port",
|
||||
self.configuration.container_ovs_bridge_name,
|
||||
veth, run_as_root=True)
|
||||
|
||||
def _get_veth_state(self):
|
||||
result = self._execute("brctl", "show",
|
||||
self.configuration.container_linux_bridge_name,
|
||||
run_as_root=True)
|
||||
veths = re.findall("veth.*\\n", result[0])
|
||||
veths = [x.rstrip('\n') for x in veths]
|
||||
msg = ("The following veth interfaces are plugged into %s now: " %
|
||||
self.configuration.container_linux_bridge_name)
|
||||
LOG.debug(msg + ", ".join(veths))
|
||||
return veths
|
||||
|
||||
def _get_corresponding_veth(self, before, after):
|
||||
result = list(set(after) ^ set(before))
|
||||
if len(result) != 1:
|
||||
raise exception.ManilaException(_("Multiple veths for container."))
|
||||
return result[0]
|
||||
|
||||
@utils.synchronized("veth-lock", external=True)
|
||||
def _setup_server(self, network_info, metadata=None):
|
||||
msg = "Creating share server '%s'."
|
||||
server_id = self._get_container_name(network_info["server_id"])
|
||||
LOG.debug(msg % server_id)
|
||||
|
||||
veths_before = self._get_veth_state()
|
||||
try:
|
||||
self.container.start_container(server_id)
|
||||
except Exception as e:
|
||||
raise exception.ManilaException(_("Cannot create container: %s") %
|
||||
e)
|
||||
veths_after = self._get_veth_state()
|
||||
|
||||
veth = self._get_corresponding_veth(veths_before, veths_after)
|
||||
self._connect_to_network(server_id, network_info, veth)
|
||||
LOG.info(_LI("Container %s was created."), server_id)
|
||||
return {"id": network_info["server_id"]}
|
144
manila/share/drivers/container/protocol_helper.py
Normal file
144
manila/share/drivers/container/protocol_helper.py
Normal file
@ -0,0 +1,144 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
import re
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LW
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DockerCIFSHelper(object):
|
||||
def __init__(self, container_helper, *args, **kwargs):
|
||||
super(DockerCIFSHelper, self).__init__()
|
||||
self.share = kwargs.get("share")
|
||||
self.conf = kwargs.get("config")
|
||||
self.container = container_helper
|
||||
|
||||
def create_share(self, server_id):
|
||||
share_name = self.share.share_id
|
||||
cmd = ["net", "conf", "addshare", share_name,
|
||||
"/shares/%s" % share_name, "writeable=y"]
|
||||
if self.conf.container_cifs_guest_ok:
|
||||
cmd.append("guest_ok=y")
|
||||
else:
|
||||
cmd.append("guest_ok=n")
|
||||
self.container.execute(server_id, cmd)
|
||||
parameters = {
|
||||
"browseable": "yes",
|
||||
"create mask": "0755",
|
||||
"read only": "no",
|
||||
}
|
||||
for param, value in parameters.items():
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, param, value]
|
||||
)
|
||||
result = self.container.execute(
|
||||
server_id,
|
||||
["ip", "addr", "show", "eth0"]
|
||||
)[0].split('\n')[2]
|
||||
address = re.findall("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", result)[0]
|
||||
return r"//%(addr)s/%(name)s" % {"addr": address, "name": share_name}
|
||||
|
||||
def delete_share(self, server_id):
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["net", "conf", "delshare", self.share.share_id]
|
||||
)
|
||||
|
||||
def _get_access_group(self, access_level):
|
||||
if access_level == const.ACCESS_LEVEL_RO:
|
||||
access = "read list"
|
||||
elif access_level == const.ACCESS_LEVEL_RW:
|
||||
access = "valid users"
|
||||
else:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
return access
|
||||
|
||||
def _get_existing_users(self, server_id, share_name, access):
|
||||
result = self.container.execute(
|
||||
server_id,
|
||||
["net", "conf", "getparm", share_name, access]
|
||||
)[0].rstrip('\n')
|
||||
return result
|
||||
|
||||
def _set_users(self, server_id, share_name, access, users_to_set):
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, access, users_to_set]
|
||||
)
|
||||
|
||||
def _allow_access(self, share_name, server_id, user_to_allow,
|
||||
access_level):
|
||||
access = self._get_access_group(access_level)
|
||||
try:
|
||||
existing_users = self._get_existing_users(server_id, share_name,
|
||||
access)
|
||||
except TypeError:
|
||||
users_to_allow = user_to_allow
|
||||
else:
|
||||
users_to_allow = " ".join([existing_users, user_to_allow])
|
||||
self._set_users(server_id, share_name, access, users_to_allow)
|
||||
|
||||
def _deny_access(self, share_name, server_id, user_to_deny,
|
||||
access_level):
|
||||
access = self._get_access_group(access_level)
|
||||
try:
|
||||
existing_users = self._get_existing_users(server_id, share_name,
|
||||
access)
|
||||
except TypeError:
|
||||
LOG.warning(_LW("Can't access smbd at share %s.") % share_name)
|
||||
return
|
||||
else:
|
||||
allowed_users = " ".join(sorted(set(existing_users.split()) -
|
||||
set([user_to_deny])))
|
||||
if allowed_users != existing_users:
|
||||
self._set_users(server_id, share_name, access, allowed_users)
|
||||
|
||||
def update_access(self, server_id, access_rules,
|
||||
add_rules=None, delete_rules=None):
|
||||
|
||||
def _rule_updater(rules, action, override_type_check=False):
|
||||
for rule in rules:
|
||||
access_level = rule['access_level']
|
||||
access_type = rule['access_type']
|
||||
# (aovchinnikov): override_type_check is used to ensure
|
||||
# broken rules deletion.
|
||||
if access_type == 'user' or override_type_check:
|
||||
action(share_name, server_id, rule['access_to'],
|
||||
access_level)
|
||||
else:
|
||||
msg = _("Access type '%s' is not supported by the "
|
||||
"driver.") % access_type
|
||||
raise exception.InvalidShareAccess(reason=msg)
|
||||
|
||||
share_name = self.share.share_id
|
||||
if not (add_rules or delete_rules):
|
||||
# clean all users first.
|
||||
self.container.execute(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, "valid users", ""]
|
||||
)
|
||||
_rule_updater(access_rules or [], self._allow_access)
|
||||
return
|
||||
_rule_updater(add_rules or [], self._allow_access)
|
||||
_rule_updater(delete_rules or [], self._deny_access,
|
||||
override_type_check=True)
|
100
manila/share/drivers/container/storage_helper.py
Normal file
100
manila/share/drivers/container/storage_helper.py
Normal file
@ -0,0 +1,100 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.share import driver
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
lv_opts = [
|
||||
cfg.StrOpt("container_volume_group",
|
||||
default="manila_docker_volumes",
|
||||
help="LVM volume group to use for volumes. This volume group "
|
||||
"must be created by the cloud administrator independently "
|
||||
"from manila operations."),
|
||||
]
|
||||
|
||||
CONF.register_opts(lv_opts)
|
||||
|
||||
|
||||
class LVMHelper(driver.ExecuteMixin):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.configuration = kwargs.pop("configuration", None)
|
||||
if self.configuration is None:
|
||||
raise exception.ManilaException(_("LVMHelper called without "
|
||||
"supplying configuration."))
|
||||
self.configuration.append_config_values(lv_opts)
|
||||
super(LVMHelper, self).__init__(*args, **kwargs)
|
||||
self.init_execute_mixin()
|
||||
|
||||
def get_share_server_pools(self, share_server=None):
|
||||
out, err = self._execute('vgs',
|
||||
self.configuration.container_volume_group,
|
||||
'--rows', run_as_root=True)
|
||||
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': self.configuration.container_volume_group,
|
||||
'total_capacity_gb': float(total_size),
|
||||
'free_capacity_gb': float(free_size),
|
||||
'reserved_percentage': 0,
|
||||
}, ]
|
||||
|
||||
def _get_lv_device(self, share):
|
||||
return os.path.join("/dev", self.configuration.container_volume_group,
|
||||
share.share_id)
|
||||
|
||||
def _get_lv_folder(self, share):
|
||||
# Provides folder name in hosts /tmp to which logical volume is
|
||||
# mounted prior to providing access to it from a container.
|
||||
return os.path.join("/tmp/shares", share.share_id)
|
||||
|
||||
def provide_storage(self, share):
|
||||
share_name = share.share_id
|
||||
self._execute("lvcreate", "-p", "rw", "-L",
|
||||
str(share.size) + "G", "-n", share_name,
|
||||
self.configuration.container_volume_group,
|
||||
run_as_root=True)
|
||||
self._execute("mkfs.ext4", self._get_lv_device(share),
|
||||
run_as_root=True)
|
||||
self._execute("mount", self._get_lv_device(share),
|
||||
self._get_lv_folder(share), run_as_root=True)
|
||||
self._execute("chmod", "-R", "750", self._get_lv_folder(share),
|
||||
run_as_root=True)
|
||||
self._execute("chown", "nobody:nogroup", self._get_lv_folder(share),
|
||||
run_as_root=True)
|
||||
|
||||
def remove_storage(self, share):
|
||||
self._execute("umount", self._get_lv_device(share), run_as_root=True)
|
||||
self._execute("lvremove", "-f", "--autobackup", "n",
|
||||
self._get_lv_device(share), run_as_root=True)
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
lv_device = self._get_lv_device(share)
|
||||
lv_folder = self._get_lv_folder(share)
|
||||
self._execute("umount", lv_folder, run_as_root=True)
|
||||
cmd = ('lvextend', '-L', '%sG' % new_size, '-n', lv_device)
|
||||
self._execute(*cmd, run_as_root=True)
|
||||
self._execute("e2fsck", "-f", "-y", lv_device, run_as_root=True)
|
||||
self._execute('resize2fs', lv_device, run_as_root=True)
|
||||
self._execute("mount", lv_device, lv_folder, run_as_root=True)
|
@ -132,7 +132,7 @@ class NeutronNetworkPluginTest(test.TestCase):
|
||||
fake_share_network['project_id'],
|
||||
network_id=fake_share_network['neutron_net_id'],
|
||||
subnet_id=fake_share_network['neutron_subnet_id'],
|
||||
device_owner='manila:share')
|
||||
device_owner='manila:share', host_id=None)
|
||||
db_api.network_allocation_create.assert_called_once_with(
|
||||
self.fake_context,
|
||||
fake_network_allocation)
|
||||
@ -141,6 +141,54 @@ class NeutronNetworkPluginTest(test.TestCase):
|
||||
save_nw_data.stop()
|
||||
save_subnet_data.stop()
|
||||
|
||||
@mock.patch.object(db_api, 'network_allocation_create',
|
||||
mock.Mock(return_values=fake_network_allocation))
|
||||
@mock.patch.object(db_api, 'share_network_get',
|
||||
mock.Mock(return_value=fake_share_network))
|
||||
@mock.patch.object(db_api, 'share_server_get',
|
||||
mock.Mock(return_value=fake_share_server))
|
||||
def test_allocate_network_host_id_prconfigured(self):
|
||||
has_provider_nw_ext = mock.patch.object(
|
||||
self.plugin, '_has_provider_network_extension').start()
|
||||
has_provider_nw_ext.return_value = True
|
||||
save_nw_data = mock.patch.object(self.plugin,
|
||||
'_save_neutron_network_data').start()
|
||||
save_subnet_data = mock.patch.object(
|
||||
self.plugin,
|
||||
'_save_neutron_subnet_data').start()
|
||||
|
||||
config_data = {
|
||||
'DEFAULT': {
|
||||
'neutron_host_id': 'fake_host_id',
|
||||
}
|
||||
}
|
||||
with test_utils.create_temp_config_with_opts(config_data):
|
||||
with mock.patch.object(self.plugin.neutron_api, 'create_port',
|
||||
mock.Mock(return_value=fake_neutron_port)):
|
||||
self.plugin.allocate_network(
|
||||
self.fake_context,
|
||||
fake_share_server,
|
||||
fake_share_network,
|
||||
allocation_info={'count': 1})
|
||||
|
||||
has_provider_nw_ext.assert_any_call()
|
||||
save_nw_data.assert_called_once_with(self.fake_context,
|
||||
fake_share_network)
|
||||
save_subnet_data.assert_called_once_with(self.fake_context,
|
||||
fake_share_network)
|
||||
self.plugin.neutron_api.create_port.assert_called_once_with(
|
||||
fake_share_network['project_id'],
|
||||
network_id=fake_share_network['neutron_net_id'],
|
||||
subnet_id=fake_share_network['neutron_subnet_id'],
|
||||
device_owner='manila:share', host_id='fake_host_id')
|
||||
db_api.network_allocation_create.assert_called_once_with(
|
||||
self.fake_context,
|
||||
fake_network_allocation)
|
||||
|
||||
has_provider_nw_ext.stop()
|
||||
save_nw_data.stop()
|
||||
save_subnet_data.stop()
|
||||
|
||||
@mock.patch.object(db_api, 'network_allocation_create',
|
||||
mock.Mock(return_values=fake_network_allocation))
|
||||
@mock.patch.object(db_api, 'share_network_get',
|
||||
@ -169,11 +217,11 @@ class NeutronNetworkPluginTest(test.TestCase):
|
||||
mock.call(fake_share_network['project_id'],
|
||||
network_id=fake_share_network['neutron_net_id'],
|
||||
subnet_id=fake_share_network['neutron_subnet_id'],
|
||||
device_owner='manila:share'),
|
||||
device_owner='manila:share', host_id=None),
|
||||
mock.call(fake_share_network['project_id'],
|
||||
network_id=fake_share_network['neutron_net_id'],
|
||||
subnet_id=fake_share_network['neutron_subnet_id'],
|
||||
device_owner='manila:share'),
|
||||
device_owner='manila:share', host_id=None),
|
||||
]
|
||||
db_api_calls = [
|
||||
mock.call(self.fake_context, fake_network_allocation),
|
||||
|
0
manila/tests/share/drivers/container/__init__.py
Normal file
0
manila/tests/share/drivers/container/__init__.py
Normal file
56
manila/tests/share/drivers/container/fakes.py
Normal file
56
manila/tests/share/drivers/container/fakes.py
Normal file
@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
"""Some useful fakes."""
|
||||
|
||||
from manila.tests.db import fakes as db_fakes
|
||||
|
||||
|
||||
def fake_share(**kwargs):
|
||||
share = {
|
||||
'id': 'fakeid',
|
||||
'share_id': 'fakeshareid',
|
||||
'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_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)
|
||||
|
||||
|
||||
def fake_network(**kwargs):
|
||||
allocations = db_fakes.FakeModel({'id': 'fake_allocation_id',
|
||||
'ip_address': '127.0.0.0.1',
|
||||
'mac_address': 'fe:16:3e:61:e0:58'})
|
||||
network = {
|
||||
'id': 'fake_network_id',
|
||||
'server_id': 'fake_server_id',
|
||||
'network_allocations': [allocations],
|
||||
'neutron_subnet_id': 'fake_subnet',
|
||||
}
|
||||
network.update(kwargs)
|
||||
return db_fakes.FakeModel(network)
|
110
manila/tests/share/drivers/container/test_container_helper.py
Normal file
110
manila/tests/share/drivers/container/test_container_helper.py
Normal file
@ -0,0 +1,110 @@
|
||||
# Copyright (c) 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.
|
||||
"""Unit tests for the Container helper module."""
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from manila import exception
|
||||
from manila.share import configuration
|
||||
from manila.share.drivers.container import container_helper
|
||||
from manila import test
|
||||
|
||||
|
||||
class DockerExecHelperTestCase(test.TestCase):
|
||||
"""Tests DockerExecHelper"""
|
||||
|
||||
def setUp(self):
|
||||
super(DockerExecHelperTestCase, self).setUp()
|
||||
self.fake_conf = configuration.Configuration(None)
|
||||
self.fake_conf.container_image_name = "fake_image"
|
||||
self.DockerExecHelper = container_helper.DockerExecHelper(
|
||||
configuration=self.fake_conf)
|
||||
|
||||
def test_start_container(self):
|
||||
self.mock_object(self.DockerExecHelper, "_inner_execute",
|
||||
mock.Mock(return_value=['fake_output', '']))
|
||||
uuid.uuid1 = mock.Mock(return_value='')
|
||||
expected = ['docker', 'run', '-d', '-i', '-t', '--privileged',
|
||||
'--name=manila_cifs_docker_container', '-v',
|
||||
'/tmp/shares:/shares', 'fake_image']
|
||||
|
||||
self.DockerExecHelper.start_container()
|
||||
|
||||
self.DockerExecHelper._inner_execute.assert_called_once_with(expected)
|
||||
|
||||
def test_start_container_impossible_failure(self):
|
||||
self.mock_object(self.DockerExecHelper, "_inner_execute",
|
||||
mock.Mock(return_value=['', 'but how?!']))
|
||||
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.DockerExecHelper.start_container)
|
||||
|
||||
def test_stop_container(self):
|
||||
self.mock_object(self.DockerExecHelper, "_inner_execute",
|
||||
mock.Mock(return_value=['fake_output', '']))
|
||||
expected = ['docker', 'stop', 'manila-fake-conainer']
|
||||
|
||||
self.DockerExecHelper.stop_container("manila-fake-conainer")
|
||||
|
||||
self.DockerExecHelper._inner_execute.assert_called_once_with(expected)
|
||||
|
||||
def test_stop_container_oh_noes(self):
|
||||
self.mock_object(self.DockerExecHelper, "_inner_execute",
|
||||
mock.Mock(return_value=['fake_output',
|
||||
'fake_problem']))
|
||||
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.DockerExecHelper.stop_container,
|
||||
"manila-fake-container")
|
||||
|
||||
def test_execute(self):
|
||||
self.mock_object(self.DockerExecHelper, "_inner_execute",
|
||||
mock.Mock(return_value='fake_output'))
|
||||
expected = ['docker', 'exec', '-i', 'fake_container', 'fake_script']
|
||||
|
||||
self.DockerExecHelper.execute("fake_container", ["fake_script"])
|
||||
|
||||
self.DockerExecHelper._inner_execute.assert_called_once_with(expected)
|
||||
|
||||
def test_execute_name_not_there(self):
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.DockerExecHelper.execute,
|
||||
None, ['do', 'stuff'])
|
||||
|
||||
def test_execute_command_not_there(self):
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.DockerExecHelper.execute,
|
||||
'fake-name', None)
|
||||
|
||||
def test_execute_bad_command_format(self):
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.DockerExecHelper.execute,
|
||||
'fake-name', 'do stuff')
|
||||
|
||||
def test__inner_execute_ok(self):
|
||||
self.DockerExecHelper._execute = mock.Mock(return_value='fake')
|
||||
|
||||
result = self.DockerExecHelper._inner_execute("fake_command")
|
||||
|
||||
self.assertEqual(result, 'fake')
|
||||
|
||||
def test__inner_execute_not_ok(self):
|
||||
self.DockerExecHelper._execute = mock.Mock(return_value='fake')
|
||||
self.DockerExecHelper._execute.side_effect = KeyError()
|
||||
|
||||
result = self.DockerExecHelper._inner_execute("fake_command")
|
||||
|
||||
self.assertEqual(result, None)
|
291
manila/tests/share/drivers/container/test_driver.py
Normal file
291
manila/tests/share/drivers/container/test_driver.py
Normal file
@ -0,0 +1,291 @@
|
||||
# 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.
|
||||
"""Unit tests for the Container driver module."""
|
||||
|
||||
import functools
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share import configuration
|
||||
from manila.share.drivers.container import driver
|
||||
from manila.share.drivers.container import protocol_helper
|
||||
from manila import test
|
||||
from manila.tests import fake_utils
|
||||
from manila.tests.share.drivers.container import fakes as cont_fakes
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('lvm_share_export_ip', 'manila.share.drivers.lvm')
|
||||
|
||||
|
||||
class ContainerShareDriverTestCase(test.TestCase):
|
||||
"""Tests ContainerShareDriver"""
|
||||
|
||||
def setUp(self):
|
||||
super(ContainerShareDriverTestCase, self).setUp()
|
||||
fake_utils.stub_out_utils_execute(self)
|
||||
self._context = context.get_admin_context()
|
||||
self._db = mock.Mock()
|
||||
self.fake_conf = configuration.Configuration(None)
|
||||
|
||||
CONF.set_default('driver_handles_share_servers', True)
|
||||
|
||||
self._driver = driver.ContainerShareDriver(
|
||||
configuration=self.fake_conf)
|
||||
|
||||
self.share = cont_fakes.fake_share()
|
||||
self.access = cont_fakes.fake_access()
|
||||
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 test__get_helper_ok(self):
|
||||
share = cont_fakes.fake_share(share_proto='CIFS')
|
||||
expected = protocol_helper.DockerCIFSHelper(None)
|
||||
|
||||
actual = self._driver._get_helper(share)
|
||||
|
||||
self.assertEqual(type(expected), type(actual))
|
||||
|
||||
def test__get_helper_existing_ok(self):
|
||||
share = cont_fakes.fake_share(share_proto='CIFS')
|
||||
expected = protocol_helper.DockerCIFSHelper
|
||||
self._driver._helpers = {'CIFS': expected}
|
||||
|
||||
actual = self._driver._get_helper(share)
|
||||
|
||||
self.assertEqual(expected, type(actual))
|
||||
|
||||
def test__get_helper_not_ok(self):
|
||||
share = cont_fakes.fake_share()
|
||||
|
||||
self.assertRaises(exception.InvalidShare, self._driver._get_helper,
|
||||
share)
|
||||
|
||||
def test_update_share_stats(self):
|
||||
self.mock_object(self._driver.storage, 'get_share_server_pools',
|
||||
mock.Mock(return_value='test-pool'))
|
||||
|
||||
self._driver._update_share_stats()
|
||||
|
||||
self.assertEqual('Docker', self._driver._stats['share_backend_name'])
|
||||
self.assertEqual('CIFS', self._driver._stats['storage_protocol'])
|
||||
self.assertEqual(0, self._driver._stats['reserved_percentage'])
|
||||
self.assertIsNone(self._driver._stats['consistency_group_support'])
|
||||
self.assertEqual(False, self._driver._stats['snapshot_support'])
|
||||
self.assertEqual('ContainerShareDriver',
|
||||
self._driver._stats['driver_name'])
|
||||
self.assertEqual('test-pool', self._driver._stats['pools'])
|
||||
|
||||
def test_create_share(self):
|
||||
helper = mock.Mock()
|
||||
self.mock_object(helper, 'create_share',
|
||||
mock.Mock(return_value='export_location'))
|
||||
self.mock_object(self._driver, "_get_helper",
|
||||
mock.Mock(return_value=helper))
|
||||
self.mock_object(self._driver.storage, 'provide_storage')
|
||||
self.mock_object(self._driver.container, 'execute')
|
||||
|
||||
self.assertEqual('export_location',
|
||||
self._driver.create_share(self._context, self.share,
|
||||
{'id': 'fake'}))
|
||||
|
||||
def test_delete_share(self):
|
||||
helper = mock.Mock()
|
||||
self.mock_object(self._driver, "_get_helper",
|
||||
mock.Mock(return_value=helper))
|
||||
self.mock_object(self._driver.container, 'execute')
|
||||
self.mock_object(self._driver.storage, 'remove_storage')
|
||||
|
||||
self._driver.delete_share(self._context, self.share, {'id': 'fake'})
|
||||
|
||||
self._driver.container.execute.assert_called_with(
|
||||
'manila_fake',
|
||||
['rm', '-fR', '/shares/fakeshareid']
|
||||
)
|
||||
|
||||
def test_extend_share(self):
|
||||
share = cont_fakes.fake_share()
|
||||
self.mock_object(self._driver.storage, "extend_share")
|
||||
|
||||
self._driver.extend_share(share, 2, 'fake-server')
|
||||
|
||||
def test_ensure_share(self):
|
||||
# Does effectively nothing by design.
|
||||
self.assertEqual(1, 1)
|
||||
|
||||
def test_update_access_access_rules_ok(self):
|
||||
helper = mock.Mock()
|
||||
self.mock_object(self._driver, "_get_helper",
|
||||
mock.Mock(return_value=helper))
|
||||
|
||||
self._driver.update_access(self._context, self.share,
|
||||
[{'access_level': const.ACCESS_LEVEL_RW}],
|
||||
[], [], {"id": "fake"})
|
||||
|
||||
helper.update_access.assert_called_with('manila_fake',
|
||||
[{'access_level': 'rw'}],
|
||||
[], [])
|
||||
|
||||
def test_get_network_allocation_numer(self):
|
||||
# Does effectively nothing by design.
|
||||
self.assertEqual(1, self._driver.get_network_allocations_number())
|
||||
|
||||
def test__get_container_name(self):
|
||||
self.assertEqual("manila_fake_server",
|
||||
self._driver._get_container_name("fake-server"))
|
||||
|
||||
def test_do_setup(self):
|
||||
# Does effectively nothing by design.
|
||||
self.assertEqual(1, 1)
|
||||
|
||||
def test_check_for_setup_error_host_not_ok_class_ok(self):
|
||||
setattr(self._driver.configuration.local_conf,
|
||||
'neutron_host_id', None)
|
||||
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver.check_for_setup_error)
|
||||
|
||||
def test_check_for_setup_error_host_not_ok_class_some_other(self):
|
||||
setattr(self._driver.configuration.local_conf,
|
||||
'neutron_host_id', None)
|
||||
setattr(self._driver.configuration.local_conf,
|
||||
'network_api_class',
|
||||
'manila.share.drivers.container.driver.ContainerShareDriver')
|
||||
driver.LOG.warning = mock.Mock()
|
||||
|
||||
self._driver.check_for_setup_error()
|
||||
|
||||
setattr(self._driver.configuration.local_conf,
|
||||
'network_api_class',
|
||||
'manila.network.neutron.neutron_network_plugin.'
|
||||
'NeutronNetworkPlugin')
|
||||
|
||||
self.assertTrue(driver.LOG.warning.called)
|
||||
|
||||
def test__connect_to_network(self):
|
||||
network_info = cont_fakes.fake_network()
|
||||
helper = mock.Mock()
|
||||
self.mock_object(self._driver, "_execute",
|
||||
mock.Mock(return_value=helper))
|
||||
self.mock_object(self._driver.container, "execute")
|
||||
|
||||
self._driver._connect_to_network("fake-server", network_info,
|
||||
"fake-veth")
|
||||
|
||||
def test__teardown_server(self):
|
||||
def fake_ovs_execute(*args, **kwargs):
|
||||
kwargs['arguments'].append(args)
|
||||
if len(args) == 3:
|
||||
return ['veth0000000']
|
||||
elif len(args) == 4:
|
||||
return ('fake:manila_b5afb5c1_6011_43c4_8a37_29820e6951a7', '')
|
||||
else:
|
||||
return 0
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('ovs-vsctl', 'list', 'interface'),
|
||||
('ovs-vsctl', 'list', 'interface', 'veth0000000'),
|
||||
('ovs-vsctl', '--', 'del-port', 'br-int', 'veth0000000')
|
||||
]
|
||||
self.mock_object(self._driver.container, "stop_container", mock.Mock())
|
||||
self._driver._execute = functools.partial(
|
||||
fake_ovs_execute, arguments=actual_arguments)
|
||||
|
||||
self._driver._teardown_server(
|
||||
server_details={"id": "b5afb5c1-6011-43c4-8a37-29820e6951a7"})
|
||||
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test__teardown_server_check_continuation(self):
|
||||
def fake_ovs_execute(*args, **kwargs):
|
||||
kwargs['arguments'].append(args)
|
||||
if len(args) == 3:
|
||||
return ['veth0000000']
|
||||
elif len(args) == 4:
|
||||
return ('fake:', '')
|
||||
else:
|
||||
return 0
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('ovs-vsctl', 'list', 'interface'),
|
||||
('ovs-vsctl', 'list', 'interface', 'veth0000000'),
|
||||
('ovs-vsctl', '--', 'del-port', 'br-int', 'veth0000000')
|
||||
]
|
||||
self.mock_object(self._driver.container, "stop_container", mock.Mock())
|
||||
self._driver._execute = functools.partial(
|
||||
fake_ovs_execute, arguments=actual_arguments)
|
||||
|
||||
self._driver._teardown_server(
|
||||
server_details={"id": "b5afb5c1-6011-43c4-8a37-29820e6951a7"})
|
||||
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test__get_veth_state(self):
|
||||
retval = ('veth0000000\n', '')
|
||||
self.mock_object(self._driver, "_execute",
|
||||
mock.Mock(return_value=retval))
|
||||
|
||||
result = self._driver._get_veth_state()
|
||||
|
||||
self.assertEqual(['veth0000000'], result)
|
||||
|
||||
def test__get_corresponding_veth_ok(self):
|
||||
before = ['veth0000000']
|
||||
after = ['veth0000000', 'veth0000001']
|
||||
|
||||
result = self._driver._get_corresponding_veth(before, after)
|
||||
|
||||
self.assertEqual('veth0000001', result)
|
||||
|
||||
def test__get_corresponding_veth_raises(self):
|
||||
before = ['veth0000000']
|
||||
after = ['veth0000000', 'veth0000001', 'veth0000002']
|
||||
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver._get_corresponding_veth,
|
||||
before, after)
|
||||
|
||||
def test__setup_server_container_fails(self):
|
||||
network_info = cont_fakes.fake_network()
|
||||
self.mock_object(self._driver.container, 'start_container')
|
||||
self._driver.container.start_container.side_effect = KeyError()
|
||||
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver._setup_server, network_info)
|
||||
|
||||
def test__setup_server_ok(self):
|
||||
network_info = cont_fakes.fake_network()
|
||||
server_id = self._driver._get_container_name(network_info["server_id"])
|
||||
self.mock_object(self._driver.container, 'start_container')
|
||||
self.mock_object(self._driver, '_get_veth_state')
|
||||
self.mock_object(self._driver, '_get_corresponding_veth',
|
||||
mock.Mock(return_value='veth0'))
|
||||
self.mock_object(self._driver, '_connect_to_network')
|
||||
|
||||
self.assertEqual(network_info['server_id'],
|
||||
self._driver._setup_server(network_info)['id'])
|
||||
self._driver.container.start_container.assert_called_once_with(
|
||||
server_id)
|
||||
self._driver._connect_to_network.assert_called_once_with(server_id,
|
||||
network_info,
|
||||
'veth0')
|
295
manila/tests/share/drivers/container/test_protocol_helper.py
Normal file
295
manila/tests/share/drivers/container/test_protocol_helper.py
Normal file
@ -0,0 +1,295 @@
|
||||
# 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.
|
||||
"""Unit tests for the Protocol helper module."""
|
||||
|
||||
import functools
|
||||
import mock
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.share.drivers.container import protocol_helper
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.container.fakes import fake_share
|
||||
|
||||
|
||||
class DockerCIFSHelperTestCase(test.TestCase):
|
||||
"""Tests ContainerShareDriver"""
|
||||
|
||||
def setUp(self):
|
||||
super(DockerCIFSHelperTestCase, self).setUp()
|
||||
self._helper = mock.Mock()
|
||||
self.fake_conf = mock.Mock()
|
||||
self.fake_conf.container_cifs_guest_ok = "yes"
|
||||
self.DockerCIFSHelper = protocol_helper.DockerCIFSHelper(
|
||||
self._helper, share=fake_share(), config=self.fake_conf)
|
||||
|
||||
def fake_exec_sync(self, *args, **kwargs):
|
||||
kwargs["execute_arguments"].append(args)
|
||||
try:
|
||||
ret_val = kwargs["ret_val"]
|
||||
except KeyError:
|
||||
ret_val = None
|
||||
return [ret_val]
|
||||
|
||||
def test_create_share_guest_ok(self):
|
||||
expected_arguments = [
|
||||
("fakeserver", ["net", "conf", "addshare", "fakeshareid",
|
||||
"/shares/fakeshareid", "writeable=y", "guest_ok=y"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"browseable", "yes"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"hosts allow", "127.0.0.1"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"read only", "no"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"hosts deny", "0.0.0.0/0"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"create mask", "0755"])]
|
||||
actual_arguments = []
|
||||
self._helper.execute = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val=" fake 192.0.2.2/24 more fake \n" * 20)
|
||||
self.DockerCIFSHelper.share = fake_share()
|
||||
|
||||
self.DockerCIFSHelper.create_share("fakeserver")
|
||||
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test_create_share_guest_not_ok(self):
|
||||
self.DockerCIFSHelper.conf = mock.Mock()
|
||||
self.DockerCIFSHelper.conf.container_cifs_guest_ok = False
|
||||
expected_arguments = [
|
||||
("fakeserver", ["net", "conf", "addshare", "fakeshareid",
|
||||
"/shares/fakeshareid", "writeable=y", "guest_ok=n"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"browseable", "yes"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"hosts allow", "192.0.2.2"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"read only", "no"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"hosts deny", "0.0.0.0/0"]),
|
||||
("fakeserver", ["net", "conf", "setparm", "fakeshareid",
|
||||
"create mask", "0755"])]
|
||||
actual_arguments = []
|
||||
self._helper.execute = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val=" fake 192.0.2.2/24 more fake \n" * 20)
|
||||
self.DockerCIFSHelper.share = fake_share()
|
||||
|
||||
self.DockerCIFSHelper.create_share("fakeserver")
|
||||
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test_delete_share(self):
|
||||
self.DockerCIFSHelper.share = fake_share()
|
||||
|
||||
self.DockerCIFSHelper.delete_share("fakeserver")
|
||||
|
||||
self.DockerCIFSHelper.container.execute.assert_called_with(
|
||||
"fakeserver",
|
||||
["net", "conf", "delshare", "fakeshareid"])
|
||||
|
||||
def test__get_access_group_ro(self):
|
||||
result = self.DockerCIFSHelper._get_access_group(const.ACCESS_LEVEL_RO)
|
||||
|
||||
self.assertEqual("read list", result)
|
||||
|
||||
def test__get_access_group_rw(self):
|
||||
result = self.DockerCIFSHelper._get_access_group(const.ACCESS_LEVEL_RW)
|
||||
|
||||
self.assertEqual("valid users", result)
|
||||
|
||||
def test__get_access_group_other(self):
|
||||
self.assertRaises(exception.InvalidShareAccessLevel,
|
||||
self.DockerCIFSHelper._get_access_group,
|
||||
"fake_level")
|
||||
|
||||
def test__get_existing_users(self):
|
||||
self.DockerCIFSHelper.container.execute = mock.Mock(
|
||||
return_value=("fake_user", ""))
|
||||
|
||||
result = self.DockerCIFSHelper._get_existing_users("fake_server_id",
|
||||
"fake_share",
|
||||
"fake_access")
|
||||
|
||||
self.assertEqual("fake_user", result)
|
||||
self.DockerCIFSHelper.container.execute.assert_called_once_with(
|
||||
"fake_server_id",
|
||||
["net", "conf", "getparm", "fake_share", "fake_access"])
|
||||
|
||||
def test__set_users(self):
|
||||
self.DockerCIFSHelper.container.execute = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper._set_users("fake_server_id", "fake_share",
|
||||
"fake_access", "fake_user")
|
||||
|
||||
self.DockerCIFSHelper.container.execute.assert_called_once_with(
|
||||
"fake_server_id",
|
||||
["net", "conf", "setparm", "fake_share", "fake_access",
|
||||
"fake_user"])
|
||||
|
||||
def test__allow_access_ok(self):
|
||||
self.DockerCIFSHelper._get_access_group = mock.Mock(
|
||||
return_value="valid users")
|
||||
self.DockerCIFSHelper._get_existing_users = mock.Mock(
|
||||
return_value="fake_user")
|
||||
self.DockerCIFSHelper._set_users = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper._allow_access("fake_share", "fake_server_id",
|
||||
"fake_user2", "rw")
|
||||
|
||||
self.DockerCIFSHelper._get_access_group.assert_called_once_with("rw")
|
||||
self.DockerCIFSHelper._get_existing_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users")
|
||||
self.DockerCIFSHelper._set_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users",
|
||||
"fake_user fake_user2")
|
||||
|
||||
def test__allow_access_not_ok(self):
|
||||
self.DockerCIFSHelper._get_access_group = mock.Mock(
|
||||
return_value="valid users")
|
||||
self.DockerCIFSHelper._get_existing_users = mock.Mock()
|
||||
self.DockerCIFSHelper._get_existing_users.side_effect = TypeError
|
||||
self.DockerCIFSHelper._set_users = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper._allow_access("fake_share", "fake_server_id",
|
||||
"fake_user2", "rw")
|
||||
|
||||
self.DockerCIFSHelper._get_access_group.assert_called_once_with("rw")
|
||||
self.DockerCIFSHelper._get_existing_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users")
|
||||
self.DockerCIFSHelper._set_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users",
|
||||
"fake_user2")
|
||||
|
||||
def test__deny_access_ok(self):
|
||||
self.DockerCIFSHelper._get_access_group = mock.Mock(
|
||||
return_value="valid users")
|
||||
self.DockerCIFSHelper._get_existing_users = mock.Mock(
|
||||
return_value="fake_user fake_user2")
|
||||
self.DockerCIFSHelper._set_users = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper._deny_access("fake_share", "fake_server_id",
|
||||
"fake_user2", "rw")
|
||||
|
||||
self.DockerCIFSHelper._get_access_group.assert_called_once_with("rw")
|
||||
self.DockerCIFSHelper._get_existing_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users")
|
||||
self.DockerCIFSHelper._set_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users",
|
||||
"fake_user")
|
||||
|
||||
def test__deny_access_ok_so_many_users(self):
|
||||
self.DockerCIFSHelper._get_access_group = mock.Mock(
|
||||
return_value="valid users")
|
||||
self.DockerCIFSHelper._get_existing_users = mock.Mock(
|
||||
return_value="joost jaap huub dirk")
|
||||
self.DockerCIFSHelper._set_users = mock.Mock()
|
||||
|
||||
# Sorry, Jaap.
|
||||
self.DockerCIFSHelper._deny_access("fake_share", "fake_server_id",
|
||||
"jaap", "rw")
|
||||
|
||||
self.DockerCIFSHelper._get_access_group.assert_called_once_with("rw")
|
||||
self.DockerCIFSHelper._get_existing_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users")
|
||||
self.DockerCIFSHelper._set_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users",
|
||||
"dirk huub joost")
|
||||
|
||||
def test__deny_access_not_ok(self):
|
||||
self.DockerCIFSHelper._get_access_group = mock.Mock(
|
||||
return_value="valid users")
|
||||
self.DockerCIFSHelper._get_existing_users = mock.Mock()
|
||||
self.DockerCIFSHelper._get_existing_users.side_effect = TypeError
|
||||
self.DockerCIFSHelper._set_users = mock.Mock()
|
||||
protocol_helper.LOG.warning = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper._deny_access("fake_share", "fake_server_id",
|
||||
"fake_user2", "rw")
|
||||
|
||||
self.DockerCIFSHelper._get_access_group.assert_called_once_with("rw")
|
||||
self.DockerCIFSHelper._get_existing_users.assert_called_once_with(
|
||||
"fake_server_id", "fake_share", "valid users")
|
||||
self.assertFalse(self.DockerCIFSHelper._set_users.called)
|
||||
self.assertTrue(protocol_helper.LOG.warning.called)
|
||||
|
||||
def test_update_access_access_rules_wrong_type(self):
|
||||
allow_rules = [{
|
||||
"access_to": "192.0.2.2",
|
||||
"access_level": "ro",
|
||||
"access_type": "fake"
|
||||
}]
|
||||
self.mock_object(self.DockerCIFSHelper, "_allow_access")
|
||||
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.DockerCIFSHelper.update_access,
|
||||
"fakeserver", allow_rules, [], [])
|
||||
|
||||
def test_update_access_access_rules_ok(self):
|
||||
access_rules = [{
|
||||
"access_to": "fakeuser",
|
||||
"access_level": "ro",
|
||||
"access_type": "user"
|
||||
}]
|
||||
self.mock_object(self.DockerCIFSHelper, "_allow_access")
|
||||
self.DockerCIFSHelper.container.execute = mock.Mock()
|
||||
|
||||
self.DockerCIFSHelper.update_access("fakeserver",
|
||||
access_rules, [], [])
|
||||
|
||||
self.DockerCIFSHelper._allow_access.assert_called_once_with(
|
||||
"fakeshareid",
|
||||
"fakeserver",
|
||||
"fakeuser",
|
||||
"ro")
|
||||
self.DockerCIFSHelper.container.execute.assert_called_once_with(
|
||||
"fakeserver",
|
||||
["net", "conf", "setparm", "fakeshareid", "valid users", ""])
|
||||
|
||||
def test_update_access_add_rules(self):
|
||||
add_rules = [{
|
||||
"access_to": "fakeuser",
|
||||
"access_level": "ro",
|
||||
"access_type": "user"
|
||||
}]
|
||||
self.mock_object(self.DockerCIFSHelper, "_allow_access")
|
||||
|
||||
self.DockerCIFSHelper.update_access("fakeserver", [],
|
||||
add_rules, [])
|
||||
|
||||
self.DockerCIFSHelper._allow_access.assert_called_once_with(
|
||||
"fakeshareid",
|
||||
"fakeserver",
|
||||
"fakeuser",
|
||||
"ro")
|
||||
|
||||
def test_update_access_delete_rules(self):
|
||||
delete_rules = [{
|
||||
"access_to": "fakeuser",
|
||||
"access_level": "ro",
|
||||
"access_type": "user"
|
||||
}]
|
||||
self.mock_object(self.DockerCIFSHelper, "_deny_access")
|
||||
|
||||
self.DockerCIFSHelper.update_access("fakeserver", [],
|
||||
[], delete_rules)
|
||||
|
||||
self.DockerCIFSHelper._deny_access.assert_called_once_with(
|
||||
"fakeshareid",
|
||||
"fakeserver",
|
||||
"fakeuser",
|
||||
"ro")
|
121
manila/tests/share/drivers/container/test_storage_helper.py
Normal file
121
manila/tests/share/drivers/container/test_storage_helper.py
Normal file
@ -0,0 +1,121 @@
|
||||
# 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.
|
||||
"""Unit tests for the Storage helper module."""
|
||||
|
||||
import functools
|
||||
import mock
|
||||
|
||||
from manila import exception
|
||||
from manila.share import configuration
|
||||
from manila.share.drivers.container import storage_helper
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.container.fakes import fake_share
|
||||
|
||||
|
||||
class LVMHelperTestCase(test.TestCase):
|
||||
"""Tests ContainerShareDriver"""
|
||||
|
||||
def setUp(self):
|
||||
super(LVMHelperTestCase, self).setUp()
|
||||
self.share = fake_share()
|
||||
self.fake_conf = configuration.Configuration(None)
|
||||
self.LVMHelper = storage_helper.LVMHelper(configuration=self.fake_conf)
|
||||
|
||||
def fake_exec_sync(self, *args, **kwargs):
|
||||
kwargs['execute_arguments'].append(args)
|
||||
try:
|
||||
ret_val = kwargs['ret_val']
|
||||
except KeyError:
|
||||
ret_val = None
|
||||
return ret_val
|
||||
|
||||
def test_lvmhelper_setup_explodes_in_gore_on_no_config_supplied(self):
|
||||
self.assertRaises(exception.ManilaException,
|
||||
storage_helper.LVMHelper,
|
||||
None)
|
||||
|
||||
def test_get_share_server_pools(self):
|
||||
ret_vgs = "VSize 100g size\nVFree 100g whatever"
|
||||
expected_result = [{'reserved_percentage': 0,
|
||||
'pool_name': 'manila_docker_volumes',
|
||||
'total_capacity_gb': 100.0,
|
||||
'free_capacity_gb': 100.0}]
|
||||
self.mock_object(self.LVMHelper, "_execute",
|
||||
mock.Mock(return_value=(ret_vgs, 0)))
|
||||
|
||||
result = self.LVMHelper.get_share_server_pools()
|
||||
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test__get_lv_device(self):
|
||||
self.assertEqual("/dev/manila_docker_volumes/fakeshareid",
|
||||
self.LVMHelper._get_lv_device(self.share))
|
||||
|
||||
def test__get_lv_folder(self):
|
||||
self.assertEqual("/tmp/shares/fakeshareid",
|
||||
self.LVMHelper._get_lv_folder(self.share))
|
||||
|
||||
def test_provide_storage(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('lvcreate', '-p', 'rw', '-L', '1G', '-n', 'fakeshareid',
|
||||
'manila_docker_volumes'),
|
||||
('mkfs.ext4', '/dev/manila_docker_volumes/fakeshareid'),
|
||||
('mount', '/dev/manila_docker_volumes/fakeshareid',
|
||||
'/tmp/shares/fakeshareid'),
|
||||
('chmod', '-R', '750', '/tmp/shares/fakeshareid'),
|
||||
('chown', 'nobody:nogroup', '/tmp/shares/fakeshareid')
|
||||
]
|
||||
self.LVMHelper._execute = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
|
||||
self.LVMHelper.provide_storage(self.share)
|
||||
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test_remove_storage(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('umount', '/dev/manila_docker_volumes/fakeshareid'),
|
||||
('lvremove', '-f', '--autobackup', 'n',
|
||||
'/dev/manila_docker_volumes/fakeshareid')
|
||||
]
|
||||
self.LVMHelper._execute = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
|
||||
self.LVMHelper.remove_storage(self.share)
|
||||
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test_extend_share(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('umount', '/tmp/shares/fakeshareid'),
|
||||
('lvextend', '-L', 'shareG', '-n',
|
||||
'/dev/manila_docker_volumes/fakeshareid'),
|
||||
('e2fsck', '-f', '-y', '/dev/manila_docker_volumes/fakeshareid'),
|
||||
('resize2fs', '/dev/manila_docker_volumes/fakeshareid'),
|
||||
('mount', '/dev/manila_docker_volumes/fakeshareid',
|
||||
'/tmp/shares/fakeshareid')
|
||||
]
|
||||
self.LVMHelper._execute = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
|
||||
self.LVMHelper.extend_share(self.share, 'share', 3)
|
||||
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
14
releasenotes/notes/container-driver-5d972cc40e314663.yaml
Normal file
14
releasenotes/notes/container-driver-5d972cc40e314663.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
prelude: >
|
||||
A new Container driver is added. It uses docker container as
|
||||
a share server.
|
||||
features:
|
||||
- The Container driver allows using a docker container as a share server.
|
||||
This allows for very fast share server startup.
|
||||
- The Container driver supports CIFS protocol.
|
||||
issues:
|
||||
- |
|
||||
The Container driver has the following known issues:
|
||||
|
||||
* Only basic driver operations are supported: create/delete share,
|
||||
update access and extend share.
|
Loading…
Reference in New Issue
Block a user