blob: 8bc2e1de8c1d39a945ebb9f730cedab771b89070 [file]
// 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 <vector>
#include <mesos/mesos.hpp>
#include <mesos/slave/container_logger.hpp>
#include <process/owned.hpp>
#include <process/gtest.hpp>
#include <process/metrics/metrics.hpp>
#include <stout/error.hpp>
#include <stout/foreach.hpp>
#include <stout/gtest.hpp>
#include <stout/hashmap.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/uuid.hpp>
#ifdef __linux__
#include "linux/fs.hpp"
#endif
#include "slave/paths.hpp"
#ifdef __linux__
#include "slave/containerizer/mesos/linux_launcher.hpp"
#include "slave/containerizer/mesos/isolators/filesystem/linux.hpp"
#endif
#include "slave/containerizer/mesos/containerizer.hpp"
#include "slave/containerizer/mesos/provisioner/backend.hpp"
#include "slave/containerizer/mesos/provisioner/paths.hpp"
#include "slave/containerizer/mesos/provisioner/backends/copy.hpp"
#include "tests/flags.hpp"
#include "tests/mesos.hpp"
#include "tests/containerizer/rootfs.hpp"
#include "tests/containerizer/store.hpp"
using namespace process;
using std::string;
using std::vector;
using mesos::internal::master::Master;
using mesos::internal::slave::Backend;
using mesos::internal::slave::Fetcher;
using mesos::internal::slave::Launcher;
#ifdef __linux__
using mesos::internal::slave::LinuxFilesystemIsolatorProcess;
using mesos::internal::slave::LinuxLauncher;
#endif
using mesos::internal::slave::MesosContainerizer;
using mesos::internal::slave::Provisioner;
using mesos::internal::slave::ProvisionerProcess;
using mesos::internal::slave::Slave;
using mesos::internal::slave::Store;
using mesos::slave::ContainerLogger;
using mesos::slave::Isolator;
namespace mesos {
namespace internal {
namespace tests {
#ifdef __linux__
class LinuxFilesystemIsolatorTest : public MesosTest
{
protected:
virtual void TearDown()
{
// Try to remove any mounts under sandbox.
if (::geteuid() == 0) {
Try<string> umount = os::shell(
"grep '%s' /proc/mounts | "
"cut -d' ' -f2 | "
"xargs --no-run-if-empty umount -l",
sandbox.get().c_str());
if (umount.isError()) {
LOG(ERROR) << "Failed to umount for sandbox '" << sandbox.get()
<< "': " << umount.error();
}
}
MesosTest::TearDown();
}
// This helper creates a MesosContainerizer instance that uses the
// LinuxFilesystemIsolator. The filesystem isolator takes a
// TestAppcProvisioner which provisions APPC images by copying files
// from the host filesystem.
// 'images' is a map of imageName -> rootfsPath.
// TODO(xujyan): The current assumption of one rootfs per image name
// is inconsistent with the real provisioner and we should fix it.
Try<Owned<MesosContainerizer>> createContainerizer(
const slave::Flags& flags,
const hashmap<string, string>& images)
{
// Create the root filesystems.
hashmap<string, Shared<Rootfs>> rootfses;
foreachpair (const string& imageName, const string& rootfsPath, images) {
Try<Owned<Rootfs>> rootfs = LinuxRootfs::create(rootfsPath);
if (rootfs.isError()) {
return Error("Failed to create LinuxRootfs: " + rootfs.error());
}
rootfses.put(imageName, rootfs.get().share());
}
Owned<Store> store(new TestStore(rootfses));
hashmap<Image::Type, Owned<Store>> stores;
stores[Image::APPC] = store;
hashmap<string, Owned<Backend>> backends = Backend::create(flags);
const string rootDir = slave::paths::getProvisionerDir(flags.work_dir);
if (!os::exists(rootDir)) {
Try<Nothing> mkdir = os::mkdir(rootDir);
if (mkdir.isError()) {
return Error("Failed to create root dir: " + mkdir.error());
}
}
Owned<ProvisionerProcess> provisionerProcess(new ProvisionerProcess(
flags,
rootDir,
stores,
backends));
Owned<Provisioner> provisioner(new Provisioner(provisionerProcess));
Try<Isolator*> isolator = LinuxFilesystemIsolatorProcess::create(flags);
if (isolator.isError()) {
return Error(
"Failed to create LinuxFilesystemIsolatorProcess: " +
isolator.error());
}
Try<Launcher*> launcher = LinuxLauncher::create(flags);
if (launcher.isError()) {
return Error("Failed to create LinuxLauncher: " + launcher.error());
}
// Create and initialize a new container logger.
Try<ContainerLogger*> logger =
ContainerLogger::create(flags.container_logger);
if (logger.isError()) {
return Error("Failed to create container logger: " + logger.error());
}
return Owned<MesosContainerizer>(
new MesosContainerizer(
flags,
true,
&fetcher,
Owned<ContainerLogger>(logger.get()),
Owned<Launcher>(launcher.get()),
provisioner,
{Owned<Isolator>(isolator.get())}));
}
ContainerInfo createContainerInfo(
const Option<string>& imageName = None(),
const vector<Volume>& volumes = vector<Volume>())
{
ContainerInfo info;
info.set_type(ContainerInfo::MESOS);
if (imageName.isSome()) {
Image* image = info.mutable_mesos()->mutable_image();
image->set_type(Image::APPC);
Image::Appc* appc = image->mutable_appc();
appc->set_name(imageName.get());
}
foreach (const Volume& volume, volumes) {
info.add_volumes()->CopyFrom(volume);
}
return info;
}
Volume createVolumeFromHostPath(
const string& containerPath,
const string& hostPath,
const Volume::Mode& mode)
{
return CREATE_VOLUME(containerPath, hostPath, mode);
}
Volume createVolumeFromImage(
const string& containerPath,
const string& imageName,
const Volume::Mode& mode)
{
Volume volume;
volume.set_container_path(containerPath);
volume.set_mode(mode);
Image* image = volume.mutable_image();
image->set_type(Image::APPC);
Image::Appc* appc = image->mutable_appc();
appc->set_name(imageName);
return volume;
}
private:
Fetcher fetcher;
};
// This test verifies that the root filesystem of the container is
// properly changed to the one that's provisioned by the provisioner.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_ChangeRootFilesystem)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"[ ! -d '" + os::getcwd() + "' ]");
executor.mutable_container()->CopyFrom(createContainerInfo("test_image"));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies that the root filesystem of the container is
// properly changed to the one that's provisioned by the provisioner.
// Also runs the command executor with the new root filesystem.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_ChangeRootFilesystemCommandExecutor)
{
Try<PID<Master>> master = StartMaster();
ASSERT_SOME(master);
slave::Flags flags = CreateSlaveFlags();
flags.image_provisioner_backend = "copy";
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
Try<PID<Slave>> slave = StartSlave(containerizer.get().get(), flags);
ASSERT_SOME(slave);
MockScheduler sched;
MesosSchedulerDriver driver(
&sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
Future<vector<Offer>> offers;
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers))
.WillRepeatedly(Return()); // Ignore subsequent offers.
driver.start();
AWAIT_READY(frameworkId);
AWAIT_READY(offers);
ASSERT_NE(0u, offers.get().size());
const Offer& offer = offers.get()[0];
SlaveID slaveId = offer.slave_id();
TaskInfo task = createTask(
offer.slave_id(),
offer.resources(),
"test -d " + flags.sandbox_directory);
ContainerInfo containerInfo;
Image* image = containerInfo.mutable_mesos()->mutable_image();
image->set_type(Image::APPC);
image->mutable_appc()->set_name("test_image");
containerInfo.set_type(ContainerInfo::MESOS);
task.mutable_container()->CopyFrom(containerInfo);
driver.launchTasks(offers.get()[0].id(), {task});
Future<TaskStatus> statusRunning;
Future<TaskStatus> statusFinished;
EXPECT_CALL(sched, statusUpdate(&driver, _))
.WillOnce(FutureArg<1>(&statusRunning))
.WillOnce(FutureArg<1>(&statusFinished));
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(statusRunning, Seconds(60));
EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
AWAIT_READY(statusFinished);
EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
driver.stop();
driver.join();
Shutdown();
}
TEST_F(LinuxFilesystemIsolatorTest, ROOT_Metrics)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
// Use a long running task so we can reliably capture the moment it's alive.
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"sleep 1000");
executor.mutable_container()->CopyFrom(createContainerInfo("test_image"));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Check metrics.
JSON::Object stats = Metrics();
EXPECT_EQ(1u, stats.values.count(
"containerizer/mesos/filesystem/containers_new_rootfs"));
EXPECT_EQ(
1, stats.values["containerizer/mesos/filesystem/containers_new_rootfs"]);
containerizer.get()->destroy(containerId);
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Executor was killed.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(9, wait.get().status());
}
// This test verifies that a volume with a relative host path is
// properly created in the container's sandbox and is properly mounted
// in the container's mount namespace.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_VolumeFromSandbox)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"echo abc > /tmp/file");
executor.mutable_container()->CopyFrom(createContainerInfo(
"test_image",
{createVolumeFromHostPath("/tmp", "tmp", Volume::RW)}));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
EXPECT_SOME_EQ("abc\n", os::read(path::join(directory, "tmp", "file")));
}
// This test verifies that a volume with an absolute host path as
// well as an absolute container path is properly mounted in the
// container's mount namespace.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_VolumeFromHost)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"test -d /tmp/sandbox");
executor.mutable_container()->CopyFrom(createContainerInfo(
"test_image",
{createVolumeFromHostPath("/tmp", os::getcwd(), Volume::RW)}));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies that a volume with an absolute host path and a
// relative container path is properly mounted in the container's
// mount namespace. The mount point will be created in the sandbox.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_VolumeFromHostSandboxMountPoint)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"test -d mountpoint/sandbox");
executor.mutable_container()->CopyFrom(createContainerInfo(
"test_image",
{createVolumeFromHostPath("mountpoint", os::getcwd(), Volume::RW)}));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies that persistent volumes are properly mounted in
// the container's root filesystem.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_PersistentVolumeWithRootFilesystem)
{
slave::Flags flags = CreateSlaveFlags();
// Need this otherwise the persistent volumes are not created
// within the slave work_dir and thus not retrievable.
flags.work_dir = os::getcwd();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"echo abc > volume/file");
executor.add_resources()->CopyFrom(createPersistentVolume(
Megabytes(32),
"test_role",
"persistent_volume_id",
"volume"));
executor.mutable_container()->CopyFrom(createContainerInfo("test_image"));
// Create a persistent volume.
string volume = slave::paths::getPersistentVolumePath(
os::getcwd(),
"test_role",
"persistent_volume_id");
ASSERT_SOME(os::mkdir(volume));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
EXPECT_SOME_EQ("abc\n", os::read(path::join(volume, "file")));
}
TEST_F(LinuxFilesystemIsolatorTest, ROOT_PersistentVolumeWithoutRootFilesystem)
{
slave::Flags flags = CreateSlaveFlags();
// Need this otherwise the persistent volumes are not created
// within the slave work_dir and thus not retrievable.
flags.work_dir = os::getcwd();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"echo abc > volume/file");
executor.add_resources()->CopyFrom(createPersistentVolume(
Megabytes(32),
"test_role",
"persistent_volume_id",
"volume"));
executor.mutable_container()->CopyFrom(createContainerInfo());
// Create a persistent volume.
string volume = slave::paths::getPersistentVolumePath(
os::getcwd(),
"test_role",
"persistent_volume_id");
ASSERT_SOME(os::mkdir(volume));
// To make sure the sandbox directory has the container ID in its
// path so it doesn't get unmounted by the launcher.
string directory = slave::paths::createExecutorDirectory(
flags.work_dir,
SlaveID(),
FrameworkID(),
executor.executor_id(),
containerId);
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
EXPECT_SOME_EQ("abc\n", os::read(path::join(volume, "file")));
}
// This test verifies that the image specified in the volume will be
// properly provisioned and mounted into the container if container
// root filesystem is not specified.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_ImageInVolumeWithoutRootFilesystem)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"test -d rootfs/bin");
executor.mutable_container()->CopyFrom(createContainerInfo(
None(),
{createVolumeFromImage("rootfs", "test_image", Volume::RW)}));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copying.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies that the image specified in the volume will be
// properly provisioned and mounted into the container if container
// root filesystem is specified.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_ImageInVolumeWithRootFilesystem)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer(
flags,
{{"test_image_rootfs", path::join(os::getcwd(), "test_image_rootfs")},
{"test_image_volume", path::join(os::getcwd(), "test_image_volume")}});
ASSERT_SOME(containerizer);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
"[ ! -d '" + os::getcwd() + "' ] && [ -d rootfs/bin ]");
executor.mutable_container()->CopyFrom(createContainerInfo(
"test_image_rootfs",
{createVolumeFromImage("rootfs", "test_image_volume", Volume::RW)}));
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copy.
AWAIT_READY_FOR(launch, Seconds(120));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
// Because destroy rootfs spents a lot of time, we use 30s as timeout here.
AWAIT_READY_FOR(wait, Seconds(30));
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies that multiple containers with images can be
// launched simultaneously with no interference.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_MultipleContainers)
{
slave::Flags flags = CreateSlaveFlags();
// Need this otherwise the persistent volumes are not created
// within the slave work_dir and thus not retrievable.
flags.work_dir = os::getcwd();
ContainerID containerId1;
containerId1.set_value(UUID::random().toString());
ContainerID containerId2;
containerId2.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer(
flags,
{{"test_image1", path::join(os::getcwd(), "test_image1")},
{"test_image2", path::join(os::getcwd(), "test_image2")}});
ASSERT_SOME(containerizer);
SlaveID slaveId;
slaveId.set_value("test_slave");
// First launch container 1 which has a long running task which
// guarantees that its work directory mount is in the host mount
// table when container 2 is launched.
ExecutorInfo executor1 = CREATE_EXECUTOR_INFO(
"test_executor1",
"sleep 1000"); // Long running task.
executor1.mutable_container()->CopyFrom(createContainerInfo("test_image1"));
// Create a persistent volume for container 1. We do this because
// we want to test container 2 cleaning up multiple mounts.
executor1.add_resources()->CopyFrom(createPersistentVolume(
Megabytes(32),
"test_role",
"persistent_volume_id",
"volume"));
string volume = slave::paths::getPersistentVolumePath(
os::getcwd(),
"test_role",
"persistent_volume_id");
ASSERT_SOME(os::mkdir(volume));
string directory1 = slave::paths::createExecutorDirectory(
flags.work_dir,
slaveId,
DEFAULT_FRAMEWORK_INFO.id(),
executor1.executor_id(),
containerId1);
Future<bool> launch1 = containerizer.get()->launch(
containerId1,
executor1,
directory1,
None(),
slaveId,
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copy.
AWAIT_READY_FOR(launch1, Seconds(60));
// Now launch container 2 which will copy the host mount table with
// container 1's work directory mount in it.
ExecutorInfo executor2 = CREATE_EXECUTOR_INFO(
"test_executor2",
"[ ! -d '" + os::getcwd() + "' ]");
executor2.mutable_container()->CopyFrom(createContainerInfo("test_image2"));
string directory2 = slave::paths::createExecutorDirectory(
flags.work_dir,
slaveId,
DEFAULT_FRAMEWORK_INFO.id(),
executor2.executor_id(),
containerId2);
Future<bool> launch2 = containerizer.get()->launch(
containerId2,
executor2,
directory2,
None(),
slaveId,
PID<Slave>(),
false);
// Need to wait for Rootfs copy.
AWAIT_READY_FOR(launch1, Seconds(60));
containerizer.get()->destroy(containerId1);
// Wait on the containers.
Future<containerizer::Termination> wait1 =
containerizer.get()->wait(containerId1);
Future<containerizer::Termination> wait2 =
containerizer.get()->wait(containerId2);
AWAIT_READY(wait1);
AWAIT_READY(wait2);
// Executor 1 was forcefully killed.
EXPECT_TRUE(wait1.get().has_status());
EXPECT_EQ(9, wait1.get().status());
// Executor 2 exited normally.
EXPECT_TRUE(wait2.get().has_status());
EXPECT_EQ(0, wait2.get().status());
}
// This test verifies that the environment variables for sandbox
// (i.e., MESOS_DIRECTORY and MESOS_SANDBOX) are set properly.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_SandboxEnvironmentVariable)
{
slave::Flags flags = CreateSlaveFlags();
ContainerID containerId;
containerId.set_value(UUID::random().toString());
Try<Owned<MesosContainerizer>> containerizer = createContainerizer(
flags,
{{"test_image", path::join(os::getcwd(), "test_image")}});
ASSERT_SOME(containerizer);
string directory = path::join(os::getcwd(), "sandbox");
ASSERT_SOME(os::mkdir(directory));
Try<string> script = strings::format(
"if [ \"$MESOS_DIRECTORY\" != \"%s\" ]; then exit 1; fi &&"
"if [ \"$MESOS_SANDBOX\" != \"%s\" ]; then exit 1; fi",
directory,
flags.sandbox_directory);
ASSERT_SOME(script);
ExecutorInfo executor = CREATE_EXECUTOR_INFO(
"test_executor",
script.get());
executor.mutable_container()->CopyFrom(createContainerInfo("test_image"));
Future<bool> launch = containerizer.get()->launch(
containerId,
executor,
directory,
None(),
SlaveID(),
PID<Slave>(),
false);
// Wait for the launch to complete.
// Need to wait for Rootfs copy.
AWAIT_READY_FOR(launch, Seconds(60));
// Wait on the container.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
}
// This test verifies the slave's work directory mount preparation if
// the mount does not exist initially.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_WorkDirMount)
{
slave::Flags flags = CreateSlaveFlags();
Try<Isolator*> isolator = LinuxFilesystemIsolatorProcess::create(flags);
ASSERT_SOME(isolator);
Try<fs::MountInfoTable> table = fs::MountInfoTable::read();
ASSERT_SOME(table);
bool mountFound = false;
foreach (const fs::MountInfoTable::Entry& entry, table.get().entries) {
if (entry.target == flags.work_dir) {
EXPECT_SOME(entry.shared());
mountFound = true;
}
}
EXPECT_TRUE(mountFound);
delete isolator.get();
}
// This test verifies the slave's work directory mount preparation if
// the mount already exists (e.g., to simulate the case when the slave
// crashes while preparing the work directory mount).
TEST_F(LinuxFilesystemIsolatorTest, ROOT_WorkDirMountPreExists)
{
slave::Flags flags = CreateSlaveFlags();
// Simulate the situation in which the slave crashes while preparing
// the work directory mount.
ASSERT_SOME(os::shell(
"mount --bind %s %s",
flags.work_dir.c_str(),
flags.work_dir.c_str()));
Try<Isolator*> isolator = LinuxFilesystemIsolatorProcess::create(flags);
ASSERT_SOME(isolator);
Try<fs::MountInfoTable> table = fs::MountInfoTable::read();
ASSERT_SOME(table);
bool mountFound = false;
foreach (const fs::MountInfoTable::Entry& entry, table.get().entries) {
if (entry.target == flags.work_dir) {
EXPECT_SOME(entry.shared());
mountFound = true;
}
}
EXPECT_TRUE(mountFound);
delete isolator.get();
}
// This test verifies that the volume usage accounting for sandboxes with
// bind-mounted volumes works correctly by creating a file within the volume
// the size of which exceeds the sandbox quota.
TEST_F(LinuxFilesystemIsolatorTest, ROOT_VolumeUsageExceedsSandboxQuota)
{
FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
frameworkInfo.set_role("role1");
Try<PID<Master>> master = StartMaster();
ASSERT_SOME(master);
slave::Flags flags = CreateSlaveFlags();
flags.isolation = "posix/disk,filesystem/linux";
// NOTE: We can't pause the clock because we need the reaper to reap
// the 'du' subprocess.
flags.container_disk_watch_interval = Milliseconds(1);
flags.enforce_container_disk_quota = true;
flags.resources = "cpus:2;mem:128;disk(role1):128";
Try<PID<Slave>> slave = StartSlave(flags);
ASSERT_SOME(slave);
MockScheduler sched;
MesosSchedulerDriver driver(
&sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
Future<vector<Offer>> offers;
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers))
.WillRepeatedly(Return()); // Ignore subsequent offers.
driver.start();
AWAIT_READY(frameworkId);
AWAIT_READY(offers);
ASSERT_NE(0u, offers.get().size());
// We request a sandbox (1MB) that is smaller than the persistent
// volume (4MB) and attempt to create a file in that volume that is
// twice the size of the sanbox (2MB).
Resources volume = createPersistentVolume(
Megabytes(4),
"role1",
"id1",
"volume_path");
Resources taskResources =
Resources::parse("cpus:1;mem:64;disk(role1):1").get() + volume;
// We sleep to give quota enforcement (du) a chance to kick in.
TaskInfo task = createTask(
offers.get()[0].slave_id(),
taskResources,
"dd if=/dev/zero of=volume_path/file bs=1048576 count=2 && sleep 1");
Future<TaskStatus> statusRunning;
Future<TaskStatus> statusFinished;
EXPECT_CALL(sched, statusUpdate(&driver, _))
.WillOnce(FutureArg<1>(&statusRunning))
.WillOnce(FutureArg<1>(&statusFinished));
driver.acceptOffers(
{offers.get()[0].id()},
{CREATE(volume),
LAUNCH({task})});
AWAIT_READY(statusRunning);
EXPECT_EQ(task.task_id(), statusRunning.get().task_id());
EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
AWAIT_READY(statusFinished);
EXPECT_EQ(task.task_id(), statusFinished.get().task_id());
EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
driver.stop();
driver.join();
Shutdown();
}
#endif // __linux__
} // namespace tests {
} // namespace internal {
} // namespace mesos {