blob: 620819330847a10d9dcd817968df9d2b180a9a29 [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 <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 "docker/docker.hpp"
#include "mesos/resources.hpp"
#include "tests/environment.hpp"
#include "tests/flags.hpp"
#include "tests/mesos.hpp"
using namespace process;
using std::list;
using std::string;
namespace mesos {
namespace internal {
namespace tests {
static const string NAME_PREFIX="mesos-docker";
class DockerTest : public MesosTest
{
virtual void TearDown()
{
Try<Owned<Docker>> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false);
ASSERT_SOME(docker);
Future<list<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));
}
}
};
// 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<list<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();
CHECK_SOME(directory) << "Failed to create temporary directory";
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_value("sleep 120");
// Start the container.
Future<Nothing> status = docker->run(
containerInfo,
commandInfo,
containerName,
directory.get(),
"/mnt/mesos/sandbox",
resources);
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.get().id);
EXPECT_EQ("/" + containerName, inspect.get().name);
EXPECT_SOME(inspect.get().pid);
// Stop the container.
status = docker->stop(containerName);
AWAIT_READY(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.get().id);
EXPECT_EQ("/" + containerName, inspect.get().name);
EXPECT_NONE(inspect.get().pid);
// Remove the container.
status = docker->rm(containerName);
AWAIT_READY(status);
// 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);
}
// Start the container again, this time we will do a "rm -f"
// directly, instead of stopping and rm.
status = docker->run(
containerInfo,
commandInfo,
containerName,
directory.get(),
"/mnt/mesos/sandbox",
resources);
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".
status = docker->rm(containerName, true);
AWAIT_READY(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 test tests parsing docker version output.
TEST_F(DockerTest, ROOT_DOCKER_parsing_version)
{
Owned<Docker> docker1 = Docker::create(
"echo Docker version 1.7.1, build",
tests::flags.docker_socket,
false).get();
Future<Version> version1 = docker1->version();
AWAIT_READY(version1);
EXPECT_EQ(version1.get(), Version::parse("1.7.1").get());
Owned<Docker> docker2 = Docker::create(
"echo Docker version 1.7.1.fc22, build",
tests::flags.docker_socket,
false).get();
Future<Version> version2 = docker2->version();
AWAIT_READY(version2);
EXPECT_EQ(version2.get(), Version::parse("1.7.1").get());
Owned<Docker> docker3 = Docker::create(
"echo Docker version 1.7.1-fc22, build",
tests::flags.docker_socket,
false).get();
Future<Version> version3 = docker3->version();
AWAIT_READY(version3);
EXPECT_EQ(version3.get(), Version::parse("1.7.1").get());
}
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("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
Future<Nothing> run = docker->run(
containerInfo,
commandInfo,
"testContainer",
"dir",
"/mnt/mesos/sandbox");
ASSERT_TRUE(run.isFailed());
}
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("alpine");
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;
commandInfo.set_shell(false);
commandInfo.set_value("true");
Resources resources =
Resources::parse("ports:[9998-9999];ports:[10001-11000]").get();
Future<Nothing> run = docker->run(
containerInfo,
commandInfo,
containerName,
"dir",
"/mnt/mesos/sandbox",
resources);
// Port should be out side of the provided ranges.
AWAIT_EXPECT_FAILED(run);
resources = Resources::parse("ports:[9998-9999];ports:[10000-11000]").get();
Try<string> directory = environment->mkdtemp();
CHECK_SOME(directory) << "Failed to create temporary directory";
run = docker->run(
containerInfo,
commandInfo,
containerName,
directory.get(),
"/mnt/mesos/sandbox",
resources);
AWAIT_READY(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("/dev/null"),
Subprocess::PATH("/dev/null"),
Subprocess::PATH("/dev/null"));
ASSERT_SOME(s);
AWAIT_READY_FOR(s.get().status(), Seconds(30));
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
Try<string> directory = environment->mkdtemp();
CHECK_SOME(directory) << "Failed to create temporary 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 path when running a
// docker container works.
TEST_F(DockerTest, ROOT_DOCKER_MountRelative)
{
Owned<Docker> docker = Docker::create(
tests::flags.docker,
tests::flags.docker_socket,
false).get();
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::DOCKER);
Volume* volume = containerInfo.add_volumes();
volume->set_host_path("test_file");
volume->set_container_path("/tmp/test_file");
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value("ls /tmp/test_file");
Try<string> directory = environment->mkdtemp();
CHECK_SOME(directory) << "Failed to create temporary directory";
const string testFile = path::join(directory.get(), "test_file");
EXPECT_SOME(os::write(testFile, "data"));
Future<Nothing> run = docker->run(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-relative-test",
directory.get(),
directory.get());
AWAIT_READY(run);
}
// This test verifies mounting in an absolute path when running a
// docker container works.
TEST_F(DockerTest, ROOT_DOCKER_MountAbsolute)
{
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();
CHECK_SOME(directory) << "Failed to create temporary directory";
const string testFile = path::join(directory.get(), "test_file");
EXPECT_SOME(os::write(testFile, "data"));
Volume* volume = containerInfo.add_volumes();
volume->set_host_path(testFile);
volume->set_container_path("/tmp/test_file");
volume->set_mode(Volume::RO);
ContainerInfo::DockerInfo dockerInfo;
dockerInfo.set_image("alpine");
containerInfo.mutable_docker()->CopyFrom(dockerInfo);
CommandInfo commandInfo;
commandInfo.set_shell(true);
commandInfo.set_value("ls /tmp/test_file");
Future<Nothing> run = docker->run(
containerInfo,
commandInfo,
NAME_PREFIX + "-mount-absolute-test",
directory.get(),
directory.get());
AWAIT_READY(run);
}
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.get().entrypoint.get().front());
EXPECT_EQ("C.UTF-8", image.get().environment.get().at("LANG"));
EXPECT_EQ("8u66", image.get().environment.get().at("JAVA_VERSION"));
EXPECT_EQ("8u66-b01-1~bpo8+1",
image.get().environment.get().at("JAVA_DEBIAN_VERSION"));
EXPECT_EQ("--driver-java-options=-Xms1024M "
"--driver-java-options=-Xmx4096M "
"--driver-java-options=-Dlog4j.logLevel=info",
image.get().environment.get().at("SPARK_OPTS"));
EXPECT_EQ("20140324",
image.get().environment.get().at("CA_CERTIFICATES_JAVA_VERSION"));
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {