blob: 1d2fc36fe491d5ecfcab386ee6915cabb544505a [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <set>
#include <string>
#include <vector>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <gmock/gmock.h>
#include <process/gtest.hpp>
#include <stout/gtest.hpp>
#include <stout/hashmap.hpp>
#include <stout/numify.hpp>
#include <stout/option.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/proc.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include "linux/cgroups.hpp"
#include "linux/perf.hpp"
#include "tests/mesos.hpp" // For TEST_CGROUPS_(HIERARCHY|ROOT).
using namespace mesos::internal::tests;
using namespace process;
using std::set;
class CgroupsTest : public ::testing::Test
{
public:
static void SetUpTestCase()
{
// Clean up the testing hierarchy, in case it wasn't cleaned up
// properly from previous tests.
AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
}
static void TearDownTestCase()
{
AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
}
};
// A fixture which is used to name tests that expect NO hierarchy to
// exist in order to test the ability to create a hierarchy (since
// most likely existing hierarchies will have all or most subsystems
// attached rendering our ability to create a hierarchy fruitless).
class CgroupsNoHierarchyTest : public CgroupsTest
{
public:
static void SetUpTestCase()
{
CgroupsTest::SetUpTestCase();
Try<std::set<std::string> > hierarchies = cgroups::hierarchies();
ASSERT_SOME(hierarchies);
ASSERT_TRUE(hierarchies.get().empty())
<< "-------------------------------------------------------------\n"
<< "We cannot run any cgroups tests that require mounting\n"
<< "hierarchies because you have the following hierarchies mounted:\n"
<< strings::trim(stringify(hierarchies.get()), " {},") << "\n"
<< "You can either unmount those hierarchies, or disable\n"
<< "this test case (i.e., --gtest_filter=-CgroupsNoHierarchyTest.*).\n"
<< "-------------------------------------------------------------";
}
};
// A fixture that assumes ANY hierarchy is acceptable for use provided
// it has the subsystems attached that were specified in the
// constructor. If no hierarchy could be found that has all the
// required subsystems then we attempt to create a new hierarchy.
class CgroupsAnyHierarchyTest : public CgroupsTest
{
public:
CgroupsAnyHierarchyTest(const std::string& _subsystems = "cpu")
: subsystems(_subsystems) {}
protected:
virtual void SetUp()
{
foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
// Establish the base hierarchy if this is the first subsystem checked.
if (baseHierarchy.empty()) {
Result<std::string> hierarchy = cgroups::hierarchy(subsystem);
ASSERT_FALSE(hierarchy.isError());
if (hierarchy.isNone()) {
baseHierarchy = TEST_CGROUPS_HIERARCHY;
} else {
// Strip the subsystem to get the base hierarchy.
baseHierarchy = strings::remove(
hierarchy.get(),
subsystem,
strings::SUFFIX);
}
}
// Mount the subsystem if necessary.
std::string hierarchy = path::join(baseHierarchy, subsystem);
Try<bool> mounted = cgroups::mounted(hierarchy, subsystem);
ASSERT_SOME(mounted);
if (!mounted.get()) {
ASSERT_SOME(cgroups::mount(hierarchy, subsystem))
<< "-------------------------------------------------------------\n"
<< "We cannot run any cgroups tests that require\n"
<< "a hierarchy with subsystem '" << subsystem << "'\n"
<< "because we failed to find an existing hierarchy\n"
<< "or create a new one (tried '" << hierarchy << "').\n"
<< "You can either remove all existing\n"
<< "hierarchies, or disable this test case\n"
<< "(i.e., --gtest_filter=-"
<< ::testing::UnitTest::GetInstance()
->current_test_info()
->test_case_name() << ".*).\n"
<< "-------------------------------------------------------------";
}
Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
CHECK_SOME(cgroups);
foreach (const std::string& cgroup, cgroups.get()) {
// Remove any cgroups that start with TEST_CGROUPS_ROOT.
if (cgroup == TEST_CGROUPS_ROOT) {
AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
}
}
}
}
virtual void TearDown()
{
// Remove all *our* cgroups.
foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
std::string hierarchy = path::join(baseHierarchy, subsystem);
Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
CHECK_SOME(cgroups);
foreach (const std::string& cgroup, cgroups.get()) {
// Remove any cgroups that start with TEST_CGROUPS_ROOT.
if (cgroup == TEST_CGROUPS_ROOT) {
AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
}
}
}
}
const std::string subsystems; // Subsystems required to run tests.
std::string baseHierarchy; // Path to the hierarchy being used.
};
class CgroupsAnyHierarchyWithCpuMemoryTest
: public CgroupsAnyHierarchyTest
{
public:
CgroupsAnyHierarchyWithCpuMemoryTest()
: CgroupsAnyHierarchyTest("cpu,memory") {}
};
class CgroupsAnyHierarchyWithFreezerTest
: public CgroupsAnyHierarchyTest
{
public:
CgroupsAnyHierarchyWithFreezerTest()
: CgroupsAnyHierarchyTest("freezer") {}
};
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Enabled)
{
EXPECT_SOME_TRUE(cgroups::enabled(""));
EXPECT_SOME_TRUE(cgroups::enabled(","));
EXPECT_SOME_TRUE(cgroups::enabled("cpu"));
EXPECT_SOME_TRUE(cgroups::enabled(",cpu"));
EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory"));
EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory,"));
EXPECT_ERROR(cgroups::enabled("invalid"));
EXPECT_ERROR(cgroups::enabled("cpu,invalid"));
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Busy)
{
EXPECT_SOME_FALSE(cgroups::busy(""));
EXPECT_SOME_FALSE(cgroups::busy(","));
EXPECT_SOME_TRUE(cgroups::busy("cpu"));
EXPECT_SOME_TRUE(cgroups::busy(",cpu"));
EXPECT_SOME_TRUE(cgroups::busy("cpu,memory"));
EXPECT_SOME_TRUE(cgroups::busy("cpu,memory,"));
EXPECT_ERROR(cgroups::busy("invalid"));
EXPECT_ERROR(cgroups::busy("cpu,invalid"));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Subsystems)
{
Try<std::set<std::string> > names = cgroups::subsystems();
ASSERT_SOME(names);
Option<std::string> cpu;
Option<std::string> memory;
foreach (const std::string& name, names.get()) {
if (name == "cpu") {
cpu = name;
} else if (name == "memory") {
memory = name;
}
}
EXPECT_SOME(cpu);
EXPECT_SOME(memory);
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_SubsystemsHierarchy)
{
std::string cpuHierarchy = path::join(baseHierarchy, "cpu");
Try<std::set<std::string> > names = cgroups::subsystems(cpuHierarchy);
ASSERT_SOME(names);
Option<std::string> cpu;
Option<std::string> memory;
foreach (const std::string& name, names.get()) {
if (name == "cpu") {
cpu = name;
} else if (name == "memory") {
memory = name;
}
}
EXPECT_SOME(cpu);
EXPECT_NONE(memory);
std::string memoryHierarchy = path::join(baseHierarchy, "memory");
names = cgroups::subsystems(memoryHierarchy);
ASSERT_SOME(names);
cpu = None();
memory = None();
foreach (const std::string& name, names.get()) {
if (name == "cpu") {
cpu = name;
} else if (name == "memory") {
memory = name;
}
}
EXPECT_NONE(cpu);
EXPECT_SOME(memory);
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FindCgroupSubsystems)
{
pid_t pid = ::getpid();
Result<std::string> cpuHierarchy = cgroups::cpu::cgroup(pid);
EXPECT_FALSE(cpuHierarchy.isError());
EXPECT_SOME(cpuHierarchy);
Result<std::string> memHierarchy = cgroups::memory::cgroup(pid);
EXPECT_FALSE(memHierarchy.isError());
EXPECT_SOME(memHierarchy);
}
TEST_F(CgroupsNoHierarchyTest, ROOT_CGROUPS_NOHIERARCHY_MountUnmountHierarchy)
{
EXPECT_ERROR(cgroups::mount("/tmp", "cpu"));
EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "invalid"));
// Try to mount a valid hierarchy, retrying as necessary since the
// previous unmount might not have taken effect yet due to a bug in
// Ubuntu 12.04.
ASSERT_SOME(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpu,memory", 10));
EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpuset"));
EXPECT_ERROR(cgroups::unmount("/tmp"));
ASSERT_SOME(cgroups::unmount(TEST_CGROUPS_HIERARCHY));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Mounted)
{
EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist"));
EXPECT_SOME_FALSE(cgroups::mounted("/tmp"));
EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected"));
EXPECT_SOME_TRUE(cgroups::mounted(baseHierarchy + "/cpu"));
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_MountedSubsystems)
{
EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist", "cpu"));
EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu,memory"));
EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu"));
EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "invalid"));
EXPECT_SOME_TRUE(cgroups::mounted(path::join(baseHierarchy, "cpu"), "cpu"));
EXPECT_SOME_TRUE(cgroups::mounted(
path::join(baseHierarchy, "memory"), "memory"));
EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy, "invalid"));
EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected", "cpu"));
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_CreateRemove)
{
EXPECT_ERROR(cgroups::create("/tmp", "test"));
EXPECT_ERROR(cgroups::create(baseHierarchy, "mesos_test_missing/1"));
ASSERT_SOME(cgroups::create(
path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
EXPECT_ERROR(cgroups::remove(baseHierarchy, "invalid"));
ASSERT_SOME(cgroups::remove(
path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Get)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
ASSERT_SOME(cgroups::create(hierarchy, "mesos_test1"));
ASSERT_SOME(cgroups::create(hierarchy, "mesos_test2"));
Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
ASSERT_SOME(cgroups);
EXPECT_EQ(cgroups.get()[0], "mesos_test2");
EXPECT_EQ(cgroups.get()[1], "mesos_test1");
ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test1"));
ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test2"));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_NestedCgroups)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
ASSERT_SOME(cgroups::create(hierarchy, path::join(TEST_CGROUPS_ROOT, "1")))
<< "-------------------------------------------------------------\n"
<< "We cannot run this test because it appears you do not have\n"
<< "a modern enough version of the Linux kernel. You won't be\n"
<< "able to use the cgroups isolator, but feel free to disable\n"
<< "this test.\n"
<< "-------------------------------------------------------------";
ASSERT_SOME(cgroups::create(hierarchy, path::join(TEST_CGROUPS_ROOT, "2")));
Try<std::vector<std::string> > cgroups =
cgroups::get(hierarchy, TEST_CGROUPS_ROOT);
ASSERT_SOME(cgroups);
ASSERT_EQ(2u, cgroups.get().size());
EXPECT_EQ(cgroups.get()[0], path::join(TEST_CGROUPS_ROOT, "2"));
EXPECT_EQ(cgroups.get()[1], path::join(TEST_CGROUPS_ROOT, "1"));
ASSERT_SOME(cgroups::remove(hierarchy, path::join(TEST_CGROUPS_ROOT, "1")));
ASSERT_SOME(cgroups::remove(hierarchy, path::join(TEST_CGROUPS_ROOT, "2")));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Tasks)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
Try<std::set<pid_t> > pids = cgroups::processes(hierarchy, "/");
ASSERT_SOME(pids);
EXPECT_NE(0u, pids.get().count(1));
EXPECT_NE(0u, pids.get().count(::getpid()));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Read)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
EXPECT_ERROR(cgroups::read(hierarchy, TEST_CGROUPS_ROOT, "invalid"));
std::string pid = stringify(::getpid());
Try<std::string> result = cgroups::read(hierarchy, "/", "tasks");
ASSERT_SOME(result);
EXPECT_TRUE(strings::contains(result.get(), pid));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Write)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
EXPECT_ERROR(
cgroups::write(hierarchy, TEST_CGROUPS_ROOT, "invalid", "invalid"));
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// In child process, wait for kill signal.
while (true) { sleep(1); }
// Should not reach here.
const char* message = "Error, child should be killed before reaching here";
while (write(STDERR_FILENO, message, strlen(message)) == -1 &&
errno == EINTR);
_exit(1);
}
// In parent process.
ASSERT_SOME(
cgroups::write(hierarchy,
TEST_CGROUPS_ROOT,
"cgroup.procs",
stringify(pid)));
Try<std::set<pid_t> > pids = cgroups::processes(hierarchy, TEST_CGROUPS_ROOT);
ASSERT_SOME(pids);
EXPECT_NE(0u, pids.get().count(pid));
// Kill the child process.
ASSERT_NE(-1, ::kill(pid, SIGKILL));
// Wait for the child process.
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
}
TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Cfs_Big_Quota)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
Duration quota = Seconds(100); // Big quota.
ASSERT_SOME(cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT, quota));
// Ensure we can read back the correct quota.
ASSERT_SOME_EQ(
quota,
cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT));
}
class CgroupsAnyHierarchyWithCpuAcctMemoryTest
: public CgroupsAnyHierarchyTest
{
public:
CgroupsAnyHierarchyWithCpuAcctMemoryTest()
: CgroupsAnyHierarchyTest("cpuacct,memory") {}
};
TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_Stat)
{
EXPECT_ERROR(cgroups::stat(baseHierarchy, TEST_CGROUPS_ROOT, "invalid"));
Try<hashmap<std::string, uint64_t> > result =
cgroups::stat(
path::join(baseHierarchy, "cpuacct"), "/", "cpuacct.stat");
ASSERT_SOME(result);
EXPECT_TRUE(result.get().contains("user"));
EXPECT_TRUE(result.get().contains("system"));
EXPECT_GT(result.get().get("user").get(), 0llu);
EXPECT_GT(result.get().get("system").get(), 0llu);
result = cgroups::stat(
path::join(baseHierarchy, "memory"), "/", "memory.stat");
ASSERT_SOME(result);
EXPECT_TRUE(result.get().contains("rss"));
EXPECT_GT(result.get().get("rss").get(), 0llu);
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Listen)
{
std::string hierarchy = path::join(baseHierarchy, "memory");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
ASSERT_SOME(
cgroups::memory::oom::killer::enabled(hierarchy, TEST_CGROUPS_ROOT))
<< "-------------------------------------------------------------\n"
<< "We cannot run this test because it appears you do not have\n"
<< "a modern enough version of the Linux kernel. You won't be\n"
<< "able to use the cgroups isolator, but feel free to disable\n"
<< "this test.\n"
<< "-------------------------------------------------------------";
// Disable oom killer.
ASSERT_SOME(cgroups::memory::oom::killer::disable(
hierarchy, TEST_CGROUPS_ROOT));
// Limit the memory usage of the test cgroup to 64MB.
ASSERT_SOME(cgroups::memory::limit_in_bytes(
hierarchy, TEST_CGROUPS_ROOT, Megabytes(64)));
// Listen on oom events for test cgroup.
Future<Nothing> future =
cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
ASSERT_FALSE(future.isFailed());
// Test the cancellation.
future.discard();
// Test the normal operation below.
future = cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
ASSERT_FALSE(future.isFailed());
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid > 0) {
// In parent process.
future.await(Seconds(5));
EXPECT_TRUE(future.isReady());
// Kill the child process.
EXPECT_NE(-1, ::kill(pid, SIGKILL));
// Wait for the child process.
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
} else {
// In child process. We try to trigger an oom here.
// Put self into the test cgroup.
Try<Nothing> assign =
cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
if (assign.isError()) {
std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
abort();
}
// Blow up the memory.
size_t limit = 1024 * 1024 * 512;
void* buffer = NULL;
if (posix_memalign(&buffer, getpagesize(), limit) != 0) {
perror("Failed to allocate page-aligned memory, posix_memalign");
abort();
}
// We use mlock and memset here to make sure that the memory
// actually gets paged in and thus accounted for.
if (mlock(buffer, limit) != 0) {
perror("Failed to lock memory, mlock");
abort();
}
if (memset(buffer, 1, limit) != buffer) {
perror("Failed to fill memory, memset");
abort();
}
// Should not reach here.
std::cerr << "OOM does not happen!" << std::endl;
abort();
}
}
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Freeze)
{
int pipes[2];
int dummy;
ASSERT_NE(-1, ::pipe(pipes));
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// In child process.
::close(pipes[0]);
// Put self into the test cgroup.
Try<Nothing> assign =
cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
if (assign.isError()) {
std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
abort();
}
// Notify the parent.
if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
perror("Failed to notify the parent");
abort();
}
::close(pipes[1]);
// Infinite loop here.
while (true);
// Should not reach here.
std::cerr << "Reach an unreachable statement!" << std::endl;
abort();
}
// In parent process.
::close(pipes[1]);
// Wait until child has assigned the cgroup.
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
::close(pipes[0]);
// Freeze the test cgroup.
AWAIT_EXPECT_READY(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
// Thaw the test cgroup.
AWAIT_EXPECT_READY(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
// Kill the child process.
ASSERT_NE(-1, ::kill(pid, SIGKILL));
// Wait for the child process.
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
}
TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FreezeNonFreezer)
{
std::string hierarchy = path::join(baseHierarchy, "cpu");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
AWAIT_EXPECT_FAILED(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
AWAIT_EXPECT_FAILED(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
// The cgroup is empty so we should still be able to destroy it.
AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
}
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Kill)
{
int pipes[2];
int dummy;
ASSERT_NE(-1, ::pipe(pipes));
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid > 0) {
// In parent process.
::close(pipes[1]);
// Wait until all children have assigned the cgroup.
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
::close(pipes[0]);
Try<Nothing> kill = cgroups::kill(hierarchy, TEST_CGROUPS_ROOT, SIGKILL);
EXPECT_SOME(kill);
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
} else {
// In child process.
// We create 4 child processes here using two forks to test the case in
// which there are multiple active processes in the given cgroup.
::fork();
::fork();
// Put self into the test cgroup.
Try<Nothing> assign =
cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
if (assign.isError()) {
std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
abort();
}
// Notify the parent.
::close(pipes[0]); // TODO(benh): Close after first fork?
if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
perror("Failed to notify the parent");
abort();
}
::close(pipes[1]);
// Wait kill signal from parent.
while (true);
// Should not reach here.
std::cerr << "Reach an unreachable statement!" << std::endl;
abort();
}
}
// TODO(benh): Write a version of this test with nested cgroups.
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Destroy)
{
int pipes[2];
int dummy;
ASSERT_NE(-1, ::pipe(pipes));
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid > 0) {
// In parent process.
::close(pipes[1]);
// Wait until all children have assigned the cgroup.
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
::close(pipes[0]);
AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
// cgroups::destroy will reap all processes in the cgroup so we should
// *not* be able to reap it now.
int status;
EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
EXPECT_EQ(ECHILD, errno);
} else {
// In child process.
// We create 4 child processes here using two forks to test the case in
// which there are multiple active processes in the given cgroup.
::fork();
::fork();
// Put self into the test cgroup.
Try<Nothing> assign =
cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
if (assign.isError()) {
std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
abort();
}
// Notify the parent.
::close(pipes[0]); // TODO(benh): Close after first fork?
if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
perror("Failed to notify the parent");
abort();
}
::close(pipes[1]);
// Wait kill signal from parent.
while (true) ;
// Should not reach here.
std::cerr << "Reach an unreachable statement!" << std::endl;
abort();
}
}
void* threadFunction(void*)
{
// Newly created threads have PTHREAD_CANCEL_ENABLE and
// PTHREAD_CANCEL_DEFERRED so they can be cancelled from the main thread.
while (true) { sleep(1); }
return NULL;
}
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_AssignThreads)
{
size_t numThreads = 5;
pthread_t pthreads[numThreads];
// Create additional threads.
for (size_t i = 0; i < numThreads; i++)
{
EXPECT_EQ(0, pthread_create(&pthreads[i], NULL, threadFunction, NULL));
}
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
// Check the test cgroup is initially empty.
Try<set<pid_t> > cgroupThreads =
cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
EXPECT_SOME(cgroupThreads);
EXPECT_EQ(0u, cgroupThreads.get().size());
// Assign ourselves to the test cgroup.
CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
// Get our threads (may be more than the numThreads we created if
// other threads are running).
Try<set<pid_t> > threads = proc::threads(::getpid());
ASSERT_SOME(threads);
// Check the test cgroup now only contains all child threads.
cgroupThreads = cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
EXPECT_SOME(cgroupThreads);
EXPECT_SOME_EQ(threads.get(), cgroupThreads);
// Terminate the additional threads.
for (size_t i = 0; i < numThreads; i++)
{
EXPECT_EQ(0, pthread_cancel(pthreads[i]));
EXPECT_EQ(0, pthread_join(pthreads[i], NULL));
}
// Move ourselves to the root cgroup.
CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
// Destroy the cgroup.
AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
}
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyStoppedProcess)
{
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// In child process.
while (true) { sleep(1); }
ABORT("Child should not reach this statement");
}
// In parent process.
// Put child into the freezer cgroup.
Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
// Stop the child process.
EXPECT_EQ(0, kill(pid, SIGSTOP));
AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
// cgroups::destroy will reap all processes in the cgroup so we should
// *not* be able to reap it now.
int status;
EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
EXPECT_EQ(ECHILD, errno);
}
TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyTracedProcess)
{
std::string hierarchy = path::join(baseHierarchy, "freezer");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// In child process.
while (true) { sleep(1); }
ABORT("Child should not reach this statement");
}
// In parent process.
Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
ASSERT_SOME(assign);
// Attach to the child process.
ASSERT_EQ(0, ptrace(PT_ATTACH, pid, NULL, NULL));
// Wait until the process is in traced state ('t' or 'T').
Duration elapsed = Duration::zero();
while (true) {
Result<proc::ProcessStatus> process = proc::status(pid);
ASSERT_SOME(process);
if (process.get().state == 'T' || process.get().state == 't') {
break;
}
if (elapsed > Seconds(1)) {
FAIL() << "Failed to wait for process to be traced";
}
os::sleep(Milliseconds(5));
elapsed += Milliseconds(5);
}
// Now destroy the cgroup.
AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
// cgroups::destroy will reap all processes in the cgroup so we should
// *not* be able to reap it now.
int status;
EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
EXPECT_EQ(ECHILD, errno);
}
class CgroupsAnyHierarchyWithPerfEventTest
: public CgroupsAnyHierarchyTest
{
public:
CgroupsAnyHierarchyWithPerfEventTest()
: CgroupsAnyHierarchyTest("perf_event") {}
};
TEST_F(CgroupsAnyHierarchyWithPerfEventTest, ROOT_CGROUPS_Perf)
{
int pipes[2];
int dummy;
ASSERT_NE(-1, ::pipe(pipes));
std::string hierarchy = path::join(baseHierarchy, "perf_event");
ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
pid_t pid = ::fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// In child process.
::close(pipes[1]);
// Wait until parent has assigned us to the cgroup.
ssize_t len;
while ((len = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
errno == EINTR);
ASSERT_EQ((ssize_t) sizeof(dummy), len);
::close(pipes[0]);
while (true) { sleep(1); }
ABORT("Child should not reach here");
}
// In parent.
::close(pipes[0]);
// Put child into the test cgroup.
ASSERT_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid));
ssize_t len;
while ((len = ::write(pipes[1], &dummy, sizeof(dummy))) == -1 &&
errno == EINTR);
ASSERT_EQ((ssize_t) sizeof(dummy), len);
::close(pipes[1]);
std::set<std::string> events;
// Hardware event.
events.insert("cycles");
// Software event.
events.insert("task-clock");
Future<mesos::PerfStatistics> statistics =
perf::sample(events, TEST_CGROUPS_ROOT, Seconds(1));
AWAIT_READY(statistics);
ASSERT_TRUE(statistics.get().has_cycles());
EXPECT_LT(0u, statistics.get().cycles());
ASSERT_TRUE(statistics.get().has_task_clock());
EXPECT_LT(0.0, statistics.get().task_clock());
// Kill the child process.
ASSERT_NE(-1, ::kill(pid, SIGKILL));
// Wait for the child process.
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
// Destroy the cgroup.
Future<Nothing> destroy = cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT);
AWAIT_READY(destroy);
}