LXC/LXD driver
This driver exploits a container as a share server instead of a virtual machine. The container acts as a NFS/CIFS server and shares logical volume attached to it. The volume gets created simultaneously to the container on a host in a volume group specified by the user. Currently NFS is limited to v3 since the only userspace NFS server that works in a container out of the box seems to be unfs3. Later it will be replaced with nfs-ganesha. Co-Authored-By: Alexey Ovchinnikov <aovchinnikov@mirantis.com> Implements: blueprint lxc-driver Depends-On: Ib1896997f2e7a505b5bf8ec0c9e5ee35942f79a6 Change-Id: Iea73bd34b94919d77ebe17cf054dde1f580384be
This commit is contained in:
parent
cdd2fb95b0
commit
4f74b224fa
@ -25,6 +25,21 @@ source $BASE/new/devstack/functions
|
||||
|
||||
export TEMPEST_CONFIG=$BASE/new/tempest/etc/tempest.conf
|
||||
|
||||
# provide_user_rules - Sets up Samba for drivers which rely on LVM.
|
||||
function provide_user_rules {
|
||||
if ! grep $USERNAME_FOR_USER_RULES "/etc/passwd"; then
|
||||
sudo useradd $USERNAME_FOR_USER_RULES
|
||||
fi
|
||||
(echo $PASSWORD_FOR_SAMBA_USER; echo $PASSWORD_FOR_SAMBA_USER) | sudo smbpasswd -s -a $USERNAME_FOR_USER_RULES
|
||||
sudo smbpasswd -e $USERNAME_FOR_USER_RULES
|
||||
samba_daemon_name=smbd
|
||||
if is_fedora; then
|
||||
samba_daemon_name=smb
|
||||
fi
|
||||
sudo service $samba_daemon_name restart
|
||||
}
|
||||
|
||||
|
||||
# === Handle script arguments ===
|
||||
|
||||
# First argument is expected to contain value equal either to 'singlebackend'
|
||||
@ -149,16 +164,7 @@ if [[ "$DRIVER" == "lvm" ]]; then
|
||||
iniset $TEMPEST_CONFIG share run_migration_tests False
|
||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||
if ! grep $USERNAME_FOR_USER_RULES "/etc/passwd"; then
|
||||
sudo useradd $USERNAME_FOR_USER_RULES
|
||||
fi
|
||||
(echo $PASSWORD_FOR_SAMBA_USER; echo $PASSWORD_FOR_SAMBA_USER) | sudo smbpasswd -s -a $USERNAME_FOR_USER_RULES
|
||||
sudo smbpasswd -e $USERNAME_FOR_USER_RULES
|
||||
samba_daemon_name=smbd
|
||||
if is_fedora; then
|
||||
samba_daemon_name=smb
|
||||
fi
|
||||
sudo service $samba_daemon_name restart
|
||||
provide_user_rules
|
||||
elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
||||
MANILA_TEMPEST_CONCURRENCY=8
|
||||
RUN_MANILA_CG_TESTS=False
|
||||
@ -179,6 +185,17 @@ elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
||||
iniset $TEMPEST_CONFIG share multitenancy_enabled False
|
||||
iniset $TEMPEST_CONFIG share multi_backend True
|
||||
iniset $TEMPEST_CONFIG share backend_replication_type 'readable'
|
||||
elif [[ "$DRIVER" == "lxd" ]]; then
|
||||
MANILA_TEMPEST_CONCURRENCY=1
|
||||
RUN_MANILA_CG_TESTS=False
|
||||
RUN_MANILA_MANAGE_TESTS=False
|
||||
iniset $TEMPEST_CONFIG share run_shrink_tests False
|
||||
iniset $TEMPEST_CONFIG share run_consistency_group_tests False
|
||||
iniset $TEMPEST_CONFIG share run_snapshot_tests False
|
||||
iniset $TEMPEST_CONFIG share run_migration_tests False
|
||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||
provide_user_rules
|
||||
fi
|
||||
|
||||
# Enable consistency group tests
|
||||
|
@ -69,6 +69,11 @@ elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
||||
echo "RUN_MANILA_REPLICATION_TESTS=True" >> $localrc_path
|
||||
fi
|
||||
|
||||
if [[ "$DRIVER" == "lxd" ]]; then
|
||||
echo "SHARE_DRIVER=manila.share.drivers.lxd.LXDDriver" >> $localrc_path
|
||||
echo "SHARE_BACKING_FILE_SIZE=32000M" >> $localrc_path
|
||||
fi
|
||||
|
||||
# Enabling isolated metadata in Neutron is required because
|
||||
# Tempest creates isolated networks and created vm's in scenario tests don't
|
||||
# have access to Nova Metadata service. This leads to unavailability of
|
||||
|
@ -57,6 +57,12 @@ function cleanup_manila {
|
||||
_clean_share_group $SHARE_GROUP $SHARE_NAME_PREFIX
|
||||
_clean_manila_lvm_backing_file $SHARE_GROUP
|
||||
_clean_zfsonlinux_data
|
||||
|
||||
if is_driver_enabled $MANILA_LXD_DRIVER; then
|
||||
# Remove all containers created by manila
|
||||
lxc delete `lxc list | grep -E 'manila-.*' | awk '{print $2}'`
|
||||
remove_lxd_service_image
|
||||
fi
|
||||
}
|
||||
|
||||
# configure_default_backends - configures default Manila backends with generic driver.
|
||||
@ -263,6 +269,18 @@ function create_manila_service_keypair {
|
||||
nova keypair-add $MANILA_SERVICE_KEYPAIR_NAME --pub-key $MANILA_PATH_TO_PUBLIC_KEY
|
||||
}
|
||||
|
||||
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.
|
||||
@ -326,6 +344,11 @@ function create_manila_service_image {
|
||||
if is_service_enabled g-reg; then
|
||||
upload_image $MANILA_SERVICE_IMAGE_URL $TOKEN
|
||||
fi
|
||||
|
||||
# Download LXD image
|
||||
if is_driver_enabled $MANILA_LXD_DRIVER; then
|
||||
import_lxd_service_image
|
||||
fi
|
||||
}
|
||||
|
||||
# create_manila_service_secgroup - creates security group that is used by
|
||||
@ -402,13 +425,40 @@ function create_default_share_type {
|
||||
enabled_backends=(${MANILA_ENABLED_BACKENDS//,/ })
|
||||
driver_handles_share_servers=$(iniget $MANILA_CONF ${enabled_backends[0]} driver_handles_share_servers)
|
||||
|
||||
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_LXD_DRIVER; then
|
||||
# TODO(u_glide): Remove this condition when LXD driver supports
|
||||
# snapshots
|
||||
command_args="$command_args --snapshot_support false"
|
||||
fi
|
||||
|
||||
manila type-create $command_args
|
||||
if [[ $MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS ]]; then
|
||||
manila type-key $MANILA_DEFAULT_SHARE_TYPE set $MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
|
||||
# init_manila - Initializes database and creates manila dir if absent
|
||||
function init_manila {
|
||||
|
||||
@ -438,20 +488,14 @@ 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
|
||||
configure_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
|
||||
if [ "$SHARE_DRIVER" == "manila.share.drivers.lxd.LXDDriver" ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
SHARE_GROUP="manila_lxd_volumes"
|
||||
configure_backing_file
|
||||
fi
|
||||
elif [ "$SHARE_DRIVER" == "manila.share.drivers.zfsonlinux.driver.ZFSonLinuxShareDriver" ]; then
|
||||
if is_service_enabled m-shr; then
|
||||
@ -611,6 +655,60 @@ function update_tempest {
|
||||
fi
|
||||
}
|
||||
|
||||
function install_lxd {
|
||||
sudo apt-get -y install software-properties-common
|
||||
sudo apt-add-repository -y ppa:ubuntu-lxc/lxd-stable
|
||||
sudo apt-get update
|
||||
install_package lxd
|
||||
install_package lxd-client
|
||||
|
||||
# Wait for LXD service
|
||||
sleep 15
|
||||
|
||||
sudo chmod a+rw /var/lib/lxd/unix.socket
|
||||
}
|
||||
|
||||
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_lxd_service_image {
|
||||
download_image $MANILA_LXD_META_URL
|
||||
download_image $MANILA_LXD_ROOTFS_URL
|
||||
|
||||
# Import image in LXD
|
||||
lxc image import $FILES/`basename "$MANILA_LXD_META_URL"` $FILES/`basename "$MANILA_LXD_ROOTFS_URL"` --alias manila-lxd-image
|
||||
}
|
||||
|
||||
function remove_lxd_service_image {
|
||||
lxc image delete $MANILA_LXD_IMAGE_ALIAS || echo "LXD image $MANILA_LXD_IMAGE_ALIAS not found"
|
||||
}
|
||||
|
||||
function install_libraries {
|
||||
if [ $(trueorfalse False MANILA_MULTI_BACKEND) == True ]; then
|
||||
if [ $(trueorfalse True RUN_MANILA_MIGRATION_TESTS) == True ]; then
|
||||
@ -621,6 +719,10 @@ function install_libraries {
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if is_driver_enabled $MANILA_LXD_DRIVER; then
|
||||
install_lxd
|
||||
fi
|
||||
}
|
||||
|
||||
# Main dispatcher
|
||||
|
@ -154,6 +154,16 @@ MANILA_ZFSONLINUX_SSH_USERNAME=${MANILA_ZFSONLINUX_SSH_USERNAME:-"stack"}
|
||||
# Manila will consider replication feature as disabled for ZFSonLinux share driver.
|
||||
MANILA_ZFSONLINUX_REPLICATION_DOMAIN=${MANILA_ZFSONLINUX_REPLICATION_DOMAIN:-"ZFSonLinux"}
|
||||
|
||||
# LXD Driver
|
||||
MANILA_LXD_DRIVER=${MANILA_LXD_DRIVER:-"manila.share.drivers.lxd.LXDDriver"}
|
||||
MANILA_LXD_IMAGE_ALIAS=${MANILA_LXD_IMAGE_ALIAS:-"manila-lxd-image"}
|
||||
# (aovchinnikov): This location is temporary and will be changed to a
|
||||
# permanent one as soon as possible.
|
||||
MANILA_LXD_META_URL=${MANILA_LXD_META_URL:-"https://github.com/a-ovchinnikov/manila-image-elements-lxd-images/releases/download/0.1.0/manila-lxd-meta.tar"}
|
||||
# (aovchinnikov): This location is temporary and will be changed to a
|
||||
# permanent one as soon as possible.
|
||||
MANILA_LXD_ROOTFS_URL=${MANILA_LXD_ROOTFS_URL:-"https://github.com/a-ovchinnikov/manila-image-elements-lxd-images/releases/download/0.1.0/manila-service-image.tar.xz"}
|
||||
|
||||
# Enable manila services
|
||||
# ----------------------
|
||||
# We have to add Manila to enabled services for screen_it to work
|
||||
|
@ -59,6 +59,8 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| LVM | DHSS = False (M) | \- | M | \- | M | M | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| LXD | DHSS = True (M) | \- | M | \- | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Quobyte | DHSS = False (K) | \- | M | M | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Windows SMB | DHSS = True (L) & False (L) | L | L | L | L | L | \- |
|
||||
@ -104,6 +106,8 @@ Mapping of share drivers and share access rules support
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| LVM | NFS (M) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| LXD | NFS,CIFS (M) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| Quobyte | NFS (K) | \- | \- | \- | NFS (K) | \- | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| Windows SMB | \- | CIFS (L) | \- | \- | \- | CIFS (L) | \- | \- |
|
||||
@ -145,6 +149,8 @@ Mapping of share drivers and security services support
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| LVM | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| LXD | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Quobyte | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Windows SMB | L | \- | \- |
|
||||
|
@ -148,3 +148,9 @@ zfs: CommandFilter, zfs, root
|
||||
|
||||
# manila/share/drivers/zfsonlinux/driver.py
|
||||
nsenter: CommandFilter, /usr/local/bin/nsenter, root
|
||||
|
||||
# LXD driver commands
|
||||
# manila/share/drivers/lxd.py
|
||||
lxc: CommandFilter, lxc, root
|
||||
# manila/share/drivers/lxd.py
|
||||
brctl: CommandFilter, brctl, 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'],
|
||||
|
@ -64,6 +64,7 @@ import manila.share.drivers.hitachi.hds_hnas
|
||||
import manila.share.drivers.hpe.hpe_3par_driver
|
||||
import manila.share.drivers.huawei.huawei_nas
|
||||
import manila.share.drivers.ibm.gpfs
|
||||
import manila.share.drivers.lxd
|
||||
import manila.share.drivers.netapp.options
|
||||
import manila.share.drivers.quobyte.quobyte
|
||||
import manila.share.drivers.service_instance
|
||||
@ -128,6 +129,8 @@ _global_opt_lists = [
|
||||
manila.share.drivers.hpe.hpe_3par_driver.HPE3PAR_OPTS,
|
||||
manila.share.drivers.huawei.huawei_nas.huawei_opts,
|
||||
manila.share.drivers.ibm.gpfs.gpfs_share_opts,
|
||||
manila.share.drivers.lxd.lxd_opts,
|
||||
manila.share.drivers.lxd.lv_opts,
|
||||
manila.share.drivers.netapp.options.netapp_proxy_opts,
|
||||
manila.share.drivers.netapp.options.netapp_connection_opts,
|
||||
manila.share.drivers.netapp.options.netapp_transport_opts,
|
||||
|
754
manila/share/drivers/lxd.py
Normal file
754
manila/share/drivers/lxd.py
Normal file
@ -0,0 +1,754 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""LXC/LXD Driver for shares.
|
||||
|
||||
This driver allows to use a LXD container as a share server.
|
||||
Current implementation suggests that a container when started by LXD 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 os
|
||||
import re
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from pylxd import api as lxd_api
|
||||
from pylxd import exceptions as pylxd_exc
|
||||
import six
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LW
|
||||
from manila.share import driver
|
||||
from manila import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
lxd_opts = [
|
||||
# (aovchinnikov): this has to stay till we can produce images which
|
||||
# can set up OVS correctly.
|
||||
cfg.StrOpt("lxd_linux_bridge_name",
|
||||
default="lxcbr0",
|
||||
required=True,
|
||||
help="Linux bridge used by LXD to plug host-side veth to. "
|
||||
"It will be unplugged from here by the driver."),
|
||||
cfg.StrOpt("lxd_ovs_bridge_name",
|
||||
default="br-int",
|
||||
required=True,
|
||||
help="OVS bridge to use to plug a container to."),
|
||||
cfg.StrOpt("lxd_nfs_server",
|
||||
default="unfs3",
|
||||
help="User space NFS server to be used. Currently the only "
|
||||
"implementation supported is unfs3. In future ganesha "
|
||||
"is planned to be supported as well. Please note, that "
|
||||
"unfs3 is mostly an experimental driver which should "
|
||||
"be used in production with care and at the user's own "
|
||||
"risk."),
|
||||
cfg.IntOpt("lxd_build_timeout",
|
||||
default=120,
|
||||
help="Time to wait till container is considered being unable "
|
||||
"to start."),
|
||||
cfg.IntOpt("lxd_check_timeout",
|
||||
default=1,
|
||||
help="Inter-check delay for container operations."),
|
||||
cfg.StrOpt("lxd_cifs_guest_ok",
|
||||
default="yes",
|
||||
help="Determines whether to allow guest access to CIFS share "
|
||||
"or not."),
|
||||
cfg.StrOpt("lxd_image_name",
|
||||
default="manila-lxd-image",
|
||||
help="LXD image to be used for a server."),
|
||||
]
|
||||
|
||||
lv_opts = [
|
||||
cfg.StrOpt("lxd_volume_group",
|
||||
default="manila_lxd_volumes",
|
||||
help="LVM volume group to use for volumes."),
|
||||
cfg.StrOpt("lxd_lv_size",
|
||||
default="10M",
|
||||
help="Logical volume size."),
|
||||
]
|
||||
|
||||
CONF.register_opts(lxd_opts)
|
||||
CONF.register_opts(lv_opts)
|
||||
|
||||
|
||||
class LXDHelper(object):
|
||||
|
||||
def __init__(self, lxd_api, config):
|
||||
super(LXDHelper, self).__init__()
|
||||
self.api = lxd_api
|
||||
self.conf = config
|
||||
|
||||
def create_container(self, name):
|
||||
container_config = {
|
||||
"name": name,
|
||||
"source": {
|
||||
"type": "image",
|
||||
"alias": self.conf.lxd_image_name
|
||||
}
|
||||
}
|
||||
|
||||
state, data = self.api.container_init(container_config)
|
||||
|
||||
LOG.debug("Container init result: %s, %s.", state, data)
|
||||
LOG.debug("Containers list %s.", self.api.container_list())
|
||||
|
||||
def container_initialized():
|
||||
try:
|
||||
LOG.debug("Operation 'initialize': %s.", data["operation"])
|
||||
state, info = self.api.operation_info(data["operation"])
|
||||
|
||||
LOG.debug("Operation 'initialize' info: %s, %s.", state, info)
|
||||
|
||||
return info["status"].lower() == "success"
|
||||
|
||||
except Exception as e:
|
||||
LOG.debug("Check error '%s'.", e)
|
||||
return False
|
||||
|
||||
self._wait(
|
||||
container_initialized,
|
||||
exception.ManilaException(_("Container creation error."))
|
||||
)
|
||||
|
||||
@utils.retry(exception.ManilaException, retries=5)
|
||||
def start_container(self, name):
|
||||
LOG.debug("Starting container %s.", name)
|
||||
|
||||
throwaway, data = self.api.container_start(name, -1)
|
||||
|
||||
def container_running():
|
||||
LOG.debug("Operation 'start': %s.", data["operation"])
|
||||
|
||||
try:
|
||||
state, info = self.api.operation_info(data["operation"])
|
||||
except Exception as e:
|
||||
raise exception.ManilaException(
|
||||
_("Cannot get operation info: %s.") % e
|
||||
)
|
||||
|
||||
LOG.debug("Operation 'start' info: %s, %s.", state, info)
|
||||
|
||||
if "status" in info["metadata"]:
|
||||
meta = info["metadata"]
|
||||
operation_failed = (
|
||||
six.text_type(meta["status"]).lower() == "failure" and
|
||||
six.text_type(meta["err"]).lower() !=
|
||||
"the container is already running"
|
||||
)
|
||||
else:
|
||||
operation_failed = False
|
||||
|
||||
if operation_failed:
|
||||
err_info = info["metadata"]["metadata"]
|
||||
|
||||
raise exception.ManilaException(
|
||||
_("Cannot start container: %s.") % err_info
|
||||
)
|
||||
|
||||
result = self.api.container_running(name)
|
||||
LOG.debug("Check is container running: %s.", result)
|
||||
return result
|
||||
|
||||
self._wait(
|
||||
container_running,
|
||||
exception.ManilaException("Container startup error.")
|
||||
)
|
||||
|
||||
def stop_container(self, name):
|
||||
LOG.debug("Stopping container %s.", name)
|
||||
state, data = self.api.container_stop(name, 60)
|
||||
|
||||
def container_stopped():
|
||||
return not self.api.container_running(name)
|
||||
|
||||
self._wait(
|
||||
container_stopped,
|
||||
exception.ManilaException(_("Container stopping error."))
|
||||
)
|
||||
self.api.container_destroy(name)
|
||||
|
||||
def _wait(self, predicate, timeout_exception):
|
||||
utils.wait_until_true(
|
||||
predicate,
|
||||
timeout=self.conf.lxd_build_timeout,
|
||||
sleep=self.conf.lxd_check_timeout,
|
||||
exception=timeout_exception
|
||||
)
|
||||
|
||||
def _wait_operation(self, operation):
|
||||
def wait():
|
||||
LOG.debug("Wait operation %s...", operation)
|
||||
try:
|
||||
state, info = self.api.operation_info(operation)
|
||||
LOG.debug("Operation details: %s.", info)
|
||||
except pylxd_exc.APIError as e:
|
||||
LOG.exception(e)
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
raise exception.ManilaException(
|
||||
_("Cannot get operation info: %s.") % e
|
||||
)
|
||||
|
||||
return (
|
||||
six.text_type(info["metadata"]["status"]).lower() != "running"
|
||||
)
|
||||
self._wait(
|
||||
wait,
|
||||
exception.ManilaException(_("Operation %s still running.") %
|
||||
operation)
|
||||
)
|
||||
|
||||
def execute_sync(self, container_name, args):
|
||||
status, result = self.api.container_run_command(
|
||||
container_name, args, interactive=True, web_sockets=True)
|
||||
|
||||
LOG.debug("CMD: %s", args)
|
||||
LOG.debug("Execution result: %s", result)
|
||||
|
||||
try:
|
||||
socket_pass = result["metadata"]["metadata"]["fds"]["0"]
|
||||
except KeyError:
|
||||
raise exception.ManilaException(
|
||||
_("Socket secret not found in operation details.")
|
||||
)
|
||||
|
||||
stream = self.api.operation_stream(result["operation"], socket_pass)
|
||||
cmd_result = ""
|
||||
|
||||
while True:
|
||||
message = stream.receive()
|
||||
|
||||
if not message:
|
||||
break
|
||||
cmd_result += message.data.decode("utf-8")
|
||||
|
||||
LOG.debug("CMD output: %s", cmd_result)
|
||||
|
||||
# NOTE(u_glide): Since LXD >= 0.24 socket should be closed by client
|
||||
# Fix in PyLXD: https://github.com/lxc/pylxd/pull/51
|
||||
stream.close()
|
||||
|
||||
status, info = self.api.operation_info(result["operation"])
|
||||
|
||||
LOG.debug("Operation details: %(info)s, %(status)s.",
|
||||
{"info": info, "status": status})
|
||||
return cmd_result
|
||||
|
||||
|
||||
class LXDUnfs3Helper(object):
|
||||
# NOTE(aovchinnikov): This is a temporary replacement for nfs-ganesha
|
||||
# designed for testing purposes. It is not intended to be used in
|
||||
# production.
|
||||
def __init__(self, lxd_helper, *args, **kwargs):
|
||||
super(LXDUnfs3Helper, self).__init__()
|
||||
self.share = None or kwargs.get("share")
|
||||
self.lxd = lxd_helper
|
||||
self.access_rules_ro = "(ro,no_root_squash,async,no_subtree_check)"
|
||||
self.access_rules_rw = "(rw,no_root_squash,async,no_subtree_check)"
|
||||
|
||||
def _restart_unfsd(self, server_id):
|
||||
LOG.debug("Restarting unfsd....")
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["pkill", "unfsd"]
|
||||
)
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["service", "unfs3", "start"]
|
||||
)
|
||||
LOG.debug("Restarting unfsd - done!")
|
||||
|
||||
def create_share(self, server_id):
|
||||
# (aovchinnikov): the moment a folder appears in /etc/exports it could
|
||||
# have accessed with ro from anywhere. Thus create_share does
|
||||
# essentially nothing.
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["touch", "/etc/exports"]
|
||||
)
|
||||
result = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["ip", "addr", "show", "eth0"]
|
||||
).split()[18].split('/')[0]
|
||||
location = result + ':' + "/shares/%s" % self.share.share_id
|
||||
return location
|
||||
|
||||
def delete_share(self, server_id):
|
||||
share_name = self.share.share_id
|
||||
share_folder = "/shares/%s" % share_name
|
||||
delete_pattern = "\$" + share_folder + ".*$d"
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["sed", "-i", delete_pattern, "/etc/exports"]
|
||||
)
|
||||
self._restart_unfsd(server_id)
|
||||
|
||||
def _deny_access(self, server_id, host_to_deny):
|
||||
share_name = self.share.share_id
|
||||
share_folder = "/shares/%s" % share_name
|
||||
deny_pattern = ("\$" + share_folder + '.*' +
|
||||
host_to_deny.replace('.', '\.') + '.*$d')
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["sed", "-i", deny_pattern, "/etc/exports"]
|
||||
)
|
||||
self._restart_unfsd(server_id)
|
||||
|
||||
def _allow_access(self, share_name, server_id, host_to_allow,
|
||||
access_level):
|
||||
if access_level == const.ACCESS_LEVEL_RO:
|
||||
access_rules = self.access_rules_ro
|
||||
elif access_level == const.ACCESS_LEVEL_RW:
|
||||
access_rules = self.access_rules_rw
|
||||
else:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
share_name = self.share.share_id
|
||||
share_folder = "/shares/%s" % share_name
|
||||
search_pattern = (share_folder + '.*' +
|
||||
host_to_allow.replace('.', '\.') + '.*')
|
||||
result = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["grep", search_pattern, "/etc/exports"]
|
||||
)
|
||||
|
||||
if result == '':
|
||||
new_share = share_folder + ' ' + host_to_allow + access_rules
|
||||
result = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["sed", "-i", "$ a\\" + new_share, "/etc/exports"]
|
||||
)
|
||||
|
||||
self._restart_unfsd(server_id)
|
||||
|
||||
def update_access(self, share_name, server_id, access_rules, add_rules,
|
||||
delete_rules):
|
||||
if not (add_rules or delete_rules):
|
||||
share_folder = "/shares/%s" % share_name
|
||||
delete_pattern = "\$" + share_folder + ".*$d"
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["sed", "-i", delete_pattern, "/etc/exports"]
|
||||
)
|
||||
for rule in (access_rules or []):
|
||||
host_to_allow = rule['access_to']
|
||||
access_level = rule['access_level']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._allow_access(share_name, server_id, host_to_allow,
|
||||
access_level)
|
||||
else:
|
||||
msg = _("Access type '%s' is not supported by the "
|
||||
"driver.") % access_type
|
||||
raise exception.InvalidShareAccess(reason=msg)
|
||||
return
|
||||
for rule in add_rules:
|
||||
host_to_allow = rule['access_to']
|
||||
access_level = rule['access_level']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._allow_access(share_name, server_id, host_to_allow,
|
||||
access_level)
|
||||
else:
|
||||
msg = _("Access type '%s' is not supported by the "
|
||||
"driver.") % access_type
|
||||
raise exception.InvalidShareAccess(reason=msg)
|
||||
for rule in delete_rules:
|
||||
host_to_deny = rule['access_to']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._deny_access(server_id, host_to_deny)
|
||||
else:
|
||||
LOG.warning(_LW("Attempt to use access type %s has been "
|
||||
"blocked.") % access_type)
|
||||
|
||||
|
||||
class LXDCIFSHelper(object):
|
||||
def __init__(self, lxd_helper, *args, **kwargs):
|
||||
super(LXDCIFSHelper, self).__init__()
|
||||
self.share = None or kwargs.get("share")
|
||||
self.conf = kwargs.get("config")
|
||||
self.lxd = lxd_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.lxd_cifs_guest_ok == "yes":
|
||||
cmd.append("guest_ok=y")
|
||||
else:
|
||||
cmd.append("guest_ok=n")
|
||||
self.lxd.execute_sync(server_id, cmd)
|
||||
parameters = {
|
||||
"browseable": "yes",
|
||||
"create mask": "0755",
|
||||
"hosts deny": "0.0.0.0/0",
|
||||
"hosts allow": "127.0.0.1",
|
||||
"read only": "no",
|
||||
}
|
||||
for param, value in parameters.items():
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, param, value]
|
||||
)
|
||||
result = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["ip", "addr", "show", "eth0"]
|
||||
).split()[18].split('/')[0]
|
||||
location = '\\\\' + result + '\\' + "/shares/%s" % share_name
|
||||
return location
|
||||
|
||||
def delete_share(self, server_id):
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "delshare", self.share.share_id]
|
||||
)
|
||||
|
||||
def _deny_access(self, server_id, host_to_deny):
|
||||
share_name = self.share.share_id
|
||||
allowed_hosts = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "getparm", share_name, "hosts allow"]
|
||||
)
|
||||
if allowed_hosts.count(host_to_deny) == 0:
|
||||
LOG.debug("Access for host %s is already denied.", host_to_deny)
|
||||
return
|
||||
pruned_hosts = filter(lambda x: not x.startswith(host_to_deny),
|
||||
allowed_hosts.split())
|
||||
allowed_hosts = " ".join(pruned_hosts)
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, "hosts allow",
|
||||
allowed_hosts]
|
||||
)
|
||||
|
||||
def _allow_access(self, share_name, server_id, host_to_allow,
|
||||
access_level):
|
||||
if access_level == const.ACCESS_LEVEL_RO:
|
||||
if self.conf.lxd_cifs_guest_ok != "yes":
|
||||
raise exception.ManilaException(_("Can't provide 'ro' access"
|
||||
"for guest."))
|
||||
LOG.debug("Host is accessible for reading data.")
|
||||
return
|
||||
elif access_level == const.ACCESS_LEVEL_RW:
|
||||
allowed_hosts = self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "getparm", share_name, "hosts allow"]
|
||||
)
|
||||
if allowed_hosts.count(host_to_allow) != 0:
|
||||
LOG.debug("Access for host %s is already allowed.",
|
||||
host_to_allow)
|
||||
return
|
||||
|
||||
allowed_hosts = ", ".join([host_to_allow, allowed_hosts])
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, "hosts allow",
|
||||
allowed_hosts]
|
||||
)
|
||||
else:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
|
||||
def _allow_user_access(self, share_name, server_id, user_to_allow,
|
||||
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)
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, access,
|
||||
user_to_allow]
|
||||
)
|
||||
|
||||
def update_access(self, share_name, server_id, access_rules,
|
||||
add_rules=None, delete_rules=None):
|
||||
if not (add_rules or delete_rules):
|
||||
# clean all hosts from allowed hosts list first.
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["net", "conf", "setparm", share_name, "hosts allow", ""]
|
||||
)
|
||||
for rule in (access_rules or []):
|
||||
host_to_allow = rule['access_to']
|
||||
access_level = rule['access_level']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._allow_access(share_name, server_id, host_to_allow,
|
||||
access_level)
|
||||
elif access_type == 'user':
|
||||
self._allow_user_access(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)
|
||||
return
|
||||
for rule in add_rules:
|
||||
host_to_allow = rule['access_to']
|
||||
access_level = rule['access_level']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._allow_access(share_name, server_id, host_to_allow,
|
||||
access_level)
|
||||
elif access_type == 'user':
|
||||
self._allow_user_access(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)
|
||||
for rule in delete_rules:
|
||||
host_to_deny = rule['access_to']
|
||||
access_type = rule['access_type']
|
||||
if access_type == 'ip':
|
||||
self._deny_access(server_id, host_to_deny)
|
||||
else:
|
||||
LOG.warning(_LW("Attempt to use access type %s has been "
|
||||
"blocked.") % access_type)
|
||||
|
||||
|
||||
class LXDDriver(driver.GaneshaMixin, driver.ShareDriver, driver.ExecuteMixin):
|
||||
"""Executes commands relating to Shares."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Do initialization."""
|
||||
super(LXDDriver, self).__init__([True], *args, **kwargs)
|
||||
|
||||
self.admin_context = context.get_admin_context()
|
||||
self.configuration.append_config_values(lxd_opts)
|
||||
self.configuration.append_config_values(lv_opts)
|
||||
self._helpers = {}
|
||||
self.backend_name = self.configuration.safe_get(
|
||||
"share_backend_name") or "LXD"
|
||||
self.private_storage = kwargs.get("private_storage")
|
||||
# TODO(uglide): add config options for LXD host and port
|
||||
# TODO(uglide): raise specific exception on timeout
|
||||
self.lxd = LXDHelper(lxd_api.API(), self.configuration)
|
||||
self.ssh_connections = {}
|
||||
self.nfshelper = self._get_nfs_helper()
|
||||
|
||||
def _update_share_stats(self):
|
||||
"""Retrieve stats info from share volume group."""
|
||||
data = {
|
||||
'share_backend_name': self.backend_name,
|
||||
'storage_protocol': 'NFS_CIFS',
|
||||
'reserved_percentage':
|
||||
self.configuration.reserved_share_percentage,
|
||||
'consistency_group_support': None,
|
||||
'snapshot_support': False,
|
||||
'driver_name': 'LXDDriver',
|
||||
'pools': self.get_share_server_pools()
|
||||
}
|
||||
super(LXDDriver, self)._update_share_stats(data)
|
||||
|
||||
def get_share_server_pools(self, share_server=None):
|
||||
out, err = self._execute('vgs',
|
||||
self.configuration.lxd_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.lxd_volume_group,
|
||||
'total_capacity_gb': float(total_size),
|
||||
'free_capacity_gb': float(free_size),
|
||||
'reserved_percentage': 0,
|
||||
}, ]
|
||||
|
||||
def _get_nfs_helper(self):
|
||||
if self.configuration.lxd_nfs_server == 'ganesha':
|
||||
raise exception.ManilaException(_("NFSGanesha is currently not "
|
||||
"supported by this driver."))
|
||||
elif self.configuration.lxd_nfs_server == 'unfs3':
|
||||
return LXDUnfs3Helper
|
||||
else:
|
||||
raise exception.ManilaException(_("Unsupported NFS userspace "
|
||||
"server: %s.") %
|
||||
self.configuration.lxd_nfs_server)
|
||||
|
||||
def _get_helper(self, share):
|
||||
if share["share_proto"].upper() == "NFS":
|
||||
helper = self._helpers.get("NFS")
|
||||
if helper is not None:
|
||||
helper.share = share
|
||||
return helper
|
||||
self._helpers["NFS"] = self.nfshelper(self.lxd, share=share)
|
||||
return self._helpers["NFS"]
|
||||
elif share["share_proto"].upper() == "CIFS":
|
||||
helper = self._helpers.get("CIFS")
|
||||
if helper is not None:
|
||||
helper.share = share
|
||||
return helper
|
||||
self._helpers["CIFS"] = LXDCIFSHelper(self.lxd, share=share,
|
||||
config=self.configuration)
|
||||
return self._helpers["CIFS"]
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=_("Wrong, unsupported or disabled protocol."))
|
||||
|
||||
def _get_lv_device(self, share):
|
||||
return os.path.join("/dev", self.configuration.lxd_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", share.share_id)
|
||||
|
||||
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.lxd.execute_sync(
|
||||
server_id,
|
||||
["mkdir", "-m", "777", "/shares/%s" % share_name]
|
||||
)
|
||||
self._execute("lvcreate", "-p", "rw", "-L",
|
||||
self.configuration.lxd_lv_size, "-n", share_name,
|
||||
self.configuration.lxd_volume_group, run_as_root=True)
|
||||
self._execute("mkfs.ext4", self._get_lv_device(share),
|
||||
run_as_root=True)
|
||||
self._execute("mkdir", "-m", "777", self._get_lv_folder(share))
|
||||
self._execute("mount", self._get_lv_device(share),
|
||||
self._get_lv_folder(share), run_as_root=True)
|
||||
self._execute("chmod", "-R", "777", self._get_lv_folder(share),
|
||||
run_as_root=True)
|
||||
self._execute("lxc", "config", "device", "add",
|
||||
server_id, share_name, "disk",
|
||||
"source=" + self._get_lv_folder(share),
|
||||
"path=/shares/" + share_name, run_as_root=True)
|
||||
|
||||
location = self._get_helper(share).create_share(server_id)
|
||||
return location
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
lv_device = self._get_lv_device(share)
|
||||
cmd = ('lvextend', '-L', '%sG' % new_size, '-n', lv_device)
|
||||
self._execute(*cmd, run_as_root=True)
|
||||
self._execute('resize2fs', lv_device, run_as_root=True)
|
||||
|
||||
def _connect_to_network(self, server_id, network_info):
|
||||
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.lxd.execute_sync(
|
||||
server_id,
|
||||
["ifconfig", "eth0", port_address, "up"]
|
||||
)
|
||||
host_veth = self._get_host_veth(server_id)
|
||||
msg_helper = {
|
||||
'id': server_id, 'veth': host_veth,
|
||||
'lb': self.configuration.lxd_linux_bridge_name,
|
||||
'ovsb': self.configuration.lxd_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.lxd_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']
|
||||
self._execute("ovs-vsctl", "--", "add-port",
|
||||
self.configuration.lxd_ovs_bridge_name, host_veth,
|
||||
*(e_mac + e_id + e_status), run_as_root=True)
|
||||
LOG.debug("Now container %(id)s should be accessible from network "
|
||||
"%(subnet)s by address %(ip)s." % msg_helper)
|
||||
|
||||
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._execute("umount", self._get_lv_device(share), run_as_root=True)
|
||||
self._execute("lxc", "config", "device", "remove",
|
||||
server_id, share.share_id, run_as_root=True)
|
||||
self._execute("lvremove", "-f", "--autobackup", "n",
|
||||
self._get_lv_device(share), run_as_root=True)
|
||||
self.lxd.execute_sync(
|
||||
server_id,
|
||||
["rm", "-fR", "/shares/%s" % share.share_id]
|
||||
)
|
||||
LOG.debug("Deletion of share %s is complete!", share.share_id)
|
||||
|
||||
def _get_host_veth(self, server_id):
|
||||
data = self.lxd.api.container_info(server_id)
|
||||
host_veth = data['network']['eth0']['host_name']
|
||||
return host_veth
|
||||
|
||||
def _teardown_server(self, *args, **kwargs):
|
||||
server_id = self._get_container_name(kwargs["server_details"]["id"])
|
||||
host_veth = self._get_host_veth(server_id)
|
||||
self.lxd.stop_container(server_id)
|
||||
self._execute("ovs-vsctl", "--", "del-port",
|
||||
self.configuration.lxd_ovs_bridge_name, host_veth,
|
||||
run_as_root=True)
|
||||
|
||||
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"])
|
||||
share_name = share.share_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(share_name, 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
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
self.lxd.create_container(server_id)
|
||||
except Exception as e:
|
||||
raise exception.ManilaException(_("Cannot create container: %s") %
|
||||
e)
|
||||
|
||||
self.lxd.start_container(server_id)
|
||||
self.lxd.api.container_run_command(server_id,
|
||||
["mkdir", "-m", "777", "/shares"])
|
||||
self._connect_to_network(server_id, network_info)
|
||||
# TODO(aovchinnikov): expand metadata above the bare minimum
|
||||
LOG.debug("Container %s was created!", server_id)
|
||||
return {"id": network_info["server_id"]}
|
@ -128,7 +128,55 @@ 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)
|
||||
|
||||
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',
|
||||
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_one_allocation_host_id(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': "fakehost",
|
||||
}
|
||||
}
|
||||
|
||||
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="fakehost")
|
||||
db_api.network_allocation_create.assert_called_once_with(
|
||||
self.fake_context,
|
||||
fake_network_allocation)
|
||||
@ -165,11 +213,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),
|
||||
|
873
manila/tests/share/drivers/test_lxd.py
Normal file
873
manila/tests/share/drivers/test_lxd.py
Normal file
@ -0,0 +1,873 @@
|
||||
# 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 LXD 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 import lxd
|
||||
from manila import test
|
||||
from manila.tests.db import fakes as db_fakes
|
||||
from manila.tests import fake_utils
|
||||
from manila import utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class LXDHelperTestCase(test.TestCase):
|
||||
"""Tests LXDUnfs3Helper"""
|
||||
|
||||
def setUp(self):
|
||||
super(LXDHelperTestCase, self).setUp()
|
||||
lxd_api = mock.Mock()
|
||||
config = None
|
||||
self.LXDHelper = lxd.LXDHelper(lxd_api, config)
|
||||
|
||||
def tearDown(self):
|
||||
super(LXDHelperTestCase, self).tearDown()
|
||||
|
||||
def test_create_container_initialized_ok(self):
|
||||
self.LXDHelper.conf = mock.Mock()
|
||||
self.LXDHelper.conf.lxd_image_name = "fake-image"
|
||||
fake_data = {"operation": "2"}
|
||||
|
||||
def fake_inner_wait():
|
||||
return True
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
try:
|
||||
fake_inner_wait()
|
||||
except Exception:
|
||||
raise exception.ManilaException()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.mock_object(self.LXDHelper.api, "container_init",
|
||||
mock.Mock(return_value=(0, fake_data)))
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.LXDHelper.create_container("fake_container")
|
||||
|
||||
def test_create_container_initialized_not_ok(self):
|
||||
self.LXDHelper.conf = mock.Mock()
|
||||
self.LXDHelper.conf.lxd_image_name = "fake-image"
|
||||
fake_data = {"operation": "2"}
|
||||
fake_operation = {"operation": "2", "status": "failure"}
|
||||
|
||||
def fake_inner_wait():
|
||||
return True
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
fake_inner_wait()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.mock_object(self.LXDHelper.api, "container_init",
|
||||
mock.Mock(return_value=(0, fake_data)))
|
||||
self.mock_object(self.LXDHelper.api, "operation_info",
|
||||
mock.Mock(return_value=(0, fake_operation)))
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.LXDHelper.create_container("fake_container")
|
||||
|
||||
def test_start_container_running_ok_status_fails(self):
|
||||
fake_data = {"operation": "2"}
|
||||
|
||||
def fake_inner_wait():
|
||||
return False
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
try:
|
||||
fake_inner_wait()
|
||||
except Exception:
|
||||
raise exception.ManilaException()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.mock_object(self.LXDHelper.api, "container_start",
|
||||
mock.Mock(return_value=(0, fake_data)))
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.LXDHelper.start_container("fake_container")
|
||||
|
||||
def test_start_container_running_ok_status_ok(self):
|
||||
fake_data = {"operation": "1"}
|
||||
|
||||
def fake_inner_wait():
|
||||
return True
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
try:
|
||||
fake_inner_wait()
|
||||
except Exception:
|
||||
raise exception.ManilaException()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.mock_object(self.LXDHelper.api, "container_start",
|
||||
mock.Mock(return_value=(0, fake_data)))
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.LXDHelper.start_container("fake_container")
|
||||
|
||||
def test_stop_container(self):
|
||||
self.mock_object(self.LXDHelper.api, "container_stop",
|
||||
mock.Mock(return_value=(0, 0)))
|
||||
self.mock_object(self.LXDHelper.api, "container_destroy")
|
||||
self.mock_object(self.LXDHelper, "_wait")
|
||||
self.LXDHelper.stop_container("fake")
|
||||
self.LXDHelper.api.container_stop.assert_called_once_with("fake", 60)
|
||||
|
||||
def test__wait(self):
|
||||
self.mock_object(utils, "wait_until_true")
|
||||
self.LXDHelper.conf = mock.Mock()
|
||||
self.LXDHelper.conf.lxd_build_timeout = 0
|
||||
self.LXDHelper.conf.lxd_check_timeout = 0
|
||||
self.LXDHelper._wait("spam", KeyError)
|
||||
utils.wait_until_true.assert_called_with('spam', exception=KeyError,
|
||||
sleep=0, timeout=0)
|
||||
|
||||
def test__wait_operation_ok(self):
|
||||
def fake_inner_wait():
|
||||
return True
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
try:
|
||||
fake_inner_wait()
|
||||
except Exception:
|
||||
raise exception.ManilaException()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.LXDHelper._wait_operation("whatever")
|
||||
|
||||
def test__wait_operation_unkown_error(self):
|
||||
def fake_inner_wait():
|
||||
raise exception.ManilaException("Cannot get operation info")
|
||||
|
||||
def fake__wait(wait, whatever):
|
||||
try:
|
||||
wait()
|
||||
except Exception:
|
||||
raise exception.ManilaException()
|
||||
self.mock_object(self.LXDHelper, "_wait", fake__wait)
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.LXDHelper._wait_operation,
|
||||
fake_inner_wait)
|
||||
|
||||
def test_execute_sync_soket_error(self):
|
||||
self.mock_object(self.LXDHelper.api, 'container_run_command',
|
||||
mock.Mock(return_value=(0, {})))
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.LXDHelper.execute_sync, 'fake', "fakes")
|
||||
|
||||
def test_execute_sync(self):
|
||||
ret_val = {"metadata": {"metadata": {"fds": {"0": ""}}},
|
||||
"operation": "None"}
|
||||
fake_stream = mock.Mock()
|
||||
self.mock_object(fake_stream, "receive", mock.Mock(return_value=None))
|
||||
self.mock_object(self.LXDHelper.api, 'container_run_command',
|
||||
mock.Mock(return_value=(0, ret_val)))
|
||||
self.mock_object(self.LXDHelper.api, 'operation_stream',
|
||||
mock.Mock(return_value=fake_stream))
|
||||
self.mock_object(self.LXDHelper.api, 'operation_info',
|
||||
mock.Mock(return_value=("", "")))
|
||||
self.LXDHelper.execute_sync("fake", "fake")
|
||||
fake_stream.close.assert_called_once_with()
|
||||
|
||||
|
||||
class LXDUnfs3HelperTestCase(test.TestCase):
|
||||
"""Tests LXDUnfs3Helper"""
|
||||
|
||||
def setUp(self):
|
||||
super(LXDUnfs3HelperTestCase, self).setUp()
|
||||
self.lxd_helper = mock.Mock()
|
||||
self.UNFS3Helper = lxd.LXDUnfs3Helper(self.lxd_helper,
|
||||
share=fake_share())
|
||||
|
||||
def tearDown(self):
|
||||
super(LXDUnfs3HelperTestCase, self).tearDown()
|
||||
|
||||
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__restart_unfsd(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver', ['pkill', 'unfsd']),
|
||||
('fakeserver', ['service', 'unfs3', 'start'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
self.UNFS3Helper._restart_unfsd('fakeserver')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test_create_share(self):
|
||||
self.mock_object(self.lxd_helper, 'execute_sync',
|
||||
mock.Mock(return_value="0/0 " * 20))
|
||||
self.UNFS3Helper.create_share('fakeserver')
|
||||
self.UNFS3Helper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver', ['ip', 'addr', 'show', 'eth0'])
|
||||
|
||||
def test_delete_share(self):
|
||||
self.mock_object(self.UNFS3Helper, '_restart_unfsd')
|
||||
self.mock_object(self.lxd_helper, 'execute_sync')
|
||||
self.UNFS3Helper.delete_share('fakeserver')
|
||||
self.UNFS3Helper.lxd.execute_sync.assert_called_once_with(
|
||||
'fakeserver',
|
||||
['sed', '-i', '\\$/shares/fakeshareid.*$d', '/etc/exports'])
|
||||
self.UNFS3Helper._restart_unfsd.assert_called_once_with('fakeserver')
|
||||
|
||||
def test__deny_access(self):
|
||||
self.mock_object(self.UNFS3Helper, '_restart_unfsd')
|
||||
self.mock_object(self.lxd_helper, 'execute_sync')
|
||||
self.UNFS3Helper._deny_access('fakeserver', '127.0.0.1')
|
||||
self.UNFS3Helper.lxd.execute_sync.assert_called_once_with(
|
||||
'fakeserver',
|
||||
['sed', '-i', '\\$/shares/fakeshareid.*127\\.0\\.0'
|
||||
'\\.1.*$d', '/etc/exports'])
|
||||
self.UNFS3Helper._restart_unfsd.assert_called_once_with('fakeserver')
|
||||
|
||||
def test__allow_access_wrong_level(self):
|
||||
self.assertRaises(exception.InvalidShareAccessLevel,
|
||||
self.UNFS3Helper._allow_access, 'fakeshare',
|
||||
'fakeserver', '127.0.0.1', 'rwx')
|
||||
|
||||
def test__allow_access_host_present_ro(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver',
|
||||
['grep', '/shares/fakeshareid.*127\\.0\\.0\\.1.*',
|
||||
'/etc/exports'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='127.0.0.1')
|
||||
self.UNFS3Helper._allow_access('fakeshare', 'fakeserver', '127.0.0.1',
|
||||
'rw')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test__allow_access_host_present_rw(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver',
|
||||
['grep', '/shares/fakeshareid.*127\\.0\\.0\\.1.*',
|
||||
'/etc/exports'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='127.0.0.1')
|
||||
self.UNFS3Helper._allow_access('fakeshare', 'fakeserver', '127.0.0.1',
|
||||
'rw')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test__allow_access_host_present_other(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver',
|
||||
['grep', '/shares/fakeshareid.*127\\.0\\.0\\.1.*',
|
||||
'/etc/exports'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='127.0.0.1')
|
||||
self.UNFS3Helper._allow_access('fakeshare', 'fakeserver', '127.0.0.1',
|
||||
'rw')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test__allow_access_no_host_ro(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver',
|
||||
['grep', '/shares/fakeshareid.*127\\.0\\.0\\.1.*',
|
||||
'/etc/exports']),
|
||||
('fakeserver',
|
||||
['sed', '-i',
|
||||
'$ a\\/shares/fakeshareid 127.0.0.1(rw,no_root_squash,async,'
|
||||
'no_subtree_check)', '/etc/exports'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
self.mock_object(self.UNFS3Helper, '_restart_unfsd')
|
||||
self.UNFS3Helper._allow_access('fakeshare', 'fakeserver', '127.0.0.1',
|
||||
'rw')
|
||||
self.UNFS3Helper._restart_unfsd.assert_called_once_with('fakeserver')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test__allow_access_no_host_rw(self):
|
||||
actual_arguments = []
|
||||
expected_arguments = [
|
||||
('fakeserver',
|
||||
['grep', '/shares/fakeshareid.*127\\.0\\.0\\.1.*',
|
||||
'/etc/exports']),
|
||||
('fakeserver',
|
||||
['sed', '-i',
|
||||
'$ a\\/shares/fakeshareid 127.0.0.1(rw,no_root_squash,async,'
|
||||
'no_subtree_check)', '/etc/exports'])]
|
||||
self.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val='')
|
||||
self.mock_object(self.UNFS3Helper, '_restart_unfsd')
|
||||
self.UNFS3Helper._allow_access('fakeshare', 'fakeserver', '127.0.0.1',
|
||||
'rw')
|
||||
self.UNFS3Helper._restart_unfsd.assert_called_once_with('fakeserver')
|
||||
self.assertEqual(expected_arguments, actual_arguments)
|
||||
|
||||
def test_update_access_access_rules_ok(self):
|
||||
allow_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_allow_access")
|
||||
self.UNFS3Helper.update_access("fakeshareid", "fakeserver",
|
||||
allow_rules, [], [])
|
||||
self.UNFS3Helper._allow_access.assert_called_once_with("fakeshareid",
|
||||
"fakeserver",
|
||||
"127.0.0.1",
|
||||
"ro")
|
||||
|
||||
def test_update_access_access_rules_wrong_type(self):
|
||||
access_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_allow_access")
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.UNFS3Helper.update_access, "fakeshareid",
|
||||
"fakeserver", access_rules, [], [])
|
||||
|
||||
def test_update_access_add_rules_ok(self):
|
||||
add_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_allow_access")
|
||||
self.UNFS3Helper.update_access("fakeshareid", "fakeserver", [],
|
||||
add_rules, [])
|
||||
self.UNFS3Helper._allow_access.assert_called_once_with("fakeshareid",
|
||||
"fakeserver",
|
||||
"127.0.0.1",
|
||||
"ro")
|
||||
|
||||
def test_update_access_add_rules_wrong_type(self):
|
||||
add_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_allow_access")
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.UNFS3Helper.update_access, "fakeshareid",
|
||||
"fakeserver", [], add_rules, [])
|
||||
|
||||
def test_update_access_delete_rules_ok(self):
|
||||
delete_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_deny_access")
|
||||
self.UNFS3Helper.update_access("fakeshareid", "fakeserver", [], [],
|
||||
delete_rules)
|
||||
self.UNFS3Helper._deny_access.assert_called_once_with("fakeserver",
|
||||
"127.0.0.1")
|
||||
|
||||
def test_update_access_delete_rules_not_ok(self):
|
||||
delete_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.UNFS3Helper, "_deny_access")
|
||||
self.UNFS3Helper.update_access("fakeshareid", "fakeserver", [], [],
|
||||
delete_rules)
|
||||
self.assertFalse(self.UNFS3Helper._deny_access.called)
|
||||
|
||||
|
||||
class LXDCIFSHelperTestCase(test.TestCase):
|
||||
"""Tests LXDCIFSHelper"""
|
||||
|
||||
def setUp(self):
|
||||
super(LXDCIFSHelperTestCase, self).setUp()
|
||||
self.lxd_helper = mock.Mock()
|
||||
self.fake_conf = mock.Mock()
|
||||
self.fake_conf.lxd_cifs_guest_ok = "yes"
|
||||
self.CIFSHelper = lxd.LXDCIFSHelper(self.lxd_helper,
|
||||
share=fake_share(),
|
||||
config=self.fake_conf)
|
||||
|
||||
def tearDown(self):
|
||||
super(LXDCIFSHelperTestCase, self).tearDown()
|
||||
|
||||
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.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val="0/0 " * 20)
|
||||
self.CIFSHelper.share = fake_share()
|
||||
self.CIFSHelper.create_share("fakeserver")
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test_create_share_guest_not_ok(self):
|
||||
self.CIFSHelper.conf = mock.Mock()
|
||||
self.CIFSHelper.conf.lxd_cifs_guest_ok = "no"
|
||||
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', '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.lxd_helper.execute_sync = functools.partial(
|
||||
self.fake_exec_sync, execute_arguments=actual_arguments,
|
||||
ret_val="0/0 " * 20)
|
||||
self.CIFSHelper.share = fake_share()
|
||||
self.CIFSHelper.create_share("fakeserver")
|
||||
self.assertEqual(expected_arguments.sort(), actual_arguments.sort())
|
||||
|
||||
def test_delete_share(self):
|
||||
self.CIFSHelper.share = fake_share()
|
||||
self.CIFSHelper.delete_share("fakeserver")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'delshare', 'fakeshareid'])
|
||||
|
||||
def test__deny_access_host_present(self):
|
||||
self.lxd_helper.execute_sync.side_effect = ['127.0.0.1', ""]
|
||||
self.CIFSHelper.share = fake_share()
|
||||
self.CIFSHelper._deny_access("fakeserver", "127.0.0.1")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'setparm', 'fakeshareid', 'hosts allow', ''])
|
||||
|
||||
def test__deny_access_no_host(self):
|
||||
self.lxd_helper.execute_sync.side_effect = ['', ""]
|
||||
self.CIFSHelper.share = fake_share()
|
||||
self.CIFSHelper._deny_access("fakeserver", "127.0.0.1")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'getparm', 'fakeshareid', 'hosts allow'])
|
||||
|
||||
def test__allow_access_host_present(self):
|
||||
self.lxd_helper.execute_sync.side_effect = ['127.0.0.1', ""]
|
||||
self.CIFSHelper._allow_access("fakeshareid", "fakeserver", "127.0.0.1",
|
||||
"rw")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'getparm', 'fakeshareid', 'hosts allow'])
|
||||
|
||||
def test__allow_access_no_host(self):
|
||||
self.lxd_helper.execute_sync.side_effect = ['', ""]
|
||||
self.CIFSHelper._allow_access("fakeshareid", "fakeserver", "127.0.0.1",
|
||||
"rw")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'setparm', 'fakeshareid', 'hosts allow',
|
||||
'127.0.0.1, '])
|
||||
|
||||
def test_allow_access_ro_guest_ok(self):
|
||||
self.CIFSHelper.conf = mock.Mock()
|
||||
self.CIFSHelper.conf.lxd_cifs_guest_ok = "yes"
|
||||
self.CIFSHelper._allow_access("fakeshareid", "fakeserver", "127.0.0.1",
|
||||
"ro")
|
||||
self.assertFalse(self.lxd_helper.execute_sync.called)
|
||||
|
||||
def test_allow_access_ro_guest_not_ok(self):
|
||||
self.CIFSHelper.conf = mock.Mock()
|
||||
self.CIFSHelper.conf.lxd_cifs_guest_ok = "no"
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.CIFSHelper._allow_access, "fakeshareid",
|
||||
"fakeserver", "127.0.0.1", "ro")
|
||||
|
||||
def test_allow_user_access_ok(self):
|
||||
self.CIFSHelper._allow_user_access("fakeshareid", "fakeserver",
|
||||
"fakeuser", "ro")
|
||||
self.CIFSHelper.lxd.execute_sync.assert_called_with(
|
||||
'fakeserver',
|
||||
['net', 'conf', 'setparm', 'fakeshareid', 'read list', 'fakeuser'])
|
||||
|
||||
def test_allow_user_access_not_ok(self):
|
||||
self.assertRaises(exception.InvalidShareAccessLevel,
|
||||
self.CIFSHelper._allow_user_access,
|
||||
"fakeshareid", "fakeserver", "fakeuser", "rx")
|
||||
|
||||
def test_update_access_access_rules_ok(self):
|
||||
allow_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", allow_rules,
|
||||
[], [])
|
||||
self.CIFSHelper._allow_access.assert_called_once_with("fakeshareid",
|
||||
"fakeserver",
|
||||
"127.0.0.1",
|
||||
"ro")
|
||||
|
||||
def test_update_access_access_rules_ok_user(self):
|
||||
allow_rules = [{
|
||||
'access_to': 'fakeuser',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_user_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", allow_rules,
|
||||
[], [])
|
||||
self.CIFSHelper._allow_user_access.assert_called_once_with(
|
||||
"fakeshareid",
|
||||
"fakeserver",
|
||||
"fakeuser",
|
||||
"ro")
|
||||
|
||||
def test_update_access_access_rules_wrong_type(self):
|
||||
allow_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'fake'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_access")
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.CIFSHelper.update_access, "fakeshareid",
|
||||
"fakeserver", allow_rules, [], [])
|
||||
|
||||
def test_update_access_add_rules_ok(self):
|
||||
add_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", [],
|
||||
add_rules, [])
|
||||
self. CIFSHelper._allow_access.assert_called_once_with("fakeshareid",
|
||||
"fakeserver",
|
||||
"127.0.0.1",
|
||||
"ro")
|
||||
|
||||
def test_update_access_add_rules_ok_user(self):
|
||||
add_rules = [{
|
||||
'access_to': 'fakeuser',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_user_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", [],
|
||||
add_rules, [])
|
||||
self. CIFSHelper._allow_user_access.assert_called_once_with(
|
||||
"fakeshareid",
|
||||
"fakeserver",
|
||||
"fakeuser",
|
||||
"ro")
|
||||
|
||||
def test_update_access_add_rules_wrong_type(self):
|
||||
add_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'fake'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_allow_access")
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.CIFSHelper.update_access, "fakeshareid",
|
||||
"fakeserver", [], add_rules, [])
|
||||
|
||||
def test_update_access_delete_rules_ok(self):
|
||||
delete_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'ip'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_deny_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", [], [],
|
||||
delete_rules)
|
||||
self.CIFSHelper._deny_access.assert_called_once_with("fakeserver",
|
||||
"127.0.0.1")
|
||||
|
||||
def test_update_access_delete_rules_not_ok(self):
|
||||
delete_rules = [{
|
||||
'access_to': '127.0.0.1',
|
||||
'access_level': 'ro',
|
||||
'access_type': 'user'
|
||||
}]
|
||||
self.mock_object(self.CIFSHelper, "_deny_access")
|
||||
self.CIFSHelper.update_access("fakeshareid", "fakeserver", [], [],
|
||||
delete_rules)
|
||||
self.assertFalse(self.CIFSHelper._deny_access.called)
|
||||
|
||||
|
||||
class LXDDriverTestCase(test.TestCase):
|
||||
"""Tests LXDDriver."""
|
||||
|
||||
def setUp(self):
|
||||
super(LXDDriverTestCase, 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 = lxd.LXDDriver(self._db, configuration=self.fake_conf)
|
||||
|
||||
self.share = fake_share()
|
||||
self.access = 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 tearDown(self):
|
||||
super(LXDDriverTestCase, self).tearDown()
|
||||
|
||||
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, '_execute')
|
||||
self.mock_object(self._driver.lxd, 'execute_sync')
|
||||
self.assertEqual('export_location',
|
||||
self._driver.create_share(self._context, self.share,
|
||||
{'id': 'fake'}))
|
||||
|
||||
def test_update_share_stats(self):
|
||||
self.mock_object(self._driver, 'get_share_server_pools',
|
||||
mock.Mock(return_value='test-pool'))
|
||||
|
||||
self._driver._update_share_stats()
|
||||
self.assertEqual('LXD', self._driver._stats['share_backend_name'])
|
||||
self.assertEqual('NFS_CIFS', self._driver._stats['storage_protocol'])
|
||||
self.assertEqual(0, self._driver._stats['reserved_percentage'])
|
||||
self.assertEqual(None,
|
||||
self._driver._stats['consistency_group_support'])
|
||||
self.assertEqual(False, self._driver._stats['snapshot_support'])
|
||||
self.assertEqual('LXDDriver', self._driver._stats['driver_name'])
|
||||
self.assertEqual('test-pool', self._driver._stats['pools'])
|
||||
|
||||
def test_get_share_server_pools(self):
|
||||
ret_vgs = "VSize 100g size\nVFree 100g whatever"
|
||||
expected_result = [{'reserved_percentage': 0,
|
||||
'pool_name': 'manila_lxd_volumes',
|
||||
'total_capacity_gb': 100.0,
|
||||
'free_capacity_gb': 100.0}]
|
||||
self.mock_object(self._driver, "_execute",
|
||||
mock.Mock(return_value=(ret_vgs, 0)))
|
||||
result = self._driver.get_share_server_pools()
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test__get_nfs_helper_ganesha(self):
|
||||
self._driver.configuration.lxd_nfs_server = "ganesha"
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver._get_nfs_helper)
|
||||
|
||||
def test__get_nfs_helper_unfs3(self):
|
||||
self.assertEqual(lxd.LXDUnfs3Helper, self._driver._get_nfs_helper())
|
||||
|
||||
def test__get_nfs_helper_other(self):
|
||||
self._driver.configuration.lxd_nfs_server = "MightyNFS"
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver._get_nfs_helper)
|
||||
|
||||
def test__get_helper_nfs_new(self):
|
||||
share = {'share_proto': 'NFS'}
|
||||
self.mock_object(self._driver, "nfshelper")
|
||||
self._driver._get_helper(share)
|
||||
self._driver.nfshelper.assert_called_once_with(self._driver.lxd,
|
||||
share=share)
|
||||
|
||||
def test__get_helper_nfs_existing(self):
|
||||
share = {'share_proto': 'NFS'}
|
||||
self._driver._helpers['NFS'] = mock.Mock()
|
||||
result = self._driver._get_helper(share)
|
||||
self.assertEqual(share, result.share)
|
||||
|
||||
def test__get_helper_cifs_new(self):
|
||||
share = {'share_proto': 'CIFS'}
|
||||
result = self._driver._get_helper(share)
|
||||
self.assertEqual(lxd.LXDCIFSHelper, type(result))
|
||||
|
||||
def test__get_helper_cifs_existing(self):
|
||||
share = {'share_proto': 'CIFS'}
|
||||
self._driver._helpers['CIFS'] = mock.Mock()
|
||||
result = self._driver._get_helper(share)
|
||||
self.assertEqual(share, result.share)
|
||||
|
||||
def test__get_helper_other(self):
|
||||
share = {'share_proto': 'SuperProtocol'}
|
||||
self.assertRaises(exception.InvalidShare, self._driver._get_helper,
|
||||
share)
|
||||
|
||||
def test__get_lv_device(self):
|
||||
self.assertEqual("/dev/manila_lxd_volumes/fakeshareid",
|
||||
self._driver._get_lv_device(self.share))
|
||||
|
||||
def test__get_lv_folder(self):
|
||||
self.assertEqual("/tmp/fakeshareid",
|
||||
self._driver._get_lv_folder(self.share))
|
||||
|
||||
def test_extend_share(self):
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self.mock_object(self._driver, '_get_lv_device',
|
||||
mock.Mock(return_value="path"))
|
||||
self._driver.extend_share(self.share, 'share', 3)
|
||||
local_path = 'path'
|
||||
self._driver._execute.assert_called_with('resize2fs', local_path,
|
||||
run_as_root=True)
|
||||
|
||||
def test__connect_to_network(self):
|
||||
network_info = fake_network()
|
||||
helper = mock.Mock()
|
||||
self.mock_object(self._driver, "_execute",
|
||||
mock.Mock(return_value=helper))
|
||||
self.mock_object(self._driver.lxd, "execute_sync")
|
||||
self.mock_object(self._driver, "_get_host_veth",
|
||||
mock.Mock(return_value="vethBEEF42"))
|
||||
self._driver._connect_to_network("fake-server", network_info)
|
||||
|
||||
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, '_execute')
|
||||
self.mock_object(self._driver.lxd, 'execute_sync')
|
||||
self._driver.delete_share(self._context, self.share, {'id': 'fake'})
|
||||
self._driver._execute.assert_called_with(
|
||||
'lvremove', '-f', '--autobackup', 'n',
|
||||
'/dev/manila_lxd_volumes/fakeshareid',
|
||||
run_as_root=True)
|
||||
|
||||
def test__get_host_veth(self):
|
||||
fake_data = {
|
||||
"network": {"eth0": {"host_name": "vethBEEF42"}}
|
||||
}
|
||||
self.mock_object(self._driver.lxd.api, "container_info",
|
||||
mock.Mock(return_value=fake_data))
|
||||
self.assertEqual("vethBEEF42",
|
||||
self._driver._get_host_veth("fakeserver"))
|
||||
|
||||
def test__teardown_server(self):
|
||||
self.mock_object(self._driver, '_get_host_veth',
|
||||
mock.Mock(return_value='veth42BEEF'))
|
||||
self.mock_object(self._driver.lxd, 'stop_container')
|
||||
self.mock_object(self._driver, '_execute')
|
||||
self._driver._teardown_server(server_details={'id': 'fake'})
|
||||
self._driver.lxd.stop_container.assert_called_with('manila-fake')
|
||||
self._driver._execute.assert_called_with("ovs-vsctl", "--", "del-port",
|
||||
'br-int', 'veth42BEEF',
|
||||
run_as_root=True)
|
||||
|
||||
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('fakeshareid', 'manila-fake',
|
||||
[{'access_level': 'rw'}],
|
||||
[], [])
|
||||
|
||||
def test__get_container_name(self):
|
||||
self.assertEqual("manila-fake-server",
|
||||
self._driver._get_container_name("fake-server"))
|
||||
|
||||
def test__setup_server_container_fails(self):
|
||||
network_info = fake_network()
|
||||
self.mock_object(self._driver.lxd, 'create_container')
|
||||
self._driver.lxd.create_container.side_effect = KeyError()
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self._driver._setup_server, network_info)
|
||||
|
||||
def test__setup_server_ok(self):
|
||||
network_info = fake_network()
|
||||
server_id = self._driver._get_container_name(network_info["server_id"])
|
||||
self.mock_object(self._driver.lxd, 'create_container')
|
||||
self.mock_object(self._driver.lxd, 'start_container')
|
||||
self.mock_object(self._driver.lxd.api, 'container_run_command')
|
||||
self.mock_object(self._driver, '_connect_to_network')
|
||||
self.assertEqual(network_info['server_id'],
|
||||
self._driver._setup_server(network_info)['id'])
|
||||
self._driver.lxd.create_container.assert_called_once_with(server_id)
|
||||
self._driver.lxd.start_container.assert_called_once_with(server_id)
|
||||
self._driver._connect_to_network.assert_called_once_with(server_id,
|
||||
network_info)
|
@ -870,3 +870,18 @@ class RequireDriverInitializedTestCase(test.TestCase):
|
||||
expected_exception = exception.DriverNotInitialized
|
||||
|
||||
self.assertRaises(expected_exception, FakeManager().call_me)
|
||||
|
||||
|
||||
class WaitUntilTrueTestCase(test.TestCase):
|
||||
|
||||
def test_wait_until_true_ok(self):
|
||||
fake_predicate = mock.Mock(return_value=True)
|
||||
exc = exception.ManilaException
|
||||
utils.wait_until_true(fake_predicate, 1, 1, exc)
|
||||
self.assertTrue(fake_predicate.called)
|
||||
|
||||
def test_wait_until_true_not_ok(self):
|
||||
fake_predicate = mock.Mock(return_value=False)
|
||||
exc = exception.ManilaException
|
||||
self.assertRaises(exception.ManilaException, utils.wait_until_true,
|
||||
fake_predicate, 1, 1, exc)
|
||||
|
@ -30,6 +30,7 @@ import socket
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import eventlet
|
||||
from eventlet import pools
|
||||
import netaddr
|
||||
from oslo_concurrency import lockutils
|
||||
@ -655,3 +656,18 @@ def translate_string_size_to_float(string, multiplier='G'):
|
||||
value = float(matched.groups()[0])
|
||||
multiplier = mapping[matched.groups()[1]] / mapping[multiplier]
|
||||
return value * multiplier
|
||||
|
||||
|
||||
def wait_until_true(predicate, timeout=60, sleep=1, exception=None):
|
||||
"""Wait until callable predicate is evaluated as True
|
||||
|
||||
:param predicate: Callable deciding whether waiting should continue.
|
||||
Best practice is to instantiate predicate with functools.partial()
|
||||
:param timeout: Timeout in seconds how long should function wait.
|
||||
:param sleep: Polling interval for results in seconds.
|
||||
:param exception: Exception class for eventlet.Timeout.
|
||||
(see doc for eventlet.Timeout for more information)
|
||||
"""
|
||||
with eventlet.timeout.Timeout(timeout, exception):
|
||||
while not predicate():
|
||||
eventlet.sleep(sleep)
|
||||
|
@ -28,6 +28,7 @@ oslo.concurrency>=3.5.0 # Apache-2.0
|
||||
paramiko>=1.16.0 # LGPL
|
||||
Paste # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pylxd>=0.19.0 # Apache-2.0
|
||||
python-neutronclient>=2.6.0 # Apache-2.0
|
||||
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
||||
requests!=2.9.0,>=2.8.1 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user