blob: 1360db4f779e98ac9114e41ba6b0c798686e9d40 [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 <unistd.h>
#include <string>
#include <process/future.hpp>
#include <process/owned.hpp>
#include <stout/gtest.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include <stout/uuid.hpp>
#include <mesos/mesos.hpp>
#include <mesos/slave/containerizer.hpp>
#ifdef __linux__
#include "linux/ns.hpp"
#endif
#include "slave/containerizer/fetcher.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
#include "slave/containerizer/mesos/paths.hpp"
#include "tests/mesos.hpp"
using std::string;
using process::Future;
using process::Owned;
using mesos::internal::slave::Containerizer;
using mesos::internal::slave::Fetcher;
using mesos::internal::slave::MesosContainerizer;
using mesos::internal::slave::containerizer::paths::getSandboxPath;
using mesos::slave::ContainerClass;
using mesos::slave::ContainerTermination;
namespace mesos {
namespace internal {
namespace tests {
#ifdef __linux__
class NamespacesIsolatorTest : public MesosTest
{
public:
void SetUp() override
{
MesosTest::SetUp();
directory = os::getcwd(); // We're inside a temporary sandbox.
containerId.set_value(id::UUID::random().toString());
}
Try<Owned<MesosContainerizer>> createContainerizer(
const string& isolation,
const Option<bool>& disallowSharingAgentPidNamespace = None(),
const Option<bool>& disallowSharingAgentIpcNamespace = None(),
const Option<Bytes>& defaultContainerShmSize = None())
{
slave::Flags flags = CreateSlaveFlags();
flags.image_providers = "docker";
flags.isolation = isolation + ",docker/runtime";
if (disallowSharingAgentPidNamespace.isSome()) {
flags.disallow_sharing_agent_pid_namespace =
disallowSharingAgentPidNamespace.get();
}
if (disallowSharingAgentIpcNamespace.isSome()) {
flags.disallow_sharing_agent_ipc_namespace =
disallowSharingAgentIpcNamespace.get();
}
flags.default_container_shm_size = defaultContainerShmSize;
fetcher.reset(new Fetcher(flags));
Try<MesosContainerizer*> _containerizer =
MesosContainerizer::create(flags, true, fetcher.get());
if (_containerizer.isError()) {
return Error(_containerizer.error());
}
return Owned<MesosContainerizer>(_containerizer.get());
}
// Read a uint64_t value from the given path.
Try<uint64_t> readValue(const string& path)
{
Try<string> value = os::read(path);
if (value.isError()) {
return Error("Failed to read '" + path + "': " + value.error());
}
return numify<uint64_t>(strings::trim(value.get()));
}
string directory;
Owned<Fetcher> fetcher;
ContainerID containerId;
};
TEST_F(NamespacesIsolatorTest, ROOT_PidNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid");
ASSERT_SOME(containerizer);
// Write the command's pid namespace inode and init name to files.
const string command =
"stat -Lc %i /proc/1/ns/pid > ns && (cat /proc/1/cmdline > init)";
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
// Check the executor exited correctly.
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that the command was run in a different pid namespace.
Result<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
ASSERT_SOME(testPidNamespace);
Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerPidNamespace);
EXPECT_NE(stringify(testPidNamespace.get()),
strings::trim(containerPidNamespace.get()));
// Check that the word 'mesos' is the part of the name for the
// container's 'init' process. This verifies that /proc has been
// correctly mounted for the container.
Try<string> init = os::read(path::join(directory, "init"));
ASSERT_SOME(init);
EXPECT_TRUE(strings::contains(init.get(), "mesos"));
}
// This test verifies a top-level container can share pid namespace
// with the agent when the field `share_pid_namespace` is set as
// true in `ContainerInfo.linux_info`. Please note that the agent flag
// `--disallow_sharing_agent_pid_namespace` is set to
// false by default, that means top-level container is allowed to share
// pid namespace with agent.
TEST_F(NamespacesIsolatorTest, ROOT_SharePidNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid");
ASSERT_SOME(containerizer);
// Write the command's pid namespace inode to file.
const string command = "stat -Lc %i /proc/1/ns/pid > ns";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_share_pid_namespace(true);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
// Check the executor exited correctly.
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that the command was run in the same pid namespace.
Result<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
ASSERT_SOME(testPidNamespace);
Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerPidNamespace);
EXPECT_EQ(stringify(testPidNamespace.get()),
strings::trim(containerPidNamespace.get()));
}
// This test verifies launching a top-level container to share
// pid namespace with agent will fail when the agent flag
// `--disallow_sharing_agent_pid_namespace` is set to true.
TEST_F(NamespacesIsolatorTest, ROOT_SharePidNamespaceWhenDisallow)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid", true);
ASSERT_SOME(containerizer);
const string command = "sleep 1000";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_share_pid_namespace(true);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
}
// This test verifies that when `namespaces/ipc` isolator is enabled and
// container's IPC mode is not set, for backward compatibility we will
// keep the previous behavior: Top level container will have its own IPC
// namespace and nested container will share the IPC namespace with its
// parent container. If the container does not have its own rootfs, it
// will share agent's /dev/shm, otherwise it will have its own /dev/shm.
TEST_F(NamespacesIsolatorTest, ROOT_INTERNET_IPCNamespaceWithIPCModeUnset)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc");
ASSERT_SOME(containerizer);
// Value we will set the top-level container's IPC namespace shmmax to.
uint64_t shmmaxValue = static_cast<uint64_t>(::getpid());
Try<uint64_t> hostShmmax = readValue("/proc/sys/kernel/shmmax");
ASSERT_SOME(hostShmmax);
// Verify that the host namespace shmmax is different.
ASSERT_NE(hostShmmax.get(), shmmaxValue);
// Launch a top-level container with IPC mode
// unset and it does not have its own rootfs.
string command =
"stat -Lc %i /proc/self/ns/ipc > ns && "
"echo " + stringify(shmmaxValue) + " > /proc/sys/kernel/shmmax && "
"cp /proc/sys/kernel/shmmax /dev/shm/shmmax && "
"sleep 1000";
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Since the top-level container does not have its own
// rootfs, it will share host's /dev/shm, so let's wait
// until /dev/shm/shmmax is created in the host.
Duration waited = Duration::zero();
do {
if (os::exists("/dev/shm/shmmax")) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch a nested container with IPC mode unset and it has its own rootfs.
ContainerID nestedContainerId;
nestedContainerId.mutable_parent()->CopyFrom(containerId);
nestedContainerId.set_value(id::UUID::random().toString());
mesos::Image image;
image.set_type(mesos::Image::DOCKER);
image.mutable_docker()->set_name("alpine");
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_mesos()->mutable_image()->CopyFrom(image);
command =
"stat -Lc %i /proc/self/ns/ipc > ns && "
"test `cat /proc/sys/kernel/shmmax` = " + stringify(shmmaxValue) + " && "
"touch /dev/shm/file";
launch = containerizer.get()->launch(
nestedContainerId,
createContainerConfig(createCommandInfo(command), containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the nested container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(nestedContainerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that top-level container and the nested container are in the
// same IPC namespace but not in the same IPC namespace with host.
Result<ino_t> testIPCNamespace = ns::getns(::getpid(), "ipc");
ASSERT_SOME(testIPCNamespace);
Try<string> containerIPCNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerIPCNamespace);
Try<string> nestedcontainerIPCNamespace =
os::read(path::join(getSandboxPath(directory, nestedContainerId), "ns"));
ASSERT_SOME(nestedcontainerIPCNamespace);
EXPECT_NE(stringify(testIPCNamespace.get()),
strings::trim(containerIPCNamespace.get()));
EXPECT_EQ(strings::trim(containerIPCNamespace.get()),
strings::trim(nestedcontainerIPCNamespace.get()));
// The nested container will have its own /dev/shm since it has its own
// rootfs, so the file it created should not exist in the host.
ASSERT_FALSE(os::exists("/dev/shm/file"));
// Check that we modified the IPC shmmax of the namespace, not the host.
Try<uint64_t> containerShmmax = readValue("/dev/shm/shmmax");
ASSERT_SOME(containerShmmax);
// Verify that we didn't modify shmmax in the host namespace.
ASSERT_EQ(hostShmmax.get(), readValue("/proc/sys/kernel/shmmax").get());
EXPECT_NE(hostShmmax.get(), containerShmmax.get());
EXPECT_EQ(shmmaxValue, containerShmmax.get());
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
ASSERT_SOME(os::rm("/dev/shm/shmmax"));
}
// This test verifies that when `namespaces/ipc` isolator is not enabled,
// for backward compatibility we will keep the previous behavior: Any
// containers will share IPC namespace with agent, and if the container
// does not have its own rootfs, it will also share agent's /dev/shm,
// otherwise it will have its own /dev/shm.
TEST_F(NamespacesIsolatorTest,
ROOT_INTERNET_IPCNamespaceWithIPCIsolatorDisabled)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux");
ASSERT_SOME(containerizer);
// Launch a top-level container which does not have its own rootfs.
string command =
"stat -Lc %i /proc/self/ns/ipc > ns && "
"touch /dev/shm/root && "
"sleep 1000";
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Since the top-level container does not have its own
// rootfs, it will share host's /dev/shm, so let's wait
// until /dev/shm/root is created in the host.
Duration waited = Duration::zero();
do {
if (os::exists("/dev/shm/root")) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch a nested container which has its own rootfs.
ContainerID nestedContainerId;
nestedContainerId.mutable_parent()->CopyFrom(containerId);
nestedContainerId.set_value(id::UUID::random().toString());
mesos::Image image;
image.set_type(mesos::Image::DOCKER);
image.mutable_docker()->set_name("alpine");
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_mesos()->mutable_image()->CopyFrom(image);
command =
"stat -Lc %i /proc/self/ns/ipc > ns && "
"touch /dev/shm/nested";
launch = containerizer.get()->launch(
nestedContainerId,
createContainerConfig(createCommandInfo(command), containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the nested container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(nestedContainerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that top-level container and the nested container are
// in the same IPC namespace with host.
Result<ino_t> testIPCNamespace = ns::getns(::getpid(), "ipc");
ASSERT_SOME(testIPCNamespace);
Try<string> containerIPCNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerIPCNamespace);
Try<string> nestedcontainerIPCNamespace =
os::read(path::join(getSandboxPath(directory, nestedContainerId), "ns"));
ASSERT_SOME(nestedcontainerIPCNamespace);
EXPECT_EQ(stringify(testIPCNamespace.get()),
strings::trim(containerIPCNamespace.get()));
EXPECT_EQ(strings::trim(containerIPCNamespace.get()),
strings::trim(nestedcontainerIPCNamespace.get()));
// The nested container will have its own /dev/shm since it has its own
// rootfs, so the file it created should not exist in the host.
ASSERT_FALSE(os::exists("/dev/shm/nested"));
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
ASSERT_SOME(os::rm("/dev/shm/root"));
}
// This test verifies that a top-level container with private IPC mode will
// have its own IPC namespace and /dev/shm, and it can share IPC namespace
// and /dev/shm with its child containers, grandchild containers and debug
// containers.
TEST_F(NamespacesIsolatorTest, ROOT_INTERNET_ShareIPCNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc");
ASSERT_SOME(containerizer);
// Launch a top-level container with `PRIVATE` IPC mode and 128MB /dev/shm,
// check its /dev/shm size is correctly set and its IPC namespace is
// different than agent's IPC namespace, write its IPC namespace inode to
// a file under /dev/shm.
const string command =
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` != `stat -Lc %i /proc/1/ns/ipc` && "
"stat -Lc %i /proc/self/ns/ipc > /dev/shm/root && "
"touch marker && "
"sleep 1000";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_ipc_mode(LinuxInfo::PRIVATE);
container->mutable_linux_info()->set_shm_size(128);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait until the marker file is created.
Duration waited = Duration::zero();
do {
if (os::exists(path::join(directory, "marker"))) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// The file created by the top-level container should only exist in
// its own /dev/shm rather than in agent's /dev/shm.
ASSERT_FALSE(os::exists("/dev/shm/root"));
// Now launch two child containers with `SHARE_PARENT` ipc mode.
ContainerID childContainerId1, childContainerId2;
childContainerId1.mutable_parent()->CopyFrom(containerId);
childContainerId1.set_value(id::UUID::random().toString());
childContainerId2.mutable_parent()->CopyFrom(containerId);
childContainerId2.set_value(id::UUID::random().toString());
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_linux_info()->set_ipc_mode(LinuxInfo::SHARE_PARENT);
// Launch the first child container, check its /dev/shm size is 128MB, it
// can see the file created by its parent container in /dev/shm and it is
// in the same IPC namespace with its parent container, and then write its
// IPC namespace inode to a file under /dev/shm.
launch = containerizer.get()->launch(
childContainerId1,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/root` && "
"stat -Lc %i /proc/self/ns/ipc > /dev/shm/child1 && "
"touch marker && "
"sleep 1000"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait until the marker file is created.
waited = Duration::zero();
const string childSandboxPath1 = getSandboxPath(directory, childContainerId1);
do {
if (os::exists(path::join(childSandboxPath1, "marker"))) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch the second child container with its own rootfs, check its /dev/shm
// size is 128MB, it can see the files created by its parent container and the
// first child container in /dev/shm and it is in the same IPC namespace with
// its parent container and the first child container, and then write its IPC
// namespace inode to a file under /dev/shm.
mesos::Image image;
image.set_type(mesos::Image::DOCKER);
image.mutable_docker()->set_name("alpine");
containerInfo.mutable_mesos()->mutable_image()->CopyFrom(image);
launch = containerizer.get()->launch(
childContainerId2,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/root` && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/child1` && "
"stat -Lc %i /proc/self/ns/ipc > /dev/shm/child2 && "
"touch marker && "
"sleep 1000"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait until the marker file is created.
waited = Duration::zero();
const string childSandboxPath2 = getSandboxPath(directory, childContainerId2);
do {
if (os::exists(path::join(childSandboxPath2, "marker"))) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch a grandchild container with `SHARE_PARENT` ipc mode under the first
// child container, check its /dev/shm size is 128MB, it can see the files
// created by its parent and grandparent containers and it is in the same IPC
// namespace with its parent and grandparent containers.
ContainerID grandchildContainerId;
grandchildContainerId.mutable_parent()->CopyFrom(childContainerId1);
grandchildContainerId.set_value(id::UUID::random().toString());
launch = containerizer.get()->launch(
grandchildContainerId,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/child1` && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/root`"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(grandchildContainerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
// Launch a debug container under the first child container, check its
// /dev/shm size is 128MB and it is in the same IPC namespace with its
// parent container.
ContainerID debugContainerId1;
debugContainerId1.mutable_parent()->CopyFrom(childContainerId1);
debugContainerId1.set_value(id::UUID::random().toString());
containerInfo.clear_mesos();
containerInfo.clear_linux_info();
launch = containerizer.get()->launch(
debugContainerId1,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/child1`"),
containerInfo,
ContainerClass::DEBUG),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
wait = containerizer.get()->wait(debugContainerId1);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
// Launch another debug container under the second child container, check its
// /dev/shm size is 128MB and it is in the same IPC namespace with its parent
// container.
ContainerID debugContainerId2;
debugContainerId2.mutable_parent()->CopyFrom(childContainerId2);
debugContainerId2.set_value(id::UUID::random().toString());
launch = containerizer.get()->launch(
debugContainerId2,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 && "
"test `stat -Lc %i /proc/self/ns/ipc` = `cat /dev/shm/child2`"),
containerInfo,
ContainerClass::DEBUG),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
wait = containerizer.get()->wait(debugContainerId2);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
}
// This test verifies that nested container with private IPC mode will
// have its own IPC namespace and /dev/shm.
TEST_F(NamespacesIsolatorTest, ROOT_PrivateIPCNamespace)
{
// Create containerizer with `--default_container_shm_size=64MB`.
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
"filesystem/linux,namespaces/ipc",
None(),
None(),
Megabytes(64));
ASSERT_SOME(containerizer);
// Launch a top-level container with `PRIVATE` IPC mode, check its /dev/shm
// size is correctly set to the default value, touch a file in its /dev/shm,
// and write its IPC namespace inode to a file in its sandbox.
const string command =
"df -m /dev/shm | grep -w 64 && "
"touch /dev/shm/root &&"
"stat -Lc %i /proc/self/ns/ipc > ns && "
"sleep 1000";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_ipc_mode(LinuxInfo::PRIVATE);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait until the `ns` file is created in the sandbox.
Duration waited = Duration::zero();
do {
if (os::exists(path::join(directory, "ns"))) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch a nested container with `PRIVATE` IPC mode, check its /dev/shm
// size is correctly set to the default value and the file created by the
// top-level container does not exist in its /dev/shm, touch a file in its
// /dev/shm and write its IPC namespace inode to a file in its sandbox.
ContainerID nestedContainerId1;
nestedContainerId1.mutable_parent()->CopyFrom(containerId);
nestedContainerId1.set_value(id::UUID::random().toString());
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_linux_info()->set_ipc_mode(LinuxInfo::PRIVATE);
launch = containerizer.get()->launch(
nestedContainerId1,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 64 &&"
"test ! -e /dev/shm/root &&"
"touch /dev/shm/nested1 &&"
"stat -Lc %i /proc/self/ns/ipc > ns && "
"sleep 1000"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait until the `ns` file is created in the sandbox.
waited = Duration::zero();
const string nestedSandboxPath1 =
getSandboxPath(directory, nestedContainerId1);
do {
if (os::exists(path::join(nestedSandboxPath1, "ns"))) {
break;
}
os::sleep(Seconds(1));
waited += Seconds(1);
} while (waited < process::TEST_AWAIT_TIMEOUT);
EXPECT_LT(waited, process::TEST_AWAIT_TIMEOUT);
// Launch another nested container with private IPC mode and 128MB
// /dev/shm, check its /dev/shm size is correctly set to 128MB and
// the files created by the top-level container and the first nested
// container do not exist in its /dev/shm, write its IPC namespace
// inode to a file in its sandbox.
ContainerID nestedContainerId2;
nestedContainerId2.mutable_parent()->CopyFrom(containerId);
nestedContainerId2.set_value(id::UUID::random().toString());
containerInfo.mutable_linux_info()->set_shm_size(128);
launch = containerizer.get()->launch(
nestedContainerId2,
createContainerConfig(
createCommandInfo(
"df -m /dev/shm | grep -w 128 &&"
"test ! -e /dev/shm/root &&"
"test ! -e /dev/shm/nested1 &&"
"stat -Lc %i /proc/self/ns/ipc > ns"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer.get()->wait(
nestedContainerId2);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
// Check top-level container and the two nested containers
// have their own IPC namespaces.
Try<uint64_t> rootIpcNamespace = readValue(path::join(directory, "ns"));
ASSERT_SOME(rootIpcNamespace);
Try<uint64_t> nestedIpcNamespace1 =
readValue(path::join(nestedSandboxPath1, "ns"));
ASSERT_SOME(nestedIpcNamespace1);
Try<uint64_t> nestedIpcNamespace2 =
readValue(path::join(getSandboxPath(directory, nestedContainerId2), "ns"));
ASSERT_SOME(nestedIpcNamespace2);
EXPECT_NE(rootIpcNamespace.get(), nestedIpcNamespace1.get());
EXPECT_NE(rootIpcNamespace.get(), nestedIpcNamespace2.get());
EXPECT_NE(nestedIpcNamespace1.get(), nestedIpcNamespace2.get());
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
}
// This test verifies that top-level container and nested
// containers can share agent's IPC namespace and /dev/shm.
TEST_F(NamespacesIsolatorTest, ROOT_INTERNET_ShareAgentIPCNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc");
ASSERT_SOME(containerizer);
// Launch a top-level container with `SHARE_PARENT` IPC mode,
// write its IPC namespace inode to a file under /dev/shm.
const string command =
"stat -Lc %i /proc/self/ns/ipc > /dev/shm/root && "
"sleep 1000";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_ipc_mode(LinuxInfo::SHARE_PARENT);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Launch a nested container with `SHARE_PARENT` IPC mode,
// write its IPC namespace inode to a file under /dev/shm.
ContainerID nestedContainerId1;
nestedContainerId1.mutable_parent()->CopyFrom(containerId);
nestedContainerId1.set_value(id::UUID::random().toString());
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_linux_info()->set_ipc_mode(LinuxInfo::SHARE_PARENT);
launch = containerizer.get()->launch(
nestedContainerId1,
createContainerConfig(
createCommandInfo("stat -Lc %i /proc/self/ns/ipc > /dev/shm/nest1"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer.get()->wait(
nestedContainerId1);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
// Launch another nested container with `SHARE_PARENT` IPC mode and its
// own rootfs, write its IPC namespace inode to a file under /dev/shm.
ContainerID nestedContainerId2;
nestedContainerId2.mutable_parent()->CopyFrom(containerId);
nestedContainerId2.set_value(id::UUID::random().toString());
mesos::Image image;
image.set_type(mesos::Image::DOCKER);
image.mutable_docker()->set_name("alpine");
containerInfo.mutable_mesos()->mutable_image()->CopyFrom(image);
launch = containerizer.get()->launch(
nestedContainerId2,
createContainerConfig(
createCommandInfo("stat -Lc %i /proc/self/ns/ipc > /dev/shm/nest2"),
containerInfo),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
wait = containerizer.get()->wait(nestedContainerId2);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
// Check top-level container and the two nested containers
// share agent's IPC namespace and /dev/shm.
Try<uint64_t> rootIpcNamespace = readValue("/dev/shm/root");
ASSERT_SOME(rootIpcNamespace);
Try<uint64_t> nestedIpcNamespace1 = readValue("/dev/shm/nest1");
ASSERT_SOME(nestedIpcNamespace1);
Try<uint64_t> nestedIpcNamespace2 = readValue("/dev/shm/nest2");
ASSERT_SOME(nestedIpcNamespace2);
Result<ino_t> agentIpcNamespace = ns::getns(::getpid(), "ipc");
ASSERT_SOME(agentIpcNamespace);
EXPECT_EQ(rootIpcNamespace.get(), agentIpcNamespace.get());
EXPECT_EQ(nestedIpcNamespace1.get(), agentIpcNamespace.get());
EXPECT_EQ(nestedIpcNamespace2.get(), agentIpcNamespace.get());
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
ASSERT_SOME(os::rm("/dev/shm/root"));
ASSERT_SOME(os::rm("/dev/shm/nest1"));
ASSERT_SOME(os::rm("/dev/shm/nest2"));
}
// This test verifies that top-level container with `SHARE_PARENT` IPC mode
// will fail to launch when `--disallow_sharing_agent_ipc_namespace = true`.
TEST_F(NamespacesIsolatorTest, ROOT_DisallowShareAgentIPCNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc", None(), true);
ASSERT_SOME(containerizer);
// Launch a top-level container with `SHARE_PARENT` IPC mode.
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", "sleep 1000"),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_ipc_mode(LinuxInfo::SHARE_PARENT);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
}
// This test verifies that we do not support specifying container's
// /dev/shm size when its IPC mode is not `PRIVATE`.
TEST_F(NamespacesIsolatorTest, ROOT_NonePrivateIPCModeWithShmSize)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc");
ASSERT_SOME(containerizer);
// Launch a container with a specified /dev/shm size but without
// specifying IPC mode.
ContainerID containerId1;
containerId1.set_value(id::UUID::random().toString());
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", "sleep 1000"),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_shm_size(128);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId1,
containerConfig,
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
// Launch another container with a specified /dev/shm size and `SHARE_PARENT`
// IPC mode.
ContainerID containerId2;
containerId2.set_value(id::UUID::random().toString());
container->mutable_linux_info()->set_ipc_mode(LinuxInfo::SHARE_PARENT);
launch = containerizer.get()->launch(
containerId2,
containerConfig,
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
}
// This test verifies that we do not support launching debug container
// with private IPC mode.
TEST_F(NamespacesIsolatorTest, ROOT_DebugContainerWithPrivateIPCMode)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/ipc");
ASSERT_SOME(containerizer);
// Launch a top-level container.
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", "sleep 1000"),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Launch a debug container with private IPC mode under the
// top-level container.
ContainerID debugContainerId;
debugContainerId.mutable_parent()->CopyFrom(containerId);
debugContainerId.set_value(id::UUID::random().toString());
ContainerInfo containerInfo;
containerInfo.set_type(ContainerInfo::MESOS);
containerInfo.mutable_linux_info()->set_ipc_mode(LinuxInfo::PRIVATE);
launch = containerizer.get()->launch(
debugContainerId,
createContainerConfig(
createCommandInfo("sleep 1000"),
containerInfo,
ContainerClass::DEBUG),
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
Future<Option<ContainerTermination>> termination =
containerizer.get()->destroy(containerId);
AWAIT_READY(termination);
ASSERT_SOME(termination.get());
ASSERT_TRUE(termination.get()->has_status());
EXPECT_WTERMSIG_EQ(SIGKILL, termination.get()->status());
}
#endif // __linux__
} // namespace tests {
} // namespace internal {
} // namespace mesos {