http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/docker_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/docker_tests.cpp b/src/tests/docker_tests.cpp deleted file mode 100644 index a4a2725..0000000 --- a/src/tests/docker_tests.cpp +++ /dev/null @@ -1,421 +0,0 @@ -/** - * 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<Docker*> docker = Docker::create(tests::flags.docker, 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)); - } - - delete docker.get(); - } -}; - -// 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, 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("busybox"); - 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); - } -} - - -TEST_F(DockerTest, ROOT_DOCKER_CheckCommandWithShell) -{ - Owned<Docker> docker(Docker::create(tests::flags.docker, false).get()); - - ContainerInfo containerInfo; - containerInfo.set_type(ContainerInfo::DOCKER); - - ContainerInfo::DockerInfo dockerInfo; - dockerInfo.set_image("busybox"); - 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, 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("busybox"); - 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, 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, 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("busybox"); - - 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 a absolute path when running a -// docker container works. -TEST_F(DockerTest, ROOT_DOCKER_MountAbsolute) -{ - Owned<Docker> docker(Docker::create(tests::flags.docker, 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("busybox"); - - 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); -} - - -} // namespace tests { -} // namespace internal { -} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/external_containerizer_test.cpp ---------------------------------------------------------------------- diff --git a/src/tests/external_containerizer_test.cpp b/src/tests/external_containerizer_test.cpp deleted file mode 100644 index 17bfb72..0000000 --- a/src/tests/external_containerizer_test.cpp +++ /dev/null @@ -1,266 +0,0 @@ -/** - * 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 <gmock/gmock.h> - -#include <string> -#include <vector> -#include <map> - -#include <mesos/resources.hpp> - -#include <process/future.hpp> - -#include <stout/os.hpp> -#include <stout/path.hpp> - -#include "master/master.hpp" -#include "master/detector.hpp" - -#include "slave/containerizer/containerizer.hpp" -#include "slave/containerizer/external_containerizer.hpp" -#include "slave/flags.hpp" -#include "slave/slave.hpp" - -#include "tests/mesos.hpp" -#include "tests/flags.hpp" - -using namespace process; - -using mesos::internal::master::Master; -using mesos::internal::slave::Containerizer; -using mesos::internal::slave::Slave; - -using std::string; -using std::vector; - -using testing::_; -using testing::DoAll; -using testing::Return; -using testing::SaveArg; -using testing::Invoke; - -namespace mesos { -namespace internal { -namespace tests { - -// The external containerizer tests currently rely on a Python script -// which needs the Mesos Python egg being built. -// TODO(tillt): Consider providing tests that do not rely on Python. -#ifdef MESOS_HAS_PYTHON - -// TODO(tillt): Update and enhance the ExternalContainerizer tests, -// possibly following some of the patterns used within the -// IsolatorTests or even entirely reusing the Containerizer tests. -class ExternalContainerizerTest : public MesosTest {}; - - -class MockExternalContainerizer : public slave::ExternalContainerizer -{ -public: - MOCK_METHOD8( - launch, - process::Future<bool>( - const ContainerID&, - const TaskInfo&, - const ExecutorInfo&, - const std::string&, - const Option<std::string>&, - const SlaveID&, - const process::PID<slave::Slave>&, - bool checkpoint)); - - MockExternalContainerizer(const slave::Flags& flags) - : ExternalContainerizer(flags) - { - // Set up defaults for mocked methods. - // NOTE: See TestContainerizer::setup for why we use - // 'EXPECT_CALL' and 'WillRepeatedly' here instead of - // 'ON_CALL' and 'WillByDefault'. - EXPECT_CALL(*this, launch(_, _, _, _, _, _, _, _)) - .WillRepeatedly(Invoke(this, &MockExternalContainerizer::_launch)); - } - - process::Future<bool> _launch( - const ContainerID& containerId, - const TaskInfo& taskInfo, - const ExecutorInfo& executorInfo, - const string& directory, - const Option<string>& user, - const SlaveID& slaveId, - const PID<Slave>& slavePid, - bool checkpoint) - { - return slave::ExternalContainerizer::launch( - containerId, - taskInfo, - executorInfo, - directory, - user, - slaveId, - slavePid, - checkpoint); - } -}; - - -// This test has been temporarily disabled due to MESOS-1257. -TEST_F(ExternalContainerizerTest, DISABLED_Launch) -{ - Try<PID<Master> > master = this->StartMaster(); - ASSERT_SOME(master); - - Flags testFlags; - - slave::Flags flags = this->CreateSlaveFlags(); - - flags.isolation = "external"; - flags.containerizer_path = - testFlags.build_dir + "/src/examples/python/test-containerizer"; - - MockExternalContainerizer containerizer(flags); - - Try<PID<Slave> > slave = this->StartSlave(&containerizer, 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); - - EXPECT_NE(0u, offers.get().size()); - - TaskInfo task; - task.set_name("isolator_test"); - task.mutable_task_id()->set_value("1"); - task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id()); - task.mutable_resources()->CopyFrom(offers.get()[0].resources()); - - Resources resources(offers.get()[0].resources()); - Option<Bytes> mem = resources.mem(); - ASSERT_SOME(mem); - Option<double> cpus = resources.cpus(); - ASSERT_SOME(cpus); - - const std::string& file = path::join(flags.work_dir, "ready"); - - // This task induces user/system load in a child process by - // running top in a child process for ten seconds. - task.mutable_command()->set_value( -#ifdef __APPLE__ - // Use logging mode with 30,000 samples with no interval. - "top -l 30000 -s 0 2>&1 > /dev/null & " -#else - // Batch mode, with 30,000 samples with no interval. - "top -b -d 0 -n 30000 2>&1 > /dev/null & " -#endif - "touch " + file + "; " // Signals that the top command is running. - "sleep 60"); - - Future<TaskStatus> status; - EXPECT_CALL(sched, statusUpdate(&driver, _)) - .WillOnce(FutureArg<1>(&status)) - .WillRepeatedly(Return()); // Ignore rest for now. - - Future<ContainerID> containerId; - EXPECT_CALL(containerizer, launch(_, _, _, _, _, _, _, _)) - .WillOnce(DoAll(FutureArg<0>(&containerId), - Invoke(&containerizer, - &MockExternalContainerizer::_launch))); - - driver.launchTasks(offers.get()[0].id(), {task}); - - AWAIT_READY(containerId); - - AWAIT_READY(status); - - EXPECT_EQ(TASK_RUNNING, status.get().state()); - - // Wait for the task to begin inducing cpu time. - while (!os::exists(file)); - - ExecutorID executorId; - executorId.set_value(task.task_id().value()); - - // We'll wait up to 10 seconds for the child process to induce - // 1/8 of a second of user and system cpu time in total. - // TODO(bmahler): Also induce rss memory consumption, by re-using - // the balloon framework. - ResourceStatistics statistics; - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> usage = containerizer.usage(containerId.get()); - AWAIT_READY(usage); - - statistics = usage.get(); - - // If we meet our usage expectations, we're done! - // NOTE: We are currently getting dummy-data from the test- - // containerizer python script matching these expectations. - // TODO(tillt): Consider working with real data. - if (statistics.cpus_user_time_secs() >= 0.120 && - statistics.cpus_system_time_secs() >= 0.05 && - statistics.mem_rss_bytes() >= 1024u) { - break; - } - - os::sleep(Milliseconds(100)); - waited += Milliseconds(100); - } while (waited < Seconds(10)); - - EXPECT_GE(statistics.cpus_user_time_secs(), 0.120); - EXPECT_GE(statistics.cpus_system_time_secs(), 0.05); - EXPECT_EQ(statistics.cpus_limit(), cpus.get()); - EXPECT_GE(statistics.mem_rss_bytes(), 1024u); - EXPECT_EQ(statistics.mem_limit_bytes(), mem.get().bytes()); - - EXPECT_CALL(sched, statusUpdate(&driver, _)) - .WillOnce(FutureArg<1>(&status)); - - driver.killTask(task.task_id()); - - AWAIT_READY(status); - - EXPECT_EQ(TASK_KILLED, status.get().state()); - - driver.stop(); - driver.join(); - - this->Shutdown(); -} - -#endif // MESOS_HAS_PYTHON - -} // namespace tests { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/fs_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/fs_tests.cpp b/src/tests/fs_tests.cpp deleted file mode 100644 index 34d3c41..0000000 --- a/src/tests/fs_tests.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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 <paths.h> - -#include <gmock/gmock.h> - -#include <stout/foreach.hpp> -#include <stout/gtest.hpp> -#include <stout/none.hpp> -#include <stout/option.hpp> -#include <stout/try.hpp> - -#include "linux/fs.hpp" - -namespace mesos { -namespace internal { -namespace tests { - -using fs::MountTable; -using fs::FileSystemTable; -using fs::MountInfoTable; - - -TEST(FsTest, MountTableRead) -{ - Try<MountTable> table = MountTable::read(_PATH_MOUNTED); - - ASSERT_SOME(table); - - Option<MountTable::Entry> root = None(); - Option<MountTable::Entry> proc = None(); - foreach (const MountTable::Entry& entry, table.get().entries) { - if (entry.dir == "/") { - root = entry; - } else if (entry.dir == "/proc") { - proc = entry; - } - } - - EXPECT_SOME(root); - ASSERT_SOME(proc); - EXPECT_EQ(proc.get().type, "proc"); -} - - -TEST(FsTest, MountTableHasOption) -{ - Try<MountTable> table = MountTable::read(_PATH_MOUNTED); - - ASSERT_SOME(table); - - Option<MountTable::Entry> proc = None(); - foreach (const MountTable::Entry& entry, table.get().entries) { - if (entry.dir == "/proc") { - proc = entry; - } - } - - ASSERT_SOME(proc); - EXPECT_TRUE(proc.get().hasOption(MNTOPT_RW)); -} - - -TEST(FsTest, FileSystemTableRead) -{ - Try<FileSystemTable> table = FileSystemTable::read(); - - ASSERT_SOME(table); - - // NOTE: We do not check for /proc because, it is not always present in - // /etc/fstab. - Option<FileSystemTable::Entry> root = None(); - foreach (const FileSystemTable::Entry& entry, table.get().entries) { - if (entry.file == "/") { - root = entry; - } - } - - EXPECT_SOME(root); -} - - -TEST(FsTest, MountInfoTableParse) -{ - // Parse a private mount (no optional fields). - const std::string privateMount = - "19 1 8:1 / / rw,relatime - ext4 /dev/sda1 rw,seclabel,data=ordered"; - Try<MountInfoTable::Entry> entry = MountInfoTable::Entry::parse(privateMount); - - ASSERT_SOME(entry); - EXPECT_EQ(19, entry.get().id); - EXPECT_EQ(1, entry.get().parent); - EXPECT_EQ(makedev(8, 1), entry.get().devno); - EXPECT_EQ("/", entry.get().root); - EXPECT_EQ("/", entry.get().target); - EXPECT_EQ("rw,relatime", entry.get().vfsOptions); - EXPECT_EQ("rw,seclabel,data=ordered", entry.get().fsOptions); - EXPECT_EQ("", entry.get().optionalFields); - EXPECT_EQ("ext4", entry.get().type); - EXPECT_EQ("/dev/sda1", entry.get().source); - - // Parse a shared mount (includes one optional field). - const std::string sharedMount = - "19 1 8:1 / / rw,relatime shared:2 - ext4 /dev/sda1 rw,seclabel"; - entry = MountInfoTable::Entry::parse(sharedMount); - - ASSERT_SOME(entry); - EXPECT_EQ(19, entry.get().id); - EXPECT_EQ(1, entry.get().parent); - EXPECT_EQ(makedev(8, 1), entry.get().devno); - EXPECT_EQ("/", entry.get().root); - EXPECT_EQ("/", entry.get().target); - EXPECT_EQ("rw,relatime", entry.get().vfsOptions); - EXPECT_EQ("rw,seclabel", entry.get().fsOptions); - EXPECT_EQ("shared:2", entry.get().optionalFields); - EXPECT_EQ("ext4", entry.get().type); - EXPECT_EQ("/dev/sda1", entry.get().source); -} - - -TEST(FsTest, DISABLED_MountInfoTableRead) -{ - // Examine the calling process's mountinfo table. - Try<fs::MountInfoTable> table = fs::MountInfoTable::read(); - ASSERT_SOME(table); - - // Every system should have at least a rootfs mounted. - Option<MountInfoTable::Entry> root = None(); - foreach (const MountInfoTable::Entry& entry, table.get().entries) { - if (entry.target == "/") { - root = entry; - } - } - - EXPECT_SOME(root); - - // Repeat for pid 1. - table = fs::MountInfoTable::read(1); - ASSERT_SOME(table); - - // Every system should have at least a rootfs mounted. - root = None(); - foreach (const MountInfoTable::Entry& entry, table.get().entries) { - if (entry.target == "/") { - root = entry; - } - } - - EXPECT_SOME(root); -} - -} // namespace tests { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/isolator.hpp ---------------------------------------------------------------------- diff --git a/src/tests/isolator.hpp b/src/tests/isolator.hpp deleted file mode 100644 index 8aaf88c..0000000 --- a/src/tests/isolator.hpp +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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. - */ - -#ifndef __TEST_ISOLATOR_HPP__ -#define __TEST_ISOLATOR_HPP__ - -#include <gmock/gmock.h> - -#include "slave/containerizer/isolator.hpp" - -namespace mesos { -namespace internal { -namespace tests { - -class TestIsolatorProcess : public slave::MesosIsolatorProcess -{ -public: - static Try<mesos::slave::Isolator*> create( - const Option<CommandInfo>& commandInfo) - { - process::Owned<MesosIsolatorProcess> process( - new TestIsolatorProcess(commandInfo)); - - return new slave::MesosIsolator(process); - } - - MOCK_METHOD2( - recover, - process::Future<Nothing>( - const std::list<mesos::slave::ExecutorRunState>&, - const hashset<ContainerID>&)); - - virtual process::Future<Option<CommandInfo>> prepare( - const ContainerID& containerId, - const ExecutorInfo& executorInfo, - const std::string& directory, - const Option<std::string>& rootfs, - const Option<std::string>& user) - { - return commandInfo; - } - - MOCK_METHOD2( - isolate, - process::Future<Nothing>(const ContainerID&, pid_t)); - - MOCK_METHOD1( - watch, - process::Future<mesos::slave::ExecutorLimitation>(const ContainerID&)); - - MOCK_METHOD2( - update, - process::Future<Nothing>(const ContainerID&, const Resources&)); - - MOCK_METHOD1( - usage, - process::Future<ResourceStatistics>(const ContainerID&)); - - MOCK_METHOD1( - cleanup, - process::Future<Nothing>(const ContainerID&)); - -private: - TestIsolatorProcess(const Option<CommandInfo>& _commandInfo) - : commandInfo(_commandInfo) - { - EXPECT_CALL(*this, watch(testing::_)) - .WillRepeatedly(testing::Return(promise.future())); - - EXPECT_CALL(*this, isolate(testing::_, testing::_)) - .WillRepeatedly(testing::Return(Nothing())); - - EXPECT_CALL(*this, cleanup(testing::_)) - .WillRepeatedly(testing::Return(Nothing())); - } - - const Option<CommandInfo> commandInfo; - - process::Promise<mesos::slave::ExecutorLimitation> promise; -}; - -} // namespace tests { -} // namespace internal { -} // namespace mesos { - -#endif // __TEST_ISOLATOR_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/isolator_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/isolator_tests.cpp b/src/tests/isolator_tests.cpp deleted file mode 100644 index 7ad0cb6..0000000 --- a/src/tests/isolator_tests.cpp +++ /dev/null @@ -1,1316 +0,0 @@ -/** - * 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 <gmock/gmock.h> - -#include <iostream> -#include <string> -#include <vector> - -#include <mesos/resources.hpp> - -#include <mesos/module/isolator.hpp> - -#include <mesos/slave/isolator.hpp> - -#include <process/future.hpp> -#include <process/owned.hpp> -#include <process/reap.hpp> - -#include <stout/abort.hpp> -#include <stout/gtest.hpp> -#include <stout/os.hpp> -#include <stout/path.hpp> - -#ifdef __linux__ -#include "linux/ns.hpp" -#endif // __linux__ - -#include "master/master.hpp" -#include "master/detector.hpp" - -#include "slave/flags.hpp" -#include "slave/slave.hpp" - -#ifdef __linux__ -#include "slave/containerizer/isolators/cgroups/constants.hpp" -#include "slave/containerizer/isolators/cgroups/cpushare.hpp" -#include "slave/containerizer/isolators/cgroups/mem.hpp" -#include "slave/containerizer/isolators/cgroups/perf_event.hpp" -#include "slave/containerizer/isolators/filesystem/shared.hpp" -#endif // __linux__ -#include "slave/containerizer/isolators/posix.hpp" - -#include "slave/containerizer/launcher.hpp" -#ifdef __linux__ -#include "slave/containerizer/fetcher.hpp" -#include "slave/containerizer/linux_launcher.hpp" - -#include "slave/containerizer/mesos/containerizer.hpp" -#include "slave/containerizer/mesos/launch.hpp" -#endif // __linux__ - -#include "tests/flags.hpp" -#include "tests/memory_test_helper.hpp" -#include "tests/mesos.hpp" -#include "tests/module.hpp" -#include "tests/utils.hpp" - -using namespace process; - -using mesos::internal::master::Master; -#ifdef __linux__ -using mesos::internal::slave::CgroupsCpushareIsolatorProcess; -using mesos::internal::slave::CgroupsMemIsolatorProcess; -using mesos::internal::slave::CgroupsPerfEventIsolatorProcess; -using mesos::internal::slave::CPU_SHARES_PER_CPU_REVOCABLE; -using mesos::internal::slave::Fetcher; -using mesos::internal::slave::LinuxLauncher; -using mesos::internal::slave::SharedFilesystemIsolatorProcess; -#endif // __linux__ -using mesos::internal::slave::Launcher; -using mesos::internal::slave::MesosContainerizer; -using mesos::internal::slave::PosixLauncher; -using mesos::internal::slave::PosixCpuIsolatorProcess; -using mesos::internal::slave::PosixMemIsolatorProcess; -using mesos::internal::slave::Slave; - -using mesos::slave::Isolator; -using mesos::slave::IsolatorProcess; - -using std::ostringstream; -using std::set; -using std::string; -using std::vector; - -using testing::_; -using testing::DoAll; -using testing::Return; -using testing::SaveArg; - -namespace mesos { -namespace internal { -namespace tests { - -static int childSetup(int pipes[2]) -{ - // In child process. - while (::close(pipes[1]) == -1 && errno == EINTR); - - // Wait until the parent signals us to continue. - char dummy; - ssize_t length; - while ((length = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 && - errno == EINTR); - - if (length != sizeof(dummy)) { - ABORT("Failed to synchronize with parent"); - } - - while (::close(pipes[0]) == -1 && errno == EINTR); - - return 0; -} - - -template <typename T> -class CpuIsolatorTest : public MesosTest {}; - - -typedef ::testing::Types< - PosixCpuIsolatorProcess, -#ifdef __linux__ - CgroupsCpushareIsolatorProcess, -#endif // __linux__ - tests::Module<Isolator, TestCpuIsolator>> CpuIsolatorTypes; - - -TYPED_TEST_CASE(CpuIsolatorTest, CpuIsolatorTypes); - - -TYPED_TEST(CpuIsolatorTest, UserCpuUsage) -{ - slave::Flags flags; - - Try<Isolator*> isolator = TypeParam::create(flags); - CHECK_SOME(isolator); - - // A PosixLauncher is sufficient even when testing a cgroups isolator. - Try<Launcher*> launcher = PosixLauncher::create(flags); - - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("cpus:1.0").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - const string& file = path::join(dir.get(), "mesos_isolator_test_ready"); - - // Max out a single core in userspace. This will run for at most one second. - string command = "while true ; do true ; done &" - "touch " + file + "; " // Signals the command is running. - "sleep 60"; - - int pipes[2]; - ASSERT_NE(-1, ::pipe(pipes)); - - vector<string> argv(3); - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = command; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - argv, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - lambda::bind(&childSetup, pipes)); - - ASSERT_SOME(pid); - - // Reap the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - // Continue in the parent. - ASSERT_SOME(os::close(pipes[0])); - - // Isolate the forked child. - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // Now signal the child to continue. - char dummy; - ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy))); - - ASSERT_SOME(os::close(pipes[1])); - - // Wait for the command to start. - while (!os::exists(file)); - - // Wait up to 1 second for the child process to induce 1/8 of a second of - // user cpu time. - ResourceStatistics statistics; - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - - statistics = usage.get(); - - // If we meet our usage expectations, we're done! - if (statistics.cpus_user_time_secs() >= 0.125) { - break; - } - - os::sleep(Milliseconds(200)); - waited += Milliseconds(200); - } while (waited < Seconds(1)); - - EXPECT_LE(0.125, statistics.cpus_user_time_secs()); - - // Ensure all processes are killed. - AWAIT_READY(launcher.get()->destroy(containerId)); - - // Make sure the child was reaped. - AWAIT_READY(status); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} - - -TYPED_TEST(CpuIsolatorTest, SystemCpuUsage) -{ - slave::Flags flags; - - Try<Isolator*> isolator = TypeParam::create(flags); - CHECK_SOME(isolator); - - // A PosixLauncher is sufficient even when testing a cgroups isolator. - Try<Launcher*> launcher = PosixLauncher::create(flags); - - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("cpus:1.0").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - const string& file = path::join(dir.get(), "mesos_isolator_test_ready"); - - // Generating random numbers is done by the kernel and will max out a single - // core and run almost exclusively in the kernel, i.e., system time. - string command = "cat /dev/urandom > /dev/null & " - "touch " + file + "; " // Signals the command is running. - "sleep 60"; - - int pipes[2]; - ASSERT_NE(-1, ::pipe(pipes)); - - vector<string> argv(3); - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = command; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - argv, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - lambda::bind(&childSetup, pipes)); - - ASSERT_SOME(pid); - - // Reap the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - // Continue in the parent. - ASSERT_SOME(os::close(pipes[0])); - - // Isolate the forked child. - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // Now signal the child to continue. - char dummy; - ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy))); - - ASSERT_SOME(os::close(pipes[1])); - - // Wait for the command to start. - while (!os::exists(file)); - - // Wait up to 1 second for the child process to induce 1/8 of a second of - // system cpu time. - ResourceStatistics statistics; - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - - statistics = usage.get(); - - // If we meet our usage expectations, we're done! - if (statistics.cpus_system_time_secs() >= 0.125) { - break; - } - - os::sleep(Milliseconds(200)); - waited += Milliseconds(200); - } while (waited < Seconds(1)); - - EXPECT_LE(0.125, statistics.cpus_system_time_secs()); - - // Ensure all processes are killed. - AWAIT_READY(launcher.get()->destroy(containerId)); - - // Make sure the child was reaped. - AWAIT_READY(status); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} - - -#ifdef __linux__ -class RevocableCpuIsolatorTest : public MesosTest {}; - - -TEST_F(RevocableCpuIsolatorTest, ROOT_CGROUPS_RevocableCpu) -{ - slave::Flags flags; - - Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = PosixLauncher::create(flags); - - // Include revocable CPU in the executor's resources. - Resource cpu = Resources::parse("cpus", "1", "*").get(); - cpu.mutable_revocable(); - - ExecutorInfo executorInfo; - executorInfo.add_resources()->CopyFrom(cpu); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - os::getcwd(), - None(), - None())); - - vector<string> argv{"sleep", "100"}; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sleep", - argv, - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null"), - None(), - None(), - None()); - - ASSERT_SOME(pid); - - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // Executor should have proper cpu.shares for revocable containers. - Result<string> cpuHierarchy = cgroups::hierarchy("cpu"); - ASSERT_SOME(cpuHierarchy); - - Result<string> cpuCgroup = cgroups::cpu::cgroup(pid.get()); - ASSERT_SOME(cpuCgroup); - - EXPECT_SOME_EQ( - CPU_SHARES_PER_CPU_REVOCABLE, - cgroups::cpu::shares(cpuHierarchy.get(), cpuCgroup.get())); - - // Kill the container and clean up. - Future<Option<int>> status = process::reap(pid.get()); - - AWAIT_READY(launcher.get()->destroy(containerId)); - - AWAIT_READY(status); - - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} -#endif // __linux__ - - -#ifdef __linux__ -class LimitedCpuIsolatorTest : public MesosTest {}; - - -TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs) -{ - slave::Flags flags; - - // Enable CFS to cap CPU utilization. - flags.cgroups_enable_cfs = true; - - Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = - LinuxLauncher::create(flags, isolator.get()->namespaces().get()); - CHECK_SOME(launcher); - - // Set the executor's resources to 0.5 cpu. - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("cpus:0.5").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - // Generate random numbers to max out a single core. We'll run this for 0.5 - // seconds of wall time so it should consume approximately 250 ms of total - // cpu time when limited to 0.5 cpu. We use /dev/urandom to prevent blocking - // on Linux when there's insufficient entropy. - string command = "cat /dev/urandom > /dev/null & " - "export MESOS_TEST_PID=$! && " - "sleep 0.5 && " - "kill $MESOS_TEST_PID"; - - int pipes[2]; - ASSERT_NE(-1, ::pipe(pipes)); - - vector<string> argv(3); - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = command; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - argv, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - lambda::bind(&childSetup, pipes)); - - ASSERT_SOME(pid); - - // Reap the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - // Continue in the parent. - ASSERT_SOME(os::close(pipes[0])); - - // Isolate the forked child. - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // Now signal the child to continue. - char dummy; - ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy))); - - ASSERT_SOME(os::close(pipes[1])); - - // Wait for the command to complete. - AWAIT_READY(status); - - Future<ResourceStatistics> usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - - // Expect that no more than 300 ms of cpu time has been consumed. We also - // check that at least 50 ms of cpu time has been consumed so this test will - // fail if the host system is very heavily loaded. This behavior is correct - // because under such conditions we aren't actually testing the CFS cpu - // limiter. - double cpuTime = usage.get().cpus_system_time_secs() + - usage.get().cpus_user_time_secs(); - - EXPECT_GE(0.30, cpuTime); - EXPECT_LE(0.05, cpuTime); - - // Ensure all processes are killed. - AWAIT_READY(launcher.get()->destroy(containerId)); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} - - -// This test verifies that we can successfully launch a container with -// a big (>= 10 cpus) cpu quota. This is to catch the regression -// observed in MESOS-1049. -// TODO(vinod): Revisit this if/when the isolator restricts the number -// of cpus that an executor can use based on the slave cpus. -TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs_Big_Quota) -{ - slave::Flags flags; - - // Enable CFS to cap CPU utilization. - flags.cgroups_enable_cfs = true; - - Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = - LinuxLauncher::create(flags, isolator.get()->namespaces().get()); - CHECK_SOME(launcher); - - // Set the executor's resources to 100.5 cpu. - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("cpus:100.5").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - int pipes[2]; - ASSERT_NE(-1, ::pipe(pipes)); - - vector<string> argv(3); - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = "exit 0"; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - argv, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - lambda::bind(&childSetup, pipes)); - - ASSERT_SOME(pid); - - // Reap the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - // Continue in the parent. - ASSERT_SOME(os::close(pipes[0])); - - // Isolate the forked child. - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // Now signal the child to continue. - char dummy; - ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy))); - - ASSERT_SOME(os::close(pipes[1])); - - // Wait for the command to complete successfully. - AWAIT_READY(status); - ASSERT_SOME_EQ(0, status.get()); - - // Ensure all processes are killed. - AWAIT_READY(launcher.get()->destroy(containerId)); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} - - -// A test to verify the number of processes and threads in a -// container. -TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Pids_and_Tids) -{ - slave::Flags flags; - flags.cgroups_cpu_enable_pids_and_tids_count = true; - - Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = - LinuxLauncher::create(flags, isolator.get()->namespaces().get()); - CHECK_SOME(launcher); - - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("cpus:0.5;mem:512").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - // Right after the creation of the cgroup, which happens in - // 'prepare', we check that it is empty. - Future<ResourceStatistics> usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - EXPECT_EQ(0U, usage.get().processes()); - EXPECT_EQ(0U, usage.get().threads()); - - int pipes[2]; - ASSERT_NE(-1, ::pipe(pipes)); - - vector<string> argv(3); - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = "while true; do sleep 1; done;"; - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - argv, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - lambda::bind(&childSetup, pipes)); - - ASSERT_SOME(pid); - - // Reap the forked child. - Future<Option<int>> status = process::reap(pid.get()); - - // Continue in the parent. - ASSERT_SOME(os::close(pipes[0])); - - // Before isolation, the cgroup is empty. - usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - EXPECT_EQ(0U, usage.get().processes()); - EXPECT_EQ(0U, usage.get().threads()); - - // Isolate the forked child. - AWAIT_READY(isolator.get()->isolate(containerId, pid.get())); - - // After the isolation, the cgroup is not empty, even though the - // process hasn't exec'd yet. - usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - EXPECT_EQ(1U, usage.get().processes()); - EXPECT_EQ(1U, usage.get().threads()); - - // Now signal the child to continue. - char dummy; - ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy))); - - ASSERT_SOME(os::close(pipes[1])); - - // Process count should be 1 since 'sleep' is still sleeping. - usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - EXPECT_EQ(1U, usage.get().processes()); - EXPECT_EQ(1U, usage.get().threads()); - - // Ensure all processes are killed. - AWAIT_READY(launcher.get()->destroy(containerId)); - - // Wait for the command to complete. - AWAIT_READY(status); - - // After the process is killed, the cgroup should be empty again. - usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - EXPECT_EQ(0U, usage.get().processes()); - EXPECT_EQ(0U, usage.get().threads()); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); - delete launcher.get(); -} -#endif // __linux__ - - -template <typename T> -class MemIsolatorTest : public MesosTest {}; - - -typedef ::testing::Types< - PosixMemIsolatorProcess, -#ifdef __linux__ - CgroupsMemIsolatorProcess, -#endif // __linux__ - tests::Module<Isolator, TestMemIsolator>> MemIsolatorTypes; - - -TYPED_TEST_CASE(MemIsolatorTest, MemIsolatorTypes); - - -TYPED_TEST(MemIsolatorTest, MemUsage) -{ - slave::Flags flags; - - Try<Isolator*> isolator = TypeParam::create(flags); - CHECK_SOME(isolator); - - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("mem:1024").get()); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - MemoryTestHelper helper; - ASSERT_SOME(helper.spawn()); - ASSERT_SOME(helper.pid()); - - // Set up the reaper to wait on the subprocess. - Future<Option<int>> status = process::reap(helper.pid().get()); - - // Isolate the subprocess. - AWAIT_READY(isolator.get()->isolate(containerId, helper.pid().get())); - - const Bytes allocation = Megabytes(128); - EXPECT_SOME(helper.increaseRSS(allocation)); - - Future<ResourceStatistics> usage = isolator.get()->usage(containerId); - AWAIT_READY(usage); - - EXPECT_GE(usage.get().mem_rss_bytes(), allocation.bytes()); - - // Ensure the process is killed. - helper.cleanup(); - - // Make sure the subprocess was reaped. - AWAIT_READY(status); - - // Let the isolator clean up. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); -} - - -#ifdef __linux__ -class PerfEventIsolatorTest : public MesosTest {}; - - -TEST_F(PerfEventIsolatorTest, ROOT_CGROUPS_Sample) -{ - slave::Flags flags; - - flags.perf_events = "cycles,task-clock"; - flags.perf_duration = Milliseconds(250); - flags.perf_interval = Milliseconds(500); - - Try<Isolator*> isolator = CgroupsPerfEventIsolatorProcess::create(flags); - ASSERT_SOME(isolator); - - ExecutorInfo executorInfo; - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Use a relative temporary directory so it gets cleaned up - // automatically with the test. - Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX")); - ASSERT_SOME(dir); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - dir.get(), - None(), - None())); - - // This first sample is likely to be empty because perf hasn't - // completed yet but we should still have the required fields. - Future<ResourceStatistics> statistics1 = isolator.get()->usage(containerId); - AWAIT_READY(statistics1); - ASSERT_TRUE(statistics1.get().has_perf()); - EXPECT_TRUE(statistics1.get().perf().has_timestamp()); - EXPECT_TRUE(statistics1.get().perf().has_duration()); - - // Wait until we get the next sample. We use a generous timeout of - // two seconds because we currently have a one second reap interval; - // when running perf with perf_duration of 250ms we won't notice the - // exit for up to one second. - ResourceStatistics statistics2; - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> statistics = isolator.get()->usage(containerId); - AWAIT_READY(statistics); - - statistics2 = statistics.get(); - - ASSERT_TRUE(statistics2.has_perf()); - - if (statistics1.get().perf().timestamp() != - statistics2.perf().timestamp()) { - break; - } - - os::sleep(Milliseconds(250)); - waited += Milliseconds(250); - } while (waited < Seconds(2)); - - sleep(2); - - EXPECT_NE(statistics1.get().perf().timestamp(), - statistics2.perf().timestamp()); - - EXPECT_TRUE(statistics2.perf().has_cycles()); - EXPECT_LE(0u, statistics2.perf().cycles()); - - EXPECT_TRUE(statistics2.perf().has_task_clock()); - EXPECT_LE(0.0, statistics2.perf().task_clock()); - - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); -} - - -class SharedFilesystemIsolatorTest : public MesosTest {}; - - -// Test that a container can create a private view of a system -// directory (/var/tmp). Check that a file written by a process inside -// the container doesn't appear on the host filesystem but does appear -// under the container's work directory. -// This test is disabled since we're planning to remove the shared -// filesystem isolator and this test is not working on other distros -// such as CentOS 7.1 -// TODO(tnachen): Remove this test when shared filesystem isolator -// is removed. -TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_RelativeVolume) -{ - slave::Flags flags = CreateSlaveFlags(); - flags.isolation = "filesystem/shared"; - - Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = - LinuxLauncher::create(flags, isolator.get()->namespaces().get()); - CHECK_SOME(launcher); - - // Use /var/tmp so we don't mask the work directory (under /tmp). - const string containerPath = "/var/tmp"; - ASSERT_TRUE(os::stat::isdir(containerPath)); - - // Use a host path relative to the container work directory. - const string hostPath = strings::remove(containerPath, "/", strings::PREFIX); - - ContainerInfo containerInfo; - containerInfo.set_type(ContainerInfo::MESOS); - containerInfo.add_volumes()->CopyFrom( - CREATE_VOLUME(containerPath, hostPath, Volume::RW)); - - ExecutorInfo executorInfo; - executorInfo.mutable_container()->CopyFrom(containerInfo); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - Future<Option<CommandInfo> > prepare = - isolator.get()->prepare( - containerId, - executorInfo, - flags.work_dir, - None(), - None()); - - AWAIT_READY(prepare); - ASSERT_SOME(prepare.get()); - - // The test will touch a file in container path. - const string file = path::join(containerPath, UUID::random().toString()); - ASSERT_FALSE(os::exists(file)); - - // Manually run the isolator's preparation command first, then touch - // the file. - vector<string> args; - args.push_back("/bin/sh"); - args.push_back("-x"); - args.push_back("-c"); - args.push_back(prepare.get().get().value() + " && touch " + file); - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - args, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - None()); - ASSERT_SOME(pid); - - // Set up the reaper to wait on the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - AWAIT_READY(status); - EXPECT_SOME_EQ(0, status.get()); - - // Check the correct hierarchy was created under the container work - // directory. - string dir = "/"; - foreach (const string& subdir, strings::tokenize(containerPath, "/")) { - dir = path::join(dir, subdir); - - struct stat hostStat; - EXPECT_EQ(0, ::stat(dir.c_str(), &hostStat)); - - struct stat containerStat; - EXPECT_EQ(0, - ::stat(path::join(flags.work_dir, dir).c_str(), &containerStat)); - - EXPECT_EQ(hostStat.st_mode, containerStat.st_mode); - EXPECT_EQ(hostStat.st_uid, containerStat.st_uid); - EXPECT_EQ(hostStat.st_gid, containerStat.st_gid); - } - - // Check it did *not* create a file in the host namespace. - EXPECT_FALSE(os::exists(file)); - - // Check it did create the file under the container's work directory - // on the host. - EXPECT_TRUE(os::exists(path::join(flags.work_dir, file))); - - delete launcher.get(); - delete isolator.get(); -} - - -// This test is disabled since we're planning to remove the shared -// filesystem isolator and this test is not working on other distros -// such as CentOS 7.1 -// TODO(tnachen): Remove this test when shared filesystem isolator -// is removed. -TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_AbsoluteVolume) -{ - slave::Flags flags = CreateSlaveFlags(); - flags.isolation = "filesystem/shared"; - - Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags); - CHECK_SOME(isolator); - - Try<Launcher*> launcher = - LinuxLauncher::create(flags, isolator.get()->namespaces().get()); - CHECK_SOME(launcher); - - // We'll mount the absolute test work directory as /var/tmp in the - // container. - const string hostPath = flags.work_dir; - const string containerPath = "/var/tmp"; - - ContainerInfo containerInfo; - containerInfo.set_type(ContainerInfo::MESOS); - containerInfo.add_volumes()->CopyFrom( - CREATE_VOLUME(containerPath, hostPath, Volume::RW)); - - ExecutorInfo executorInfo; - executorInfo.mutable_container()->CopyFrom(containerInfo); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - Future<Option<CommandInfo> > prepare = - isolator.get()->prepare( - containerId, - executorInfo, - flags.work_dir, - None(), - None()); - - AWAIT_READY(prepare); - ASSERT_SOME(prepare.get()); - - // Test the volume mounting by touching a file in the container's - // /tmp, which should then be in flags.work_dir. - const string filename = UUID::random().toString(); - ASSERT_FALSE(os::exists(path::join(containerPath, filename))); - - vector<string> args; - args.push_back("/bin/sh"); - args.push_back("-x"); - args.push_back("-c"); - args.push_back(prepare.get().get().value() + - " && touch " + - path::join(containerPath, filename)); - - Try<pid_t> pid = launcher.get()->fork( - containerId, - "/bin/sh", - args, - Subprocess::FD(STDIN_FILENO), - Subprocess::FD(STDOUT_FILENO), - Subprocess::FD(STDERR_FILENO), - None(), - None(), - None()); - ASSERT_SOME(pid); - - // Set up the reaper to wait on the forked child. - Future<Option<int> > status = process::reap(pid.get()); - - AWAIT_READY(status); - EXPECT_SOME_EQ(0, status.get()); - - // Check the file was created in flags.work_dir. - EXPECT_TRUE(os::exists(path::join(hostPath, filename))); - - // Check it didn't get created in the host's view of containerPath. - EXPECT_FALSE(os::exists(path::join(containerPath, filename))); - - delete launcher.get(); - delete isolator.get(); -} - - -class NamespacesPidIsolatorTest : public MesosTest {}; - - -TEST_F(NamespacesPidIsolatorTest, ROOT_PidNamespace) -{ - slave::Flags flags = CreateSlaveFlags(); - flags.isolation = "namespaces/pid"; - - string directory = os::getcwd(); // We're inside a temporary sandbox. - - Fetcher fetcher; - - Try<MesosContainerizer*> containerizer = - MesosContainerizer::create(flags, false, &fetcher); - ASSERT_SOME(containerizer); - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - // Write the command's pid namespace inode and init name to files. - const string command = - "stat -c %i /proc/self/ns/pid > ns && (cat /proc/1/comm > init)"; - - process::Future<bool> launch = containerizer.get()->launch( - containerId, - CREATE_EXECUTOR_INFO("executor", command), - directory, - None(), - SlaveID(), - process::PID<Slave>(), - false); - AWAIT_READY(launch); - ASSERT_TRUE(launch.get()); - - // Wait on the container. - process::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()); - - // Check that the command was run in a different pid namespace. - Try<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 'sh' is 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_EQ("sh", strings::trim(init.get())); - - delete containerizer.get(); -} - - -// Username for the unprivileged user that will be created to test -// unprivileged cgroup creation. It will be removed after the tests. -// It is presumed this user does not normally exist. -const string UNPRIVILEGED_USERNAME = "mesos.test.unprivileged.user"; - - -template <typename T> -class UserCgroupIsolatorTest : public MesosTest -{ -public: - static void SetUpTestCase() - { - // Remove the user in case it wasn't cleaned up from a previous - // test. - os::system("userdel -r " + UNPRIVILEGED_USERNAME + " > /dev/null"); - - ASSERT_EQ(0, os::system("useradd " + UNPRIVILEGED_USERNAME)); - } - - - static void TearDownTestCase() - { - ASSERT_EQ(0, os::system("userdel -r " + UNPRIVILEGED_USERNAME)); - } -}; - - -// Test all isolators that use cgroups. -typedef ::testing::Types< - CgroupsMemIsolatorProcess, - CgroupsCpushareIsolatorProcess, - CgroupsPerfEventIsolatorProcess> CgroupsIsolatorTypes; - - -TYPED_TEST_CASE(UserCgroupIsolatorTest, CgroupsIsolatorTypes); - - -TYPED_TEST(UserCgroupIsolatorTest, ROOT_CGROUPS_UserCgroup) -{ - slave::Flags flags; - flags.perf_events = "cpu-cycles"; // Needed for CgroupsPerfEventIsolator. - - Try<Isolator*> isolator = TypeParam::create(flags); - ASSERT_SOME(isolator); - - ExecutorInfo executorInfo; - executorInfo.mutable_resources()->CopyFrom( - Resources::parse("mem:1024;cpus:1").get()); // For cpu/mem isolators. - - ContainerID containerId; - containerId.set_value(UUID::random().toString()); - - AWAIT_READY(isolator.get()->prepare( - containerId, - executorInfo, - os::getcwd(), - None(), - UNPRIVILEGED_USERNAME)); - - // Isolators don't provide a way to determine the cgroups they use - // so we'll inspect the cgroups for an isolated dummy process. - pid_t pid = fork(); - if (pid == 0) { - // Child just sleeps. - ::sleep(100); - - ABORT("Child process should not reach here"); - } - ASSERT_GT(pid, 0); - - AWAIT_READY(isolator.get()->isolate(containerId, pid)); - - // Get the container's cgroups from /proc/$PID/cgroup. We're only - // interested in the cgroups that this isolator has created which we - // can do explicitly by selecting those that have the path that - // corresponds to the 'cgroups_root' slave flag. For example: - // - // $ cat /proc/pid/cgroup - // 6:blkio:/ - // 5:perf_event:/ - // 4:memory:/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c - // 3:freezer:/ - // 2:cpuacct:/ - // 1:cpu:/ - // - // Our 'grep' will only select the 'memory' line and then 'awk' will - // output 'memory/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c'. - ostringstream output; - Try<int> status = os::shell( - &output, - "grep '" + path::join("/", flags.cgroups_root) + "' /proc/" + - stringify(pid) + "/cgroup | awk -F ':' '{print $2$3}'"); - - ASSERT_SOME(status); - - // Kill the dummy child process. - ::kill(pid, SIGKILL); - int exitStatus; - EXPECT_NE(-1, ::waitpid(pid, &exitStatus, 0)); - - vector<string> cgroups = strings::tokenize(output.str(), "\n"); - ASSERT_FALSE(cgroups.empty()); - - foreach (const string& cgroup, cgroups) { - // Check the user cannot manipulate the container's cgroup control - // files. - EXPECT_NE(0, os::system( - "su - " + UNPRIVILEGED_USERNAME + - " -c 'echo $$ >" + - path::join(flags.cgroups_hierarchy, cgroup, "cgroup.procs") + - "'")); - - // Check the user can create a cgroup under the container's - // cgroup. - string userCgroup = path::join(cgroup, "user"); - - EXPECT_EQ(0, os::system( - "su - " + - UNPRIVILEGED_USERNAME + - " -c 'mkdir " + - path::join(flags.cgroups_hierarchy, userCgroup) + - "'")); - - // Check the user can manipulate control files in the created - // cgroup. - EXPECT_EQ(0, os::system( - "su - " + - UNPRIVILEGED_USERNAME + - " -c 'echo $$ >" + - path::join(flags.cgroups_hierarchy, userCgroup, "cgroup.procs") + - "'")); - } - - // Clean up the container. This will also remove the nested cgroups. - AWAIT_READY(isolator.get()->cleanup(containerId)); - - delete isolator.get(); -} -#endif // __linux__ - -} // namespace tests { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/launch_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/launch_tests.cpp b/src/tests/launch_tests.cpp deleted file mode 100644 index 73c8c5f..0000000 --- a/src/tests/launch_tests.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/** - * 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 <string> -#include <vector> - -#include <gmock/gmock.h> - -#include <stout/foreach.hpp> -#include <stout/gtest.hpp> -#include <stout/os.hpp> -#include <stout/try.hpp> - -#include <process/gtest.hpp> -#include <process/io.hpp> -#include <process/reap.hpp> -#include <process/subprocess.hpp> - -#include "mesos/resources.hpp" - -#include "slave/containerizer/mesos/launch.hpp" - -#include "linux/fs.hpp" - -#include "tests/flags.hpp" -#include "tests/utils.hpp" - -using namespace process; - -using std::string; -using std::vector; - -namespace mesos { -namespace internal { -namespace tests { - -class Chroot -{ -public: - Chroot(const string& _rootfs) - : rootfs(_rootfs) {} - - virtual ~Chroot() {} - - virtual Try<Subprocess> run(const string& command) = 0; - - const string rootfs; -}; - - -class BasicLinuxChroot : public Chroot -{ -public: - static Try<Owned<Chroot>> create(const string& rootfs) - { - if (!os::exists(rootfs)) { - return Error("rootfs does not exist"); - } - - if (os::system("cp -r /bin " + rootfs + "/") != 0) { - return ErrnoError("Failed to copy /bin to chroot"); - } - - if (os::system("cp -r /lib " + rootfs + "/") != 0) { - return ErrnoError("Failed to copy /lib to chroot"); - } - - if (os::system("cp -r /lib64 " + rootfs + "/") != 0) { - return ErrnoError("Failed to copy /lib64 to chroot"); - } - - vector<string> directories = {"proc", "sys", "dev", "tmp"}; - foreach (const string& directory, directories) { - Try<Nothing> mkdir = os::mkdir(path::join(rootfs, directory)); - if (mkdir.isError()) { - return Error("Failed to create /" + directory + " in chroot: " + - mkdir.error()); - } - } - - // We need to bind mount the rootfs so we can pivot on it. - Try<Nothing> mount = - fs::mount(rootfs, rootfs, None(), MS_BIND | MS_SLAVE, NULL); - - if (mount.isError()) { - return Error("Failed to bind mount chroot rootfs: " + mount.error()); - } - - return Owned<Chroot>(new BasicLinuxChroot(rootfs)); - } - - virtual Try<Subprocess> run(const string& _command) - { - slave::MesosContainerizerLaunch::Flags launchFlags; - - CommandInfo command; - command.set_value(_command); - - launchFlags.command = JSON::Protobuf(command); - launchFlags.directory = "/tmp"; - launchFlags.pipe_read = open("/dev/zero", O_RDONLY); - launchFlags.pipe_write = open("/dev/null", O_WRONLY); - launchFlags.rootfs = rootfs; - - vector<string> argv(2); - argv[0] = "mesos-containerizer"; - argv[1] = slave::MesosContainerizerLaunch::NAME; - - Try<Subprocess> s = subprocess( - path::join(tests::flags.build_dir, "src", "mesos-containerizer"), - argv, - Subprocess::PATH("/dev/null"), - Subprocess::PIPE(), - Subprocess::FD(STDERR_FILENO), - launchFlags, - None(), - None(), - lambda::bind(&clone, lambda::_1)); - - if (s.isError()) { - close(launchFlags.pipe_read.get()); - close(launchFlags.pipe_write.get()); - } else { - s.get().status().onAny([=]() { - // Close when the subprocess terminates. - close(launchFlags.pipe_read.get()); - close(launchFlags.pipe_write.get()); - }); - } - - return s; - } - -private: - static pid_t clone(const lambda::function<int()>& f) - { - static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)]; - - return ::clone( - _clone, - &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down. - CLONE_NEWNS | SIGCHLD, // Specify SIGCHLD as child termination signal. - (void*) &f); - } - - static int _clone(void* f) - { - const lambda::function<int()>* _f = - static_cast<const lambda::function<int()>*> (f); - - return (*_f)(); - } - - BasicLinuxChroot(const string& rootfs) : Chroot(rootfs) {} - - ~BasicLinuxChroot() - { - // Because the test process has the rootfs as its cwd the umount - // won't actually happen until the - // TemporaryDirectoryTest::TearDown() changes back to the original - // directory. - fs::unmount(rootfs, MNT_DETACH); - } -}; - - -template <typename T> -class LaunchChrootTest : public TemporaryDirectoryTest {}; - - -// TODO(idownes): Add tests for OSX chroots. -typedef ::testing::Types<BasicLinuxChroot> ChrootTypes; - - -TYPED_TEST_CASE(LaunchChrootTest, ChrootTypes); - - -TYPED_TEST(LaunchChrootTest, ROOT_DifferentRoot) -{ - Try<Owned<Chroot>> chroot = TypeParam::create(os::getcwd()); - ASSERT_SOME(chroot); - - // Add /usr/bin/stat into the chroot. - const string usrbin = path::join(chroot.get()->rootfs, "usr", "bin"); - ASSERT_SOME(os::mkdir(usrbin)); - ASSERT_EQ(0, os::system("cp /usr/bin/stat " + path::join(usrbin, "stat"))); - - Clock::pause(); - - Try<Subprocess> s = chroot.get()->run( - "/usr/bin/stat -c %i / >" + path::join("/", "stat.output")); - - CHECK_SOME(s); - - // Advance time until the internal reaper reaps the subprocess. - while (s.get().status().isPending()) { - Clock::advance(Seconds(1)); - Clock::settle(); - } - - AWAIT_ASSERT_READY(s.get().status()); - ASSERT_SOME(s.get().status().get()); - - int status = s.get().status().get().get(); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(0, WEXITSTATUS(status)); - - // Check the chroot has a different root by comparing the inodes. - Try<ino_t> self = os::stat::inode("/"); - ASSERT_SOME(self); - - Try<string> read = os::read(path::join(chroot.get()->rootfs, "stat.output")); - CHECK_SOME(read); - - Try<ino_t> other = numify<ino_t>(strings::trim(read.get())); - ASSERT_SOME(other); - - EXPECT_NE(self.get(), other.get()); -} - -} // namespace tests { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/launcher.hpp ---------------------------------------------------------------------- diff --git a/src/tests/launcher.hpp b/src/tests/launcher.hpp deleted file mode 100644 index 78216e0..0000000 --- a/src/tests/launcher.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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 <list> -#include <map> -#include <string> -#include <vector> - -#include <gmock/gmock.h> - -#include <mesos/mesos.hpp> - -#include <mesos/slave/isolator.hpp> - -#include <process/future.hpp> -#include <process/owned.hpp> -#include <process/subprocess.hpp> - -#include <stout/flags.hpp> -#include <stout/lambda.hpp> -#include <stout/nothing.hpp> -#include <stout/option.hpp> - -#include "slave/containerizer/launcher.hpp" - -namespace mesos { -namespace internal { -namespace tests { - - -ACTION_P(InvokeRecover, launcher) -{ - return launcher->real->recover(arg0); -} - - -ACTION_P(InvokeFork, launcher) -{ - return launcher->real->fork( - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); -} - - -ACTION_P(InvokeDestroy, launcher) -{ - return launcher->real->destroy(arg0); -} - - -class TestLauncher : public slave::Launcher -{ -public: - TestLauncher(const process::Owned<slave::Launcher>& _real) - : real(_real) - { - using testing::_; - using testing::DoDefault; - - ON_CALL(*this, recover(_)) - .WillByDefault(InvokeRecover(this)); - EXPECT_CALL(*this, recover(_)) - .WillRepeatedly(DoDefault()); - - ON_CALL(*this, fork(_, _, _, _, _, _, _, _, _)) - .WillByDefault(InvokeFork(this)); - EXPECT_CALL(*this, fork(_, _, _, _, _, _, _, _, _)) - .WillRepeatedly(DoDefault()); - - ON_CALL(*this, destroy(_)) - .WillByDefault(InvokeDestroy(this)); - EXPECT_CALL(*this, destroy(_)) - .WillRepeatedly(DoDefault()); - } - - ~TestLauncher() {} - - MOCK_METHOD1( - recover, - process::Future<hashset<ContainerID>>( - const std::list<mesos::slave::ExecutorRunState>& states)); - - MOCK_METHOD9( - fork, - Try<pid_t>( - const ContainerID& containerId, - const std::string& path, - const std::vector<std::string>& argv, - const process::Subprocess::IO& in, - const process::Subprocess::IO& out, - const process::Subprocess::IO& err, - const Option<flags::FlagsBase>& flags, - const Option<std::map<std::string, std::string> >& env, - const Option<lambda::function<int()> >& setup)); - - MOCK_METHOD1( - destroy, - process::Future<Nothing>(const ContainerID& containerId)); - - process::Owned<slave::Launcher> real; -}; - -} // namespace tests { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/memory_pressure_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/memory_pressure_tests.cpp b/src/tests/memory_pressure_tests.cpp deleted file mode 100644 index 8089879..0000000 --- a/src/tests/memory_pressure_tests.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/** - * 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/resources.hpp> -#include <mesos/scheduler.hpp> - -#include <process/gtest.hpp> - -#include <stout/gtest.hpp> -#include <stout/os.hpp> - -#include "master/master.hpp" - -#include "slave/slave.hpp" - -#include "slave/containerizer/containerizer.hpp" -#include "slave/containerizer/fetcher.hpp" - -#include "messages/messages.hpp" - -#include "tests/mesos.hpp" - -using namespace process; - -using mesos::internal::master::Master; - -using mesos::internal::slave::Fetcher; -using mesos::internal::slave::MesosContainerizer; -using mesos::internal::slave::Slave; - -using std::vector; - -using testing::_; -using testing::Eq; -using testing::Return; - -namespace mesos { -namespace internal { -namespace tests { - -class MemoryPressureMesosTest : public ContainerizerTest<MesosContainerizer> -{ -public: - static void SetUpTestCase() - { - // Verify that the dd command and its flags used in a bit are valid - // on this system. - ASSERT_EQ(0, os::system("dd count=1 bs=1M if=/dev/zero of=/dev/null")) - << "Cannot find a compatible 'dd' command"; - } -}; - - -TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_Statistics) -{ - Try<PID<Master>> master = StartMaster(); - ASSERT_SOME(master); - - slave::Flags flags = CreateSlaveFlags(); - - // We only care about memory cgroup for this test. - flags.isolation = "cgroups/mem"; - flags.slave_subsystems = None(); - - Fetcher fetcher; - - Try<MesosContainerizer*> containerizer = - MesosContainerizer::create(flags, true, &fetcher); - - ASSERT_SOME(containerizer); - - Try<PID<Slave>> slave = StartSlave(containerizer.get(), flags); - ASSERT_SOME(slave); - - MockScheduler sched; - - MesosSchedulerDriver driver( - &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); - - EXPECT_CALL(sched, registered(_, _, _)); - - Future<vector<Offer>> offers; - EXPECT_CALL(sched, resourceOffers(_, _)) - .WillOnce(FutureArg<1>(&offers)) - .WillRepeatedly(Return()); // Ignore subsequent offers. - - driver.start(); - - AWAIT_READY(offers); - EXPECT_NE(0u, offers.get().size()); - - Offer offer = offers.get()[0]; - - // Run a task that triggers memory pressure event. We request 1G - // disk because we are going to write a 512 MB file repeatedly. - TaskInfo task = createTask( - offer.slave_id(), - Resources::parse("cpus:1;mem:256;disk:1024").get(), - "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done"); - - Future<TaskStatus> status; - EXPECT_CALL(sched, statusUpdate(&driver, _)) - .WillOnce(FutureArg<1>(&status)) - .WillRepeatedly(Return()); // Ignore subsequent updates. - - driver.launchTasks(offer.id(), {task}); - - AWAIT_READY(status); - EXPECT_EQ(task.task_id(), status.get().task_id()); - EXPECT_EQ(TASK_RUNNING, status.get().state()); - - Future<hashset<ContainerID>> containers = containerizer.get()->containers(); - AWAIT_READY(containers); - ASSERT_EQ(1u, containers.get().size()); - - ContainerID containerId = *(containers.get().begin()); - - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> usage = containerizer.get()->usage(containerId); - AWAIT_READY(usage); - - if (usage.get().mem_low_pressure_counter() > 0) { - EXPECT_GE(usage.get().mem_low_pressure_counter(), - usage.get().mem_medium_pressure_counter()); - EXPECT_GE(usage.get().mem_medium_pressure_counter(), - usage.get().mem_critical_pressure_counter()); - break; - } - - os::sleep(Milliseconds(100)); - waited += Milliseconds(100); - } while (waited < Seconds(5)); - - EXPECT_LE(waited, Seconds(5)); - - driver.stop(); - driver.join(); - - Shutdown(); - delete containerizer.get(); -} - - -// Test that memory pressure listening is restarted after recovery. -TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_SlaveRecovery) -{ - Try<PID<Master>> master = StartMaster(); - ASSERT_SOME(master); - - slave::Flags flags = CreateSlaveFlags(); - - // We only care about memory cgroup for this test. - flags.isolation = "cgroups/mem"; - flags.slave_subsystems = None(); - - Fetcher fetcher; - - Try<MesosContainerizer*> containerizer1 = - MesosContainerizer::create(flags, true, &fetcher); - - ASSERT_SOME(containerizer1); - - Try<PID<Slave>> slave = StartSlave(containerizer1.get(), flags); - ASSERT_SOME(slave); - - MockScheduler sched; - - // Enable checkpointing for the framework. - FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; - frameworkInfo.set_checkpoint(true); - - MesosSchedulerDriver driver( - &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); - - EXPECT_CALL(sched, registered(_, _, _)); - - Future<vector<Offer>> offers; - EXPECT_CALL(sched, resourceOffers(_, _)) - .WillOnce(FutureArg<1>(&offers)) - .WillRepeatedly(Return()); // Ignore subsequent offers. - - driver.start(); - - AWAIT_READY(offers); - EXPECT_NE(0u, offers.get().size()); - - Offer offer = offers.get()[0]; - - // Run a task that triggers memory pressure event. We request 1G - // disk because we are going to write a 512 MB file repeatedly. - TaskInfo task = createTask( - offer.slave_id(), - Resources::parse("cpus:1;mem:256;disk:1024").get(), - "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done"); - - Future<TaskStatus> status; - EXPECT_CALL(sched, statusUpdate(&driver, _)) - .WillOnce(FutureArg<1>(&status)) - .WillRepeatedly(Return()); // Ignore subsequent updates. - - driver.launchTasks(offers.get()[0].id(), {task}); - - AWAIT_READY(status); - EXPECT_EQ(task.task_id(), status.get().task_id()); - EXPECT_EQ(TASK_RUNNING, status.get().state()); - - // We restart the slave to let it recover. - Stop(slave.get()); - delete containerizer1.get(); - - Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover); - - Future<SlaveReregisteredMessage> slaveReregisteredMessage = - FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); - - // Use the same flags. - Try<MesosContainerizer*> containerizer2 = - MesosContainerizer::create(flags, true, &fetcher); - - ASSERT_SOME(containerizer2); - - slave = StartSlave(containerizer2.get(), flags); - ASSERT_SOME(slave); - - Clock::pause(); - - AWAIT_READY(_recover); - - // Wait for slave to schedule reregister timeout. - Clock::settle(); - - // Ensure the slave considers itself recovered. - Clock::advance(slave::EXECUTOR_REREGISTER_TIMEOUT); - - Clock::resume(); - - // Wait for the slave to re-register. - AWAIT_READY(slaveReregisteredMessage); - - Future<hashset<ContainerID>> containers = containerizer2.get()->containers(); - AWAIT_READY(containers); - ASSERT_EQ(1u, containers.get().size()); - - ContainerID containerId = *(containers.get().begin()); - - Duration waited = Duration::zero(); - do { - Future<ResourceStatistics> usage = containerizer2.get()->usage(containerId); - AWAIT_READY(usage); - - if (usage.get().mem_low_pressure_counter() > 0) { - EXPECT_GE(usage.get().mem_low_pressure_counter(), - usage.get().mem_medium_pressure_counter()); - EXPECT_GE(usage.get().mem_medium_pressure_counter(), - usage.get().mem_critical_pressure_counter()); - break; - } - - os::sleep(Milliseconds(100)); - waited += Milliseconds(100); - } while (waited < Seconds(5)); - - EXPECT_LE(waited, Seconds(5)); - - driver.stop(); - driver.join(); - - Shutdown(); - delete containerizer2.get(); -} - -} // namespace tests { -} // namespace internal { -} // namespace mesos {
