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:
Igor Malinovskiy 2015-12-16 16:39:28 +02:00 committed by Alexey Ovchinnikov
parent cdd2fb95b0
commit 4f74b224fa
14 changed files with 1896 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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"]}

View File

@ -128,7 +128,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)
@ -137,6 +137,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_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)
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',
@ -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),

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

View File

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

View File

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

View File

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