blob: be826cf5229a96af192aaf6b452b5701825d853a [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
#include <algorithm>
#include <list>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <process/future.hpp>
#include <process/gtest.hpp>
#include <process/owned.hpp>
#include <process/subprocess.hpp>
#include <stout/duration.hpp>
#include <stout/option.hpp>
#include <stout/gtest.hpp>
#include <stout/os/constants.hpp>
#include "docker/docker.hpp"
#include "mesos/resources.hpp"
#include "tests/environment.hpp"
#include "tests/flags.hpp"
#include "tests/mesos.hpp"
#include "tests/containerizer/docker_common.hpp"
using namespace process;
using std::list;
using std::string;
using std::vector;
namespace mesos {
namespace internal {
namespace tests {
static const string NAME_PREFIX = "mesos-docker";
#ifdef __WINDOWS__
static constexpr char DOCKER_MAPPED_DIR_PATH[] = "C:\\mnt\\mesos\\sandbox";
static constexpr char LIST_COMMAND[] = "dir";
#else
static constexpr char DOCKER_MAPPED_DIR_PATH[] = "/mnt/mesos/sandbox";
// Since Mesos doesn't support the `z/Z` flags for docker volumes, if you have
// SELinux on your system, regular `ls` will faill with permission denied.
// `ls -d` just lists the directory name if it exists, which is sufficient for
// these tests.
static constexpr char LIST_COMMAND[] = "ls -d";
#endif // __WINDOWS__
static constexpr char TEST_DIR_NAME[] = "test_dir";
class DockerTest : public MesosTest
{
void SetUp() override
{
MesosTest::SetUp();
Future<Nothing> pull = pullDockerImage(DOCKER_TEST_IMAGE);
LOG_FIRST_N(WARNING, 1) << "Downloading " << string(DOCKER_TEST_IMAGE)
<< ". This may take a while...";
// The Windows image is ~200 MB, while the Linux image is ~2MB, so
// hopefully this is enough time for the Windows image.
AWAIT_READY_FOR(pull, Minutes(10));
}
void TearDown() override
{
Try<Owned<Docker>> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
Future<vector<Docker::Container>> containers =
docker.get()->ps(true, NAME_PREFIX);
AWAIT_READY(containers);
// Cleanup all mesos launched containers.
foreach (const Docker::Container& container, containers.get()) {
AWAIT_READY_FOR(docker.get()->rm(container.id, true), Seconds(30));
}
MesosTest::TearDown();
}
protected:
// Converts `path` to C:\`path` on Windows and /`path` on Linux.
string fromRootDir(const string& str) {
#ifdef __WINDOWS__
return path::join("C:", str);
#else
return "/" + str;
#endif // __WINDOWS__
}
Volume createDockerVolume(
const string& driver,
const string& name,
const string& containerPath)
{
Volume volume;
volume.set_mode(Volume::RW);
volume.set_container_path(containerPath);
Volume::Source* source = volume.mutable_source();
source->set_type(Volume::Source::DOCKER_VOLUME);
Volume::Source::DockerVolume* docker = source->mutable_docker_volume();
docker->set_driver(driver);
docker->set_name(name);
return volume;
}
};
// This test tests the functionality of the docker's interfaces.
TEST_F(DockerTest, ROOT_DOCKER_interface)
{
const string containerName = NAME_PREFIX + "-test";
Resources resources = Resources::parse("cpus:1;mem:512").get();
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
// Verify that we do not see the container.
Future<vector<Docker::Container>> containers =
docker->ps(true, containerName);
AWAIT_READY(containers);
foreach (const Docker::Container& container, containers.get()) {
EXPECT_NE("/" + containerName, container.name);
}
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_value(SLEEP_COMMAND(120));
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
DOCKER_MAPPED_DIR_PATH,
resources);
ASSERT_SOME(runOptions);
// Start the container.
Future<Option<int>> status = docker->run(runOptions.get());
Future<Docker::Container> inspect =
docker->inspect(containerName, Seconds(1));
AWAIT_READY(inspect);
// Should be able to see the container now.
containers = docker->ps();
AWAIT_READY(containers);
bool found = false;
foreach (const Docker::Container& container, containers.get()) {
if ("/" + containerName == container.name) {
found = true;
break;
}
}
EXPECT_TRUE(found);
// Test some fields of the container.
EXPECT_NE("", inspect->id);
EXPECT_EQ("/" + containerName, inspect->name);
EXPECT_SOME(inspect->pid);
// Stop the container.
Future<Nothing> stop = docker->stop(containerName);
AWAIT_READY(stop);
assertDockerKillStatus(status);
// Now, the container should not appear in the result of ps().
// But it should appear in the result of ps(true).
containers = docker->ps();
AWAIT_READY(containers);
foreach (const Docker::Container& container, containers.get()) {
EXPECT_NE("/" + containerName, container.name);
}
containers = docker->ps(true, containerName);
AWAIT_READY(containers);
found = false;
foreach (const Docker::Container& container, containers.get()) {
if ("/" + containerName == container.name) {
found = true;
break;
}
}
EXPECT_TRUE(found);
// Check the container's info, both id and name should remain the
// same since we haven't removed it, but the pid should be none
// since it's not running.
inspect = docker->inspect(containerName);
AWAIT_READY(inspect);
EXPECT_NE("", inspect->id);
EXPECT_EQ("/" + containerName, inspect->name);
EXPECT_NONE(inspect->pid);
// Remove the container.
Future<Nothing> rm = docker->rm(containerName);
AWAIT_READY(rm);
// Should not be able to inspect the container.
inspect = docker->inspect(containerName);
AWAIT_FAILED(inspect);
// Also, now we should not be able to see the container by invoking
// ps(true).
containers = docker->ps(true, containerName);
AWAIT_READY(containers);
foreach (const Docker::Container& container, containers.get()) {
EXPECT_NE("/" + containerName, container.name);
}
runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
DOCKER_MAPPED_DIR_PATH,
resources);
ASSERT_SOME(runOptions);
// Start the container again, this time we will do a "rm -f"
// directly, instead of stopping and rm.
status = docker->run(runOptions.get());
inspect = docker->inspect(containerName, Seconds(1));
AWAIT_READY(inspect);
// Verify that the container is there.
containers = docker->ps();
AWAIT_READY(containers);
found = false;
foreach (const Docker::Container& container, containers.get()) {
if ("/" + containerName == container.name) {
found = true;
break;
}
}
EXPECT_TRUE(found);
// Then do a "rm -f".
rm = docker->rm(containerName, true);
AWAIT_READY(rm);
assertDockerKillStatus(status);
// Verify that the container is totally removed, that is we can't
// find it by ps() or ps(true).
containers = docker->ps();
AWAIT_READY(containers);
foreach (const Docker::Container& container, containers.get()) {
EXPECT_NE("/" + containerName, container.name);
}
containers = docker->ps(true, containerName);
AWAIT_READY(containers);
foreach (const Docker::Container& container, containers.get()) {
EXPECT_NE("/" + containerName, container.name);
}
}
// This tests our 'docker kill' wrapper.
TEST_F(DockerTest, ROOT_DOCKER_kill)
{
const string containerName = NAME_PREFIX + "-test";
Resources resources = Resources::parse("cpus:1;mem:512").get();
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_value(SLEEP_COMMAND(120));
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
DOCKER_MAPPED_DIR_PATH,
resources);
ASSERT_SOME(runOptions);
// Start the container, kill it, and expect it to terminate.
Future<Option<int>> run = docker->run(runOptions.get());
// Note that we cannot issue the kill until we know that the
// run has been processed. We check for this by waiting for
// a successful 'inspect' result.
Future<Docker::Container> inspect =
docker->inspect(containerName, Milliseconds(10));
AWAIT_READY(inspect);
Future<Nothing> kill = docker->kill(
containerName,
SIGKILL);
AWAIT_READY(kill);
assertDockerKillStatus(run);
// Now, the container should not appear in the result of ps().
// But it should appear in the result of ps(true).
Future<vector<Docker::Container>> containers = docker->ps();
AWAIT_READY(containers);
auto nameEq = [&containerName](const Docker::Container& container) {
return "/" + containerName == container.name;
};
EXPECT_TRUE(std::none_of(containers->begin(), containers->end(), nameEq));
containers = docker->ps(true, containerName);
AWAIT_READY(containers);
auto ps = std::find_if(containers->begin(), containers->end(), nameEq);
ASSERT_TRUE(ps != containers->end());
// The container returned from ps should match the name from inspect.
// Note that the name is different from the name in `docker ps`,
// because `docker->ps()` internally calls `docker inspect` for each
// container shown by `docker ps`.
EXPECT_EQ(inspect->name, ps->name);
EXPECT_EQ(inspect->id, ps->id);
// Check the container's info, both id and name should remain the
// same since we haven't removed it, but the pid should be none
// since it's not running.
inspect = docker->inspect(containerName);
AWAIT_READY(inspect);
EXPECT_EQ(ps->id, inspect->id);
EXPECT_EQ(ps->name, inspect->name);
EXPECT_NONE(inspect->pid);
}
// This test tests parsing docker version output.
TEST_F(DockerTest, ROOT_DOCKER_Version)
{
Try<Owned<Docker>> docker = Docker::create(
"echo Docker version 1.7.1, build",
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
AWAIT_EXPECT_EQ(Version(1, 7, 1), docker.get()->version());
docker = Docker::create(
"echo Docker version 1.7.1.fc22, build",
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
AWAIT_EXPECT_EQ(Version(1, 7, 1), docker.get()->version());
docker = Docker::create(
"echo Docker version 1.7.1-fc22, build",
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
AWAIT_EXPECT_EQ(Version(1, 7, 1, {"fc22"}), docker.get()->version());
docker = Docker::create(
"echo Docker version 17.05.0-ce, build 89658bed64",
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
AWAIT_EXPECT_EQ(Version(17, 05, 0, {"ce"}), docker.get()->version());
}
TEST_F(DockerTest, ROOT_DOCKER_CheckCommandWithShell)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
"testContainer",
"dir",
DOCKER_MAPPED_DIR_PATH);
ASSERT_ERROR(runOptions);
}
TEST_F(DockerTest, ROOT_DOCKER_CheckPortResource)
{
const string containerName = NAME_PREFIX + "-port-resource-test";
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
// Make sure the container is removed.
Future<Nothing> remove = docker->rm(containerName, true);
ASSERT_TRUE(process::internal::await(remove, Seconds(10)));
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
ContainerInfo::DockerInfo::PortMapping portMapping;
portMapping.set_host_port(10000);
portMapping.set_container_port(80);
dockerInfo.add_port_mappings()->CopyFrom(portMapping);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
#ifdef __WINDOWS__
commandInfo.set_shell(true);
commandInfo.set_value("exit 0");
#else
commandInfo.set_shell(false);
commandInfo.set_value("true");
#endif // __WINDOWS__
Resources resources =
Resources::parse("ports:[9998-9999];ports:[10001-11000]").get();
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
"dir",
DOCKER_MAPPED_DIR_PATH,
resources);
ASSERT_ERROR(runOptions);
resources = Resources::parse("ports:[9998-9999];ports:[10000-11000]").get();
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
DOCKER_MAPPED_DIR_PATH,
resources);
ASSERT_SOME(runOptions);
Future<Option<int>> run = docker->run(runOptions.get());
AWAIT_EXPECT_WEXITSTATUS_EQ(0, run);
}
TEST_F(DockerTest, ROOT_DOCKER_CancelPull)
{
// Delete the test image if it exists.
Try<Subprocess> s = process::subprocess(
tests::flags.docker + " rmi lingmann/1gb",
Subprocess::PATH(os::DEV_NULL),
Subprocess::PATH(os::DEV_NULL),
Subprocess::PATH(os::DEV_NULL));
ASSERT_SOME(s);
AWAIT_READY_FOR(s->status(), Seconds(30));
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
// Assume that pulling the very large image 'lingmann/1gb' will take
// sufficiently long that we can start it and discard (i.e., cancel
// it) right away and the future will indeed get discarded.
Future<Docker::Image> future =
docker->pull(directory.get(), "lingmann/1gb");
future.discard();
AWAIT_DISCARDED(future);
}
// This test verifies mounting in a relative host path when running a
// docker container works.
TEST_F(DockerTest, ROOT_DOCKER_MountRelativeHostPath)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
const string containerPath = fromRootDir(path::join("tmp", TEST_DIR_NAME));
const string command = string(LIST_COMMAND) + " " + containerPath;
Volume* volume = containerInfo.add_volumes();
volume->set_host_path(TEST_DIR_NAME);
volume->set_container_path(containerPath);
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value(command);
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
const string testDir = path::join(directory.get(), TEST_DIR_NAME);
EXPECT_SOME(os::mkdir(testDir));
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-relative-host-path-test",
directory.get(),
DOCKER_MAPPED_DIR_PATH);
Future<Option<int>> run = docker->run(runOptions.get());
AWAIT_EXPECT_WEXITSTATUS_EQ(0, run);
}
// This test verifies mounting in an absolute host path when running a
// docker container works.
TEST_F(DockerTest, ROOT_DOCKER_MountAbsoluteHostPath)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
const string testDir = path::join(directory.get(), TEST_DIR_NAME);
EXPECT_SOME(os::mkdir(testDir));
const string containerPath = fromRootDir(path::join("tmp", TEST_DIR_NAME));
const string command = string(LIST_COMMAND) + " " + containerPath;
Volume* volume = containerInfo.add_volumes();
volume->set_host_path(testDir);
volume->set_container_path(containerPath);
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value(command);
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-absolute-host-path-test",
directory.get(),
DOCKER_MAPPED_DIR_PATH);
ASSERT_SOME(runOptions);
Future<Option<int>> run = docker->run(runOptions.get());
AWAIT_EXPECT_WEXITSTATUS_EQ(0, run);
}
// This test verifies mounting in an absolute host path to
// a relative container path when running a docker container
// works. Windows does not support mounting volumes inside
// other volumes, so skip this test for Windows.
TEST_F_TEMP_DISABLED_ON_WINDOWS(
DockerTest, ROOT_DOCKER_MountRelativeContainerPath)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
const string testDir = path::join(directory.get(), TEST_DIR_NAME);
EXPECT_SOME(os::mkdir(testDir));
const string containerPath = path::join("tmp", TEST_DIR_NAME);
const string command =
string(LIST_COMMAND) + " " +
path::join(DOCKER_MAPPED_DIR_PATH, containerPath);
Volume* volume = containerInfo.add_volumes();
volume->set_host_path(testDir);
volume->set_container_path(containerPath);
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value(command);
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-relative-container-path-test",
directory.get(),
DOCKER_MAPPED_DIR_PATH);
ASSERT_SOME(runOptions);
Future<Option<int>> run = docker->run(runOptions.get());
AWAIT_EXPECT_WEXITSTATUS_EQ(0, run);
}
// This test verifies a docker container mounting relative host
// path to a relative container path fails.
TEST_F(DockerTest, ROOT_DOCKER_MountRelativeHostPathRelativeContainerPath)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
const string containerPath = path::join("tmp", TEST_DIR_NAME);
const string command =
string(LIST_COMMAND) + " " +
path::join(DOCKER_MAPPED_DIR_PATH, containerPath);
Volume* volume = containerInfo.add_volumes();
volume->set_host_path(TEST_DIR_NAME);
volume->set_container_path(containerPath);
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value(command);
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
const string testDir = path::join(directory.get(), TEST_DIR_NAME);
EXPECT_SOME(os::mkdir(testDir));
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-relative-host-path/container-path-test",
directory.get(),
DOCKER_MAPPED_DIR_PATH);
ASSERT_ERROR(runOptions);
}
class DockerImageTest : public MesosTest {};
// This test verifies that docker image constructor is able to read
// entrypoint and environment from a docker inspect JSON object.
TEST_F(DockerImageTest, ParseInspectonImage)
{
JSON::Value inspect = JSON::parse(
"{"
" \"Id\": "
"\"0a8ee093d995e48aa8af626b8a4c48fe3949e474b0ccca9be9d5cf08abd9eda1\","
" \"Parent\": "
"\"6fbfa9a156a7655f1bbc2b3ca3624850d373fa403555ae42ed05fe5b478588fa\","
" \"Comment\": \"\","
" \"Created\": \"2015-10-01T13:24:42.549270714Z\","
" \"Container\": "
"\"d87a718e07e151623b0310d82b27d2f0acdb1376755ce4aea7a26313cdab379a\","
" \"ContainerConfig\": {"
" \"Hostname\": \"7b840bf4fc5e\","
" \"Domainname\": \"\","
" \"User\": \"\","
" \"AttachStdin\": false,"
" \"AttachStdout\": false,"
" \"AttachStderr\": false,"
" \"PortSpecs\": null,"
" \"ExposedPorts\": null,"
" \"Tty\": false,"
" \"OpenStdin\": false,"
" \"StdinOnce\": false,"
" \"Env\": ["
" \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:"
"/sbin:/bin\","
" \"LANG=C.UTF-8\","
" \"JAVA_VERSION=8u66\","
" \"JAVA_DEBIAN_VERSION=8u66-b01-1~bpo8+1\","
" \"SPARK_OPTS=--driver-java-options=-Xms1024M --driver-java-options=-Xmx4096M --driver-java-options=-Dlog4j.logLevel=info\"," // NOLINT(whitespace/line_length)
" \"CA_CERTIFICATES_JAVA_VERSION=20140324\""
" ],"
" \"Cmd\": ["
" \"/bin/sh\","
" \"-c\","
" \"#(nop) ENTRYPOINT \\u0026{[\\\"./bin/start\\\"]}\""
" ],"
" \"Image\": "
"\"6fbfa9a156a7655f1bbc2b3ca3624850d373fa403555ae42ed05fe5b478588fa\","
" \"Volumes\": null,"
" \"VolumeDriver\": \"\","
" \"WorkingDir\": \"/marathon\","
" \"Entrypoint\": ["
" \"./bin/start\""
" ],"
" \"NetworkDisabled\": false,"
" \"MacAddress\": \"\","
" \"OnBuild\": [],"
" \"Labels\": {}"
" },"
" \"DockerVersion\": \"1.8.3-rc1\","
" \"Author\": \"\","
" \"Config\": {"
" \"Hostname\": \"7b840bf4fc5e\","
" \"Domainname\": \"\","
" \"User\": \"\","
" \"AttachStdin\": false,"
" \"AttachStdout\": false,"
" \"AttachStderr\": false,"
" \"PortSpecs\": null,"
" \"ExposedPorts\": null,"
" \"Tty\": false,"
" \"OpenStdin\": false,"
" \"StdinOnce\": false,"
" \"Env\": ["
" \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:"
"/sbin:/bin\","
" \"LANG=C.UTF-8\","
" \"JAVA_VERSION=8u66\","
" \"JAVA_DEBIAN_VERSION=8u66-b01-1~bpo8+1\","
" \"SPARK_OPTS=--driver-java-options=-Xms1024M --driver-java-options=-Xmx4096M --driver-java-options=-Dlog4j.logLevel=info\"," // NOLINT(whitespace/line_length)
" \"CA_CERTIFICATES_JAVA_VERSION=20140324\""
" ],"
" \"Cmd\": null,"
" \"Image\": "
"\"6fbfa9a156a7655f1bbc2b3ca3624850d373fa403555ae42ed05fe5b478588fa\","
" \"Volumes\": null,"
" \"VolumeDriver\": \"\","
" \"WorkingDir\": \"/marathon\","
" \"Entrypoint\": ["
" \"./bin/start\""
" ],"
" \"NetworkDisabled\": false,"
" \"MacAddress\": \"\","
" \"OnBuild\": [],"
" \"Labels\": {}"
" },"
" \"Architecture\": \"amd64\","
" \"Os\": \"linux\","
" \"Size\": 0,"
" \"VirtualSize\": 977664708"
"}").get();
Try<JSON::Object> json = JSON::parse<JSON::Object>(stringify(inspect));
ASSERT_SOME(json);
Try<Docker::Image> image = Docker::Image::create(json.get());
ASSERT_SOME(image);
EXPECT_EQ("./bin/start", image->entrypoint->front());
EXPECT_EQ("C.UTF-8", image->environment->at("LANG"));
EXPECT_EQ("8u66", image->environment->at("JAVA_VERSION"));
EXPECT_EQ("8u66-b01-1~bpo8+1",
image->environment->at("JAVA_DEBIAN_VERSION"));
EXPECT_EQ("--driver-java-options=-Xms1024M "
"--driver-java-options=-Xmx4096M "
"--driver-java-options=-Dlog4j.logLevel=info",
image->environment->at("SPARK_OPTS"));
EXPECT_EQ("20140324",
image->environment->at("CA_CERTIFICATES_JAVA_VERSION"));
}
// Tests the --devices flag of 'docker run' by adding the
// /dev/nvidiactl device (present alongside Nvidia GPUs).
// Skip this test on Windows, since GPU support does not work yet.
//
// TODO(bmahler): Avoid needing Nvidia GPUs to test this.
TEST_F_TEMP_DISABLED_ON_WINDOWS(DockerTest, ROOT_DOCKER_NVIDIA_GPU_DeviceAllow)
{
const string containerName = NAME_PREFIX + "-test";
Resources resources = Resources::parse("cpus:1;mem:512;gpus:1").get();
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
// Make sure the additional device is exposed (/dev/nvidiactl) and
// make sure that the default devices (e.g. /dev/null) are still
// accessible.
CommandInfo commandInfo;
commandInfo.set_value("touch /dev/nvidiactl && touch /dev/null");
Docker::Device nvidiaCtl;
nvidiaCtl.hostPath = Path("/dev/nvidiactl");
nvidiaCtl.containerPath = Path("/dev/nvidiactl");
nvidiaCtl.access.read = true;
nvidiaCtl.access.write = true;
nvidiaCtl.access.mknod = true;
vector<Docker::Device> devices = { nvidiaCtl };
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
"/mnt/mesos/sandbox",
resources,
false,
None(),
devices);
ASSERT_SOME(runOptions);
Future<Option<int>> status = docker->run(runOptions.get());
AWAIT_EXPECT_WEXITSTATUS_EQ(0, status);
}
// Tests that devices are parsed correctly from 'docker inspect'.
// Skip this test on Windows, since GPU support does not work yet.
//
// TODO(bmahler): Avoid needing Nvidia GPUs to test this and
// merge this into a more general inspect test.
TEST_F_TEMP_DISABLED_ON_WINDOWS(
DockerTest, ROOT_DOCKER_NVIDIA_GPU_InspectDevices)
{
const string containerName = NAME_PREFIX + "-test";
Resources resources = Resources::parse("cpus:1;mem:512;gpus:1").get();
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
// Make sure the additional device is exposed (/dev/nvidiactl) and
// make sure that the default devices (e.g. /dev/null) are still
// accessible. We then sleep to allow time to inspect and verify
// that the device is correctly parsed from the json.
CommandInfo commandInfo;
commandInfo.set_value("touch /dev/nvidiactl && touch /dev/null && sleep 120");
Docker::Device nvidiaCtl;
nvidiaCtl.hostPath = Path("/dev/nvidiactl");
nvidiaCtl.containerPath = Path("/dev/nvidiactl");
nvidiaCtl.access.read = true;
nvidiaCtl.access.write = true;
nvidiaCtl.access.mknod = false;
vector<Docker::Device> devices = { nvidiaCtl };
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
containerName,
directory.get(),
"/mnt/mesos/sandbox",
resources,
false,
None(),
devices);
ASSERT_SOME(runOptions);
Future<Option<int>> status = docker->run(runOptions.get());
Future<Docker::Container> container =
docker->inspect(containerName, Milliseconds(1));
AWAIT_READY(container);
EXPECT_EQ(1u, container->devices.size());
EXPECT_EQ(nvidiaCtl.hostPath, container->devices[0].hostPath);
EXPECT_EQ(nvidiaCtl.containerPath, container->devices[0].hostPath);
EXPECT_EQ(nvidiaCtl.access.read, container->devices[0].access.read);
EXPECT_EQ(nvidiaCtl.access.write, container->devices[0].access.write);
EXPECT_EQ(nvidiaCtl.access.mknod, container->devices[0].access.mknod);
AWAIT_READY(docker->kill(containerName, SIGKILL));
AWAIT_EXPECT_WEXITSTATUS_EQ(128 + SIGKILL, status);
}
// This tests verifies that a task requiring more than one volume driver (in
// multiple Volumes) is rejected.
TEST_F(DockerTest, ROOT_DOCKER_ConflictingVolumeDriversInMultipleVolumes)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
const string containerPath1 = fromRootDir(path::join("tmp", "test1"));
const string containerPath2 = fromRootDir(path::join("tmp", "test2"));
Volume volume1 = createDockerVolume("driver1", "name1", containerPath1);
containerInfo.add_volumes()->CopyFrom(volume1);
Volume volume2 = createDockerVolume("driver2", "name2", containerPath2);
containerInfo.add_volumes()->CopyFrom(volume2);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(false);
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
"testContainer",
"dir",
DOCKER_MAPPED_DIR_PATH);
ASSERT_ERROR(runOptions);
}
// This tests verifies that a task requiring more than one volume driver (via
// Volume.Source.DockerInfo.driver and ContainerInfo.DockerInfo.volume_driver)
// is rejected.
TEST_F(DockerTest, ROOT_DOCKER_ConflictingVolumeDrivers)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
const string containerPath = fromRootDir(path::join("tmp", "test1"));
Volume volume1 = createDockerVolume("driver", "name1", containerPath);
containerInfo.add_volumes()->CopyFrom(volume1);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image(DOCKER_TEST_IMAGE);
dockerInfo.set_volume_driver("driver1");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(false);
Try<Docker::RunOptions> runOptions = Docker::RunOptions::create(
containerInfo,
commandInfo,
"testContainer",
"dir",
DOCKER_MAPPED_DIR_PATH);
ASSERT_ERROR(runOptions);
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {