Get rid of SUID binary to restart docker containers

This patch removes a script with SUID flag,restart_docker_container,
which was used to restart docker containers and replaces it by
native python implementation. The aim of this replacmenet is to reduce
the security risk caused by SUID binaries, as well as to implement
more features and unit test coverages easily.

To allow swift user to manage docker conainers, now it is required
that swift user belong to docker group and have access to docker
unix domain socket.

Change-Id: I8103d4d826f5121e16f67f1ff49102ceecaf8a80
This commit is contained in:
Takashi Kajinami 2020-09-07 00:41:47 +09:00
parent e2060cd342
commit d60176cc2f
10 changed files with 231 additions and 292 deletions

3
.gitignore vendored
View File

@ -36,9 +36,6 @@ src/java/SBus/org_openstack_storlet_sbus_SBusJNI.h
*.jar
StorletSamples/java/*/bin
# scripts build
scripts/restart_docker_container
# functional tests
tests/functional/.ipynb_checkpoints/

View File

@ -47,7 +47,6 @@ SWIFT_MEMBER_USER_PWD=member
# Storlets install tunables
STORLETS_DEFAULT_USER_DOMAIN_ID=${STORLETS_DEFAULT_USER_DOMAIN_ID:-default}
STORLETS_DEFAULT_PROJECT_DOMAIN_ID=${STORLETS_DEFAULT_PROJECT_DOMAIN_ID:-default}
STORLET_MANAGEMENT_USER=${STORLET_MANAGEMENT_USER:-$USER}
STORLETS_DOCKER_DEVICE=${STORLETS_DOCKER_DEVICE:-/home/docker_device}
STORLETS_DOCKER_BASE_IMG=${STORLETS_DOCKER_BASE_IMG:-ubuntu:20.04}
STORLETS_DOCKER_BASE_IMG_NAME=${STORLETS_DOCKER_BASE_IMG_NAME:-ubuntu_20.04}
@ -60,7 +59,7 @@ STORLETS_STORLET_CONTAINER_NAME=${STORLETS_STORLET_CONTAINER_NAME:-storlet}
STORLETS_DEPENDENCY_CONTAINER_NAME=${STORLETS_DEPENDENCY_CONTAINER_NAME:-dependency}
STORLETS_LOG_CONTAIER_NAME=${STORLETS_LOG_CONTAIER_NAME:-log}
STORLETS_GATEWAY_MODULE=${STORLETS_GATEWAY_MODULE:-docker}
STORLETS_GATEWAY_CONF_FILE=${STORLETS_GATEWAY_CONF_FILE:-/etc/swift/storlet_docker_gateway.conf}
STORLETS_GATEWAY_CONF_FILE=${STORLETS_GATEWAY_CONF_FILE:-${SWIFT_CONF_DIR}/storlet_docker_gateway.conf}
STORLETS_PROXY_EXECUTION_ONLY=${STORLETS_PROXY_EXECUTION_ONLY:-false}
STORLETS_SCRIPTS_DIR=${STORLETS_SCRIPTS_DIR:-"$STORLETS_DOCKER_DEVICE"/scripts}
STORLETS_STORLETS_DIR=${STORLETS_STORLETS_DIR:-"$STORLETS_DOCKER_DEVICE"/storlets/scopes}
@ -76,18 +75,6 @@ TMP_REGISTRY_PREFIX=/tmp/registry
# Functions
# ---------
function _storlets_swift_start {
swift-init --run-dir=${SWIFT_DATA_DIR}/run all start || true
}
function _storlets_swift_stop {
swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
}
function _storlets_swift_restart {
swift-init --run-dir=${SWIFT_DATA_DIR}/run all restart || true
}
function _export_os_vars {
export OS_IDENTITY_API_VERSION=3
export OS_AUTH_URL="http://$KEYSTONE_IP/identity/v3"
@ -148,7 +135,7 @@ function configure_swift_and_keystone_for_storlets {
rm /tmp/storlet-docker-gateway.conf
# Create storlet related containers and set ACLs
_storlets_swift_start
start_swift
_export_swift_os_vars
openstack object store account set --property Storlet-Enabled=True
swift post --read-acl $SWIFT_DEFAULT_PROJECT:$SWIFT_MEMBER_USER $STORLETS_STORLET_CONTAINER_NAME
@ -160,46 +147,32 @@ function _install_docker {
# TODO: Add other dirstors.
# This one is geared towards Ubuntu
# See other projects that install docker
DOCKER_UNIX_SOCKET=/var/run/docker.sock
DOCKER_SERVICE_TIMEOUT=5
install_package socat
wget http://get.docker.com -O install_docker.sh
sudo chmod 777 install_docker.sh
sudo bash -x install_docker.sh
sudo rm install_docker.sh
sudo killall docker || true
# systemd env doesn't require /etc/default/docker options
if [[ ! -e /etc/default/docker ]]; then
sudo touch /etc/default/docker
sudo ls /lib/systemd/system
sudo sed -i '0,/[service]/a EnvironmentFile=-/etc/default/docker' /lib/systemd/system/docker.service
sudo cat /lib/systemd/system/docker.service
# Add swift user to docker group so that the user can manage docker
# containers without sudo
sudo grep -q docker /etc/group
if [ $? -ne 0 ]; then
sudo groupadd docker
fi
sudo cat /etc/default/docker
sudo sed -r 's#^.*DOCKER_OPTS=.*$#DOCKER_OPTS="--debug -g /home/docker_device/docker --storage-opt dm.override_udev_sync_check=true"#' /etc/default/docker
add_user_to_group $STORLETS_SWIFT_RUNTIME_USER docker
# Start the daemon - restart just in case the package ever auto-starts...
if [ $STORLETS_SWIFT_RUNTIME_USER == $USER ]; then
# NOTE(takashi): We need this workaroud because we can't reload
# user-group relationship in bash scripts
DOCKER_UNIX_SOCKET=/var/run/docker.sock
sudo chown $USER:$USER $DOCKER_UNIX_SOCKET
fi
# Restart docker daemon
restart_service docker
echo "Waiting for docker daemon to start..."
DOCKER_GROUP=$(groups | cut -d' ' -f1)
CONFIGURE_CMD="while ! /bin/echo -e 'GET /version HTTP/1.0\n\n' | socat - unix-connect:$DOCKER_UNIX_SOCKET 2>/dev/null | grep -q '200 OK'; do
# Set the right group on docker unix socket before retrying
sudo chgrp $DOCKER_GROUP $DOCKER_UNIX_SOCKET
sudo chmod g+rw $DOCKER_UNIX_SOCKET
sleep 1
done"
if ! timeout $DOCKER_SERVICE_TIMEOUT sh -c "$CONFIGURE_CMD"; then
die $LINENO "docker did not start"
fi
}
function prepare_storlets_install {
sudo mkdir -p "$STORLETS_DOCKER_DEVICE"/docker
sudo chmod 777 $STORLETS_DOCKER_DEVICE
_install_docker
if is_ubuntu; then
@ -236,11 +209,11 @@ EOF
function create_base_jre_image {
echo "Create base jre image"
docker pull $STORLETS_DOCKER_BASE_IMG
sudo docker pull $STORLETS_DOCKER_BASE_IMG
mkdir -p ${TMP_REGISTRY_PREFIX}/repositories/"$STORLETS_DOCKER_BASE_IMG_NAME"_jre${STORLETS_JDK_VERSION}
_generate_jre_dockerfile
cd ${TMP_REGISTRY_PREFIX}/repositories/"$STORLETS_DOCKER_BASE_IMG_NAME"_jre${STORLETS_JDK_VERSION}
docker build -q -t ${STORLETS_DOCKER_BASE_IMG_NAME}_jre${STORLETS_JDK_VERSION} .
sudo docker build -q -t ${STORLETS_DOCKER_BASE_IMG_NAME}_jre${STORLETS_JDK_VERSION} .
cd -
}
@ -292,7 +265,7 @@ function create_storlet_engine_image {
_generate_logback_xml
_generate_jre_storlet_dockerfile
cd ${TMP_REGISTRY_PREFIX}/repositories/"$STORLETS_DOCKER_BASE_IMG_NAME"_jre${STORLETS_JDK_VERSION}_storlets
docker build -q -t ${STORLETS_DOCKER_BASE_IMG_NAME}_jre${STORLETS_JDK_VERSION}_storlets .
sudo docker build -q -t ${STORLETS_DOCKER_BASE_IMG_NAME}_jre${STORLETS_JDK_VERSION}_storlets .
cd -
}
@ -317,12 +290,8 @@ function install_storlets_code {
sudo cp `which ${bin_file}` /usr/local/libexec/storlets/
done
sudo mkdir -p $STORLETS_DOCKER_DEVICE/scripts
sudo chown "$STORLETS_SWIFT_RUNTIME_USER":"$STORLETS_SWIFT_RUNTIME_GROUP" "$STORLETS_DOCKER_DEVICE"/scripts
sudo chmod 0755 "$STORLETS_DOCKER_DEVICE"/scripts
sudo cp scripts/restart_docker_container "$STORLETS_DOCKER_DEVICE"/scripts/
sudo chmod 04755 "$STORLETS_DOCKER_DEVICE"/scripts/restart_docker_container
sudo chown root:root "$STORLETS_DOCKER_DEVICE"/scripts/restart_docker_container
sudo mkdir -p -m 0755 $STORLETS_DOCKER_DEVICE
sudo chown -R "$STORLETS_SWIFT_RUNTIME_USER":"$STORLETS_SWIFT_RUNTIME_GROUP" $STORLETS_DOCKER_DEVICE
# NOTE(takashi): We should cleanup egg-info directory here, otherwise it
# causes permission denined when installing package by tox.
@ -334,13 +303,11 @@ function install_storlets_code {
function _generate_swift_middleware_conf {
cat <<EOF > /tmp/swift_middleware_conf
[proxy-confs]
proxy_server_conf_file = /etc/swift/proxy-server.conf
storlet_proxy_server_conf_file = /etc/swift/storlet-proxy-server.conf
proxy_server_conf_file = ${SWIFT_CONF_DIR}/proxy-server.conf
storlet_proxy_server_conf_file = ${SWIFT_CONF_DIR}/storlet-proxy-server.conf
[object-confs]
object_server_conf_files = /etc/swift/object-server/1.conf
#object_server_conf_files = /etc/swift/object-server/1.conf, /etc/swift/object-server/2.conf, /etc/swift/object-server/3.conf, /etc/swift/object-server/4.conf
#object_server_conf_files = /etc/swift/object-server.conf
object_server_conf_files = ${SWIFT_CONF_DIR}/object-server/1.conf
[common-confs]
storlet_middleware = $STORLETS_MIDDLEWARE_NAME
@ -379,7 +346,7 @@ function create_default_tenant_image {
mkdir -p ${TMP_REGISTRY_PREFIX}/repositories/$SWIFT_DEFAULT_PROJECT_ID
_generate_default_tenant_dockerfile
cd ${TMP_REGISTRY_PREFIX}/repositories/$SWIFT_DEFAULT_PROJECT_ID
docker build -q -t ${SWIFT_DEFAULT_PROJECT_ID:0:13} .
sudo docker build -q -t ${SWIFT_DEFAULT_PROJECT_ID:0:13} .
cd -
}
@ -414,12 +381,12 @@ function install_storlets {
create_test_config_file
echo "restart swift"
_storlets_swift_restart
stop_swift
start_swift
}
function uninstall_storlets {
sudo service docker stop
sudo sed -r 's#^.*DOCKER_OPTS=.*$#DOCKER_OPTS="--debug --storage-opt dm.override_udev_sync_check=true"#' /etc/default/docker
echo "Cleaning all storlets runtime stuff..."
sudo rm -fr ${STORLETS_DOCKER_DEVICE}

View File

@ -130,6 +130,14 @@ We need the following for Docker
sed -i '$acomplete -F _docker docker' /etc/bash_completion.d/docker
update-rc.d docker defaults
Also, add swift user to docker group so that the user can manage docker
containers without sudo
::
sudo usermod -aG docker swift
Get and install the storlets code
---------------------------------
@ -258,18 +266,6 @@ Create the run time directory
sudo mkdir -p $STORLETS_HOME
sudo chmod 777 $STORLETS_HOME
Create the scripts directory and populate it.
Note that these scripts are executed by the middleware but
require root privileges.
::
mkdir $STORLETS_HOME/scripts
cd STORLETS_HOME/scripts
cp $HOME/scripts/restart_docker_container .
sudo chown root:root restart_docker_container
sudo chmod 04755 restart_docker_container
The run time directory will be later populated by the middleware with:
#. storlets - Docker container mapped directories keeping storlet jars
#. pipe - A Docker container mapped directories holding named pipes shared between the middleware and the containers.

View File

@ -6,12 +6,6 @@
# so you may need root privilege to execute this script
set -x
# build scripts
cd scripts
# TODO(takashi): also install them
make
cd -
# install c library
cd src/c/sbus
make && make install

View File

@ -7,3 +7,4 @@ setuptools>=17.1
eventlet>=0.17.4 # MIT
greenlet>=0.3.1
stevedore>=1.16.0 # Apache-2.0
docker

View File

@ -103,7 +103,6 @@ function uninstall_swift_using_devstack {
sudo sed -i.bak '/swift.img/d' /etc/fstab
}
function uninstall_s2aio {
_prepare_devstack_env
@ -131,6 +130,7 @@ case $COMMAND in
"stop" )
stop_s2aio
;;
* )
usage
esac

View File

@ -1,16 +0,0 @@
CC = gcc
CFLAGS =
LDFLAGS =
TARGET = restart_docker_container
SRCS = restart_docker_container.c
OBJS = $(SRCS:.c=.o)
.PHONY: all
all: ${TARGET}
$(TARGET): $(OBJS)
$(CC) ${LDFLAGS} -o $@ $^
clean:
rm ${TARGET} ${OBJS}

View File

@ -1,87 +0,0 @@
/*----------------------------------------------------------------------------
* Copyright IBM Corp. 2015, 2015 All Rights Reserved
* Copyright (c) 2010-2016 OpenStack Foundation
* 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.
* ---------------------------------------------------------------------------
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
/*
* Stop and Run a docker container using:
* docker stop <container name>
* docker run --net=none --name <container name> -d -v /dev/log:/dev/log \
* -v <mount dir 1> -v <mount dir 2> -v <mount dir 3> -v <mount dir 4> \
* <image name>
*
* <container name> - The name of the container to stop / to start
* <image name> - the name of the image from which to start the container
* <mount dir 1> - The directory where the named pipes are placed.
* Typically mounted to /mnt/channels in the container
* <mount dir 2> - The directory where the storlets are placed.
* Typically mounted to /home/swift in the container
* <mount dir 3> - The directory where storlets library are placed.
* Typically mounted to /usr/local/lib/storlets
* <mount dir 4> - The directory where storlets executables are placed.
* Typically mounted to /usr/local/libexec/storlets
*/
int main(int argc, char **argv) {
char command[4096];
char container_name[256];
char container_image[256];
char mount_dir1[512];
char mount_dir2[512];
char mount_dir3[512];
char mount_dir4[512];
if (argc != 7) {
fprintf(stderr, "Usage: %s container_name container_image mount_dir1 mount_dir2 mount_dir3 mount_dir4\n",
argv[0]);
return 1;
}
snprintf(container_name,(size_t)256,"%s",argv[1]);
snprintf(container_image,(size_t)256,"%s",argv[2]);
snprintf(mount_dir1,(size_t)512, "%s", argv[3]);
snprintf(mount_dir2,(size_t)512, "%s", argv[4]);
snprintf(mount_dir3,(size_t)512, "%s", argv[5]);
snprintf(mount_dir4,(size_t)512, "%s", argv[6]);
int ret;
setresuid(0, 0, 0);
setresgid(0, 0, 0);
sprintf(command, "/usr/bin/docker stop -t 1 %s", container_name);
ret = system(command);
sprintf(command, "/usr/bin/docker rm %s", container_name);
ret = system(command);
sprintf(command,
"/usr/bin/docker run --net=none --name %s -d -v /dev/log:/dev/log -v %s -v %s -v %s -v %s %s",
container_name,
mount_dir1,
mount_dir2,
mount_dir3,
mount_dir4,
container_image);
ret = system(command);
if(ret){
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -17,10 +17,12 @@ import errno
import os
import select
import stat
import subprocess
import sys
import time
import six
import docker
import docker.errors
from docker.types import Mount as DockerMount
import eventlet
import json
@ -290,38 +292,44 @@ class RunTimeSandbox(object):
docker_container_name = '%s_%s' % (self.docker_image_name_prefix,
self.scope)
pipe_mount = '%s:%s' % (self.paths.host_pipe_dir,
self.paths.sandbox_pipe_dir)
storlet_mount = '%s:%s:ro' % (self.paths.host_storlet_base_dir,
self.paths.sandbox_storlet_base_dir)
storlet_native_lib_mount = '%s:%s:ro' % (
self.paths.host_storlet_native_lib_dir,
self.paths.sandbox_storlet_native_lib_dir)
storlet_native_bin_mount = '%s:%s:ro' % (
self.paths.host_storlet_native_bin_dir,
self.paths.sandbox_storlet_native_bin_dir)
mounts = [
DockerMount('/dev/log', '/dev/log', type='bind'),
DockerMount(self.paths.sandbox_pipe_dir,
self.paths.host_pipe_dir,
type='bind'),
DockerMount(self.paths.sandbox_storlet_base_dir,
self.paths.host_storlet_base_dir,
type='bind'),
DockerMount(self.paths.sandbox_storlet_native_lib_dir,
self.paths.host_storlet_native_lib_dir,
type='bind', read_only=True),
DockerMount(self.paths.sandbox_storlet_native_bin_dir,
self.paths.host_storlet_native_bin_dir,
type='bind', read_only=True)
]
cmd = [os.path.join(self.paths.host_restart_script_dir,
'restart_docker_container'),
docker_container_name, docker_image_name, pipe_mount,
storlet_mount, storlet_native_lib_mount,
storlet_native_bin_mount]
try:
client = docker.from_env()
# Stop the existing storlet container
try:
scontainer = client.containers.get(docker_container_name)
except docker.errors.NotFound:
# The container is not yet created
pass
else:
scontainer.stop(timeout=1)
scontainer.remove()
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
if stdout:
if not isinstance(stdout, str):
stdout = stdout.decode("utf-8")
self.logger.debug('STDOUT: %s' % stdout.replace('\n', '#012'))
if stderr:
if not isinstance(stderr, str):
stderr = stderr.decode("utf-8")
self.logger.error('STDERR: %s' % stderr.replace('\n', '#012'))
if proc.returncode:
raise StorletRuntimeException('Failed to restart docker container')
# Start the new one
client.containers.run(
docker_image_name, detach=True, name=docker_container_name,
network_disabled=True, mounts=mounts)
except docker.errors.ImageNotFound:
msg = "Image %s is not found" % docker_image_name
raise StorletRuntimeException(msg)
except docker.errors.APIError:
self.logger.exception("Failed to manage docker containers")
raise StorletRuntimeException("Docker runtime error")
def restart(self):
"""

View File

@ -21,6 +21,10 @@ import errno
from contextlib import contextmanager
from six import StringIO
from stat import ST_MODE
import docker.client
import docker.errors
import docker.models.containers
from storlets.sbus.client import SBusResponse
from storlets.sbus.client.exceptions import SBusClientIOError, \
@ -244,7 +248,8 @@ class TestRunTimeSandbox(unittest.TestCase):
def setUp(self):
self.logger = FakeLogger()
# TODO(takashi): take these values from config file
self.conf = {'docker_repo': 'localhost:5001'}
self.conf = {'docker_repo': 'localhost:5001',
'default_docker_image_name': 'defaultimage'}
self.scope = '0123456789abc'
self.sbox = RunTimeSandbox(self.scope, self.conf, self.logger)
@ -277,8 +282,8 @@ class TestRunTimeSandbox(unittest.TestCase):
def test_wait(self):
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'SBusClient.ping') as ping, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'time.sleep') as sleep:
mock.patch('storlets.gateway.gateways.docker.runtime.'
'time.sleep') as sleep:
ping.return_value = SBusResponse(True, 'OK')
self.sbox.wait()
self.assertEqual(sleep.call_count, 0)
@ -294,87 +299,161 @@ class TestRunTimeSandbox(unittest.TestCase):
# TODO(takashi): should test timeout case
def test__restart(self):
# storlet container is not running
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_client.containers = mock_containers
mock_containers.get.side_effect = \
docker.errors.NotFound('container is not found')
docker_from_env.return_value = mock_client
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(1, mock_containers.run.call_count)
# storlet container is running
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_client.containers = mock_containers
mock_container = \
mock.MagicMock(spec_set=docker.models.containers.Container)
mock_containers.get.return_value = mock_container
docker_from_env.return_value = mock_client
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(1, mock_container.stop.call_count)
self.assertEqual(1, mock_container.remove.call_count)
self.assertEqual(1, mock_containers.run.call_count)
# get failed
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_client.containers = mock_containers
mock_containers.get.side_effect = \
docker.errors.APIError('api error')
docker_from_env.return_value = mock_client
with self.assertRaises(StorletRuntimeException):
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(0, mock_containers.run.call_count)
# stop failed
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_client.containers = mock_containers
mock_container = \
mock.MagicMock(spec_set=docker.models.containers.Container)
mock_containers.get.return_value = mock_container
mock_container.stop.side_effect = \
docker.errors.APIError('api error')
docker_from_env.return_value = mock_client
with self.assertRaises(StorletRuntimeException):
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(1, mock_container.stop.call_count)
self.assertEqual(0, mock_container.remove.call_count)
self.assertEqual(0, mock_containers.run.call_count)
# remove failed
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_client.containers = mock_containers
mock_container = \
mock.MagicMock(spec_set=docker.models.containers.Container)
mock_containers.get.return_value = mock_container
mock_container.remove.side_effect = \
docker.errors.APIError('api error')
docker_from_env.return_value = mock_client
with self.assertRaises(StorletRuntimeException):
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(1, mock_container.stop.call_count)
self.assertEqual(1, mock_container.remove.call_count)
self.assertEqual(0, mock_containers.run.call_count)
# run failed
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'docker.from_env') as docker_from_env:
mock_client = mock.MagicMock(spec_set=docker.client.DockerClient)
mock_containers = mock.MagicMock(
spec_set=docker.models.containers.ContainerCollection)
mock_containers.run.side_effect = \
docker.errors.APIError('api error')
mock_client.containers = mock_containers
mock_container = \
mock.MagicMock(spec_set=docker.models.containers.Container)
mock_containers.get.return_value = mock_container
docker_from_env.return_value = mock_client
with self.assertRaises(StorletRuntimeException):
self.sbox._restart('storlet_image')
self.assertEqual(1, mock_containers.get.call_count)
self.assertEqual(1, mock_container.stop.call_count)
self.assertEqual(1, mock_container.remove.call_count)
self.assertEqual(1, mock_containers.run.call_count)
def test_restart(self):
class FakeProc(object):
def __init__(self, stdout, stderr, code):
self.stdout = stdout
self.stderr = stderr
self.returncode = code
def communicate(self):
return (self.stdout, self.stderr)
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimePaths.create_host_pipe_dir'), \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'subprocess.Popen') as popen:
_wait = self.sbox.wait
def dummy_wait_success(*args, **kwargs):
return 1
self.sbox.wait = dummy_wait_success
# Test that popen is called successfully
popen.return_value = FakeProc('Try to restart\nOK', '', 0)
'RunTimePaths.create_host_pipe_dir') as pipe_dir, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox._restart') as _restart, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox.wait') as wait:
self.sbox.restart()
self.assertEqual(1, popen.call_count)
self.sbox.wait = _wait
self.assertEqual(1, pipe_dir.call_count)
self.assertEqual(1, _restart.call_count)
self.assertEqual((self.scope,), _restart.call_args.args)
self.assertEqual(1, wait.call_count)
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimePaths.create_host_pipe_dir'), \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'subprocess.Popen') as popen:
_wait = self.sbox.wait
'RunTimePaths.create_host_pipe_dir') as pipe_dir, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox._restart') as _restart, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox.wait') as wait:
_restart.side_effect = [StorletRuntimeException(), None]
self.sbox.restart()
self.assertEqual(1, pipe_dir.call_count)
self.assertEqual(2, _restart.call_count)
self.assertEqual((self.scope,),
_restart.call_args_list[0].args)
self.assertEqual(('defaultimage',),
_restart.call_args_list[1].args)
self.assertEqual(1, wait.call_count)
def dummy_wait_success(*args, **kwargs):
return 1
self.sbox.wait = dummy_wait_success
# Test double failure to restart the container
# for both the tenant image and generic image
popen.return_value = FakeProc('Try to restart', 'Some error', 1)
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimePaths.create_host_pipe_dir') as pipe_dir, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox._restart') as _restart, \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimeSandbox.wait') as wait:
_restart.side_effect = StorletTimeout()
with self.assertRaises(StorletRuntimeException):
self.sbox.restart()
self.assertEqual(2, popen.call_count)
self.sbox.wait = _wait
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimePaths.create_host_pipe_dir'), \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'subprocess.Popen') as popen:
_wait = self.sbox.wait
def dummy_wait_success(*args, **kwargs):
return 1
self.sbox.wait = dummy_wait_success
# Test failure to restart the container for the tenant image
# success for the generic image
popen.side_effect = [FakeProc('Try to restart', 'Some error', 1),
FakeProc('Try to restart\nOK', '', 0)]
self.sbox.restart()
self.assertEqual(2, popen.call_count)
self.sbox.wait = _wait
with mock.patch('storlets.gateway.gateways.docker.runtime.'
'RunTimePaths.create_host_pipe_dir'), \
mock.patch('storlets.gateway.gateways.docker.runtime.'
'subprocess.Popen') as popen:
_wait = self.sbox.wait
def dummy_wait_failure(*args, **kwargs):
raise StorletTimeout()
self.sbox.wait = dummy_wait_failure
popen.return_value = FakeProc('OK', '', 0)
with self.assertRaises(StorletRuntimeException):
self.sbox.restart()
self.sbox.wait = _wait
self.assertEqual(1, pipe_dir.call_count)
self.assertEqual(1, _restart.call_count)
self.assertEqual((self.scope,), _restart.call_args.args)
self.assertEqual(0, wait.call_count)
def test_get_storlet_classpath(self):
storlet_id = 'Storlet.jar'