jdk-24/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp
2024-09-11 13:51:31 +00:00

558 lines
23 KiB
C++

/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "precompiled.hpp"
#ifdef LINUX
#include "runtime/os.hpp"
#include "cgroupSubsystem_linux.hpp"
#include "cgroupV1Subsystem_linux.hpp"
#include "cgroupV2Subsystem_linux.hpp"
#include "unittest.hpp"
#include "utilities/globalDefinitions.hpp"
#include <stdio.h>
// for basename
#include <libgen.h>
typedef struct {
const char* mount_path;
const char* root_path;
const char* cgroup_path;
const char* expected_path;
} TestCase;
// Utilities
static bool file_exists(const char* filename) {
struct stat st;
return os::stat(filename, &st) == 0;
}
// we rely on temp_file returning modifiable memory in resource area.
static char* temp_file(const char* prefix) {
const testing::TestInfo* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
stringStream path;
path.print_raw(os::get_temp_directory());
path.print_raw(os::file_separator());
path.print("%s-test-jdk.pid%d.%s.%s", prefix, os::current_process_id(),
test_info->test_case_name(), test_info->name());
return path.as_string(true);
}
static void delete_file(const char* filename) {
if (!file_exists(filename)) {
return;
}
int ret = remove(filename);
EXPECT_TRUE(ret == 0 || errno == ENOENT) << "failed to remove file '" << filename << "': "
<< os::strerror(errno) << " (" << errno << ")";
}
class TestController : public CgroupController {
private:
char* _path;
public:
TestController(char* p): _path(p) {}
const char* subsystem_path() override {
return _path;
};
bool is_read_only() override {
return true; // doesn't matter
}
};
static void fill_file(const char* path, const char* content) {
delete_file(path);
FILE* fp = os::fopen(path, "w");
if (fp == nullptr) {
return;
}
if (content != nullptr) {
fprintf(fp, "%s", content);
}
fclose(fp);
}
TEST(cgroupTest, read_numerical_key_value_failure_cases) {
char* test_file = temp_file("cgroups");
const char* b = basename(test_file);
EXPECT_TRUE(b != nullptr) << "basename was null";
stringStream path;
path.print_raw(os::file_separator());
path.print_raw(b);
const char* base_with_slash = path.as_string(true);
TestController* controller = new TestController((char*)os::get_temp_directory());
constexpr julong bad = 0xBAD;
julong x = bad;
fill_file(test_file, "foo ");
bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_FALSE(is_ok) << "Value is missing in key/value case, expecting false";
EXPECT_EQ(bad, x) << "x must be unchanged";
x = bad;
fill_file(test_file, "faulty_start foo 101");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_FALSE(is_ok) << "key must be at the start";
EXPECT_EQ(bad, x) << "x must be unchanged";
x = bad;
fill_file(test_file, nullptr);
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_FALSE(is_ok) << "key not in empty file";
EXPECT_EQ(bad, x) << "x must be unchanged";
x = bad;
fill_file(test_file, "foo\n");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_FALSE(is_ok) << "key must have a value";
EXPECT_EQ(bad, x) << "x must be unchanged";
x = bad;
fill_file(test_file, "foof 1002");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_FALSE(is_ok) << "key must be exact match";
EXPECT_EQ(bad, x) << "x must be unchanged";
// Cleanup
delete_file(test_file);
}
TEST(cgroupTest, read_numerical_key_value_success_cases) {
char* test_file = temp_file("cgroups");
const char* b = basename(test_file);
EXPECT_TRUE(b != nullptr) << "basename was null";
stringStream path;
path.print_raw(os::file_separator());
path.print_raw(b);
const char* base_with_slash = path.as_string(true);
TestController* controller = new TestController((char*)os::get_temp_directory());
constexpr julong bad = 0xBAD;
julong x = bad;
fill_file(test_file, "foo 100");
bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)100, x);
x = bad;
fill_file(test_file, "foo\t111");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)111, x);
x = bad;
fill_file(test_file, "foo\nbar 333\nfoo\t111");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)111, x);
x = bad;
fill_file(test_file, "foof 100\nfoo 133");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)133, x);
x = bad;
fill_file(test_file, "foo\t333\nfoot 999");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)333, x);
x = bad;
fill_file(test_file, "foo 1\nfoo car");
is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x);
EXPECT_TRUE(is_ok);
EXPECT_EQ((julong)1, x);
// Cleanup
delete_file(test_file);
}
TEST(cgroupTest, read_number_null) {
TestController* null_path_controller = new TestController((char*)nullptr);
const char* test_file_path = "/not-used";
constexpr julong bad = 0xBAD;
julong a = bad;
// null subsystem_path() case
bool is_ok = null_path_controller->read_number(test_file_path, &a);
EXPECT_FALSE(is_ok) << "Null subsystem path should be an error";
EXPECT_EQ(bad, a) << "Expected untouched scan value";
}
TEST(cgroupTest, read_string_beyond_max_path) {
char larger_than_max[MAXPATHLEN + 1];
for (int i = 0; i < (MAXPATHLEN); i++) {
larger_than_max[i] = 'A' + (i % 26);
}
larger_than_max[MAXPATHLEN] = '\0';
TestController* too_large_path_controller = new TestController(larger_than_max);
const char* test_file_path = "/file-not-found";
char foo[1024];
foo[0] = '\0';
bool is_ok = too_large_path_controller->read_string(test_file_path, foo, 1024);
EXPECT_FALSE(is_ok) << "Too long path should be an error";
EXPECT_STREQ("", foo) << "Expected untouched scan value";
}
TEST(cgroupTest, read_number_file_not_exist) {
TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist");
const char* test_file_path = "/file-not-found";
constexpr julong bad = 0xBAD;
julong result = bad;
bool is_ok = unknown_path_ctrl->read_number(test_file_path, &result);
EXPECT_FALSE(is_ok) << "File not found should be an error";
EXPECT_EQ(bad, result) << "Expected untouched scan value";
}
TEST(cgroupTest, read_numerical_key_value_null) {
TestController* null_path_controller = new TestController((char*)nullptr);
const char* test_file_path = "/not-used";
const char* key = "something";
constexpr julong bad = 0xBAD;
julong a = bad;
// null subsystem_path() case
bool is_ok = null_path_controller->read_numerical_key_value(test_file_path, key, &a);
EXPECT_FALSE(is_ok) << "Null subsystem path should be an error";
EXPECT_EQ(bad, a) << "Expected untouched scan value";
}
TEST(cgroupTest, read_number_tests) {
char* test_file = temp_file("cgroups");
const char* b = basename(test_file);
constexpr julong bad = 0xBAD;
EXPECT_TRUE(b != nullptr) << "basename was null";
stringStream path;
path.print_raw(os::file_separator());
path.print_raw(b);
const char* base_with_slash = path.as_string(true);
fill_file(test_file, "8888");
TestController* controller = new TestController((char*)os::get_temp_directory());
julong foo = bad;
bool ok = controller->read_number(base_with_slash, &foo);
EXPECT_TRUE(ok) << "Number parsing should have been successful";
EXPECT_EQ((julong)8888, foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")";
// Some interface files might have negative values, ensure we can read
// them and manually cast them as needed.
fill_file(test_file, "-1");
foo = bad;
ok = controller->read_number(base_with_slash, &foo);
EXPECT_TRUE(ok) << "Number parsing should have been successful";
EXPECT_EQ((jlong)-1, (jlong)foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")";
foo = bad;
fill_file(test_file, nullptr);
ok = controller->read_number(base_with_slash, &foo);
EXPECT_FALSE(ok) << "Empty file should have failed";
EXPECT_EQ(bad, foo) << "foo was altered";
// Some interface files have numbers as well as the string
// 'max', which means unlimited.
jlong result = -10;
fill_file(test_file, "max\n");
ok = controller->read_number_handle_max(base_with_slash, &result);
EXPECT_TRUE(ok) << "Number parsing for 'max' string should have been successful";
EXPECT_EQ((jlong)-1, result) << "'max' means unlimited (-1)";
result = -10;
fill_file(test_file, "11114\n");
ok = controller->read_number_handle_max(base_with_slash, &result);
EXPECT_TRUE(ok) << "Number parsing for should have been successful";
EXPECT_EQ((jlong)11114, result) << "Incorrect result";
result = -10;
fill_file(test_file, "-51114\n");
ok = controller->read_number_handle_max(base_with_slash, &result);
EXPECT_TRUE(ok) << "Number parsing for should have been successful";
EXPECT_EQ((jlong)-51114, result) << "Incorrect result";
delete_file(test_file);
}
TEST(cgroupTest, read_string_tests) {
char* test_file = temp_file("cgroups");
const char* b = basename(test_file);
EXPECT_TRUE(b != nullptr) << "basename was null";
stringStream path;
path.print_raw(os::file_separator());
path.print_raw(b);
const char* base_with_slash = path.as_string(true);
fill_file(test_file, "foo-bar");
TestController* controller = new TestController((char*)os::get_temp_directory());
char result[1024];
bool ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_STREQ("foo-bar", result);
result[0] = '\0';
fill_file(test_file, "1234");
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_STREQ("1234", result);
// values with a space
result[0] = '\0';
fill_file(test_file, "abc def");
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_STREQ("abc def", result);
result[0] = '\0';
fill_file(test_file, " \na");
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_STREQ(" ", result);
// only the first line are being returned
result[0] = '\0';
fill_file(test_file, "test\nabc");
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_STREQ("test", result);
result[0] = '\0';
fill_file(test_file, nullptr);
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_FALSE(ok) << "Empty file should have failed";
EXPECT_STREQ("", result) << "Expected untouched result";
delete_file(test_file);
// File contents larger than 1K
// We only read in the first 1K - 1 bytes
const size_t large_len = 2 * 1024;
char too_large[large_len];
for (size_t i = 0; i < large_len; i++) {
too_large[i] = 'A' + (i % 26);
}
too_large[large_len - 1] = '\0';
result[0] = '\0';
fill_file(test_file, too_large);
ok = controller->read_string(base_with_slash, result, 1024);
EXPECT_TRUE(ok) << "String parsing should have been successful";
EXPECT_TRUE(1023 == strlen(result)) << "Expected only the first 1023 chars to be read in";
EXPECT_EQ(0, strncmp(too_large, result, 1023));
EXPECT_EQ(result[1023], '\0') << "The last character must be the null character";
}
TEST(cgroupTest, read_number_tuple_test) {
char* test_file = temp_file("cgroups");
const char* b = basename(test_file);
EXPECT_TRUE(b != nullptr) << "basename was null";
stringStream path;
path.print_raw(os::file_separator());
path.print_raw(b);
const char* base_with_slash = path.as_string(true);
fill_file(test_file, "max 10000");
TestController* controller = new TestController((char*)os::get_temp_directory());
jlong result = -10;
bool ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result);
EXPECT_TRUE(ok) << "Should be OK to read value";
EXPECT_EQ((jlong)-1, result) << "max should be unlimited (-1)";
result = -10;
ok = controller->read_numerical_tuple_value(base_with_slash, false /* use_first */, &result);
EXPECT_TRUE(ok) << "Should be OK to read the value";
EXPECT_EQ((jlong)10000, result);
// non-max strings
fill_file(test_file, "abc 10000");
result = -10;
ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result);
EXPECT_FALSE(ok) << "abc should not be parsable";
EXPECT_EQ((jlong)-10, result) << "result value should be unchanged";
fill_file(test_file, nullptr);
result = -10;
ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result);
EXPECT_FALSE(ok) << "Empty file should be an error";
EXPECT_EQ((jlong)-10, result) << "result value should be unchanged";
}
TEST(cgroupTest, read_numerical_key_beyond_max_path) {
char larger_than_max[MAXPATHLEN + 1];
for (int i = 0; i < (MAXPATHLEN); i++) {
larger_than_max[i] = 'A' + (i % 26);
}
larger_than_max[MAXPATHLEN] = '\0';
TestController* too_large_path_controller = new TestController(larger_than_max);
const char* test_file_path = "/file-not-found";
const char* key = "something";
julong a = 0xBAD;
bool is_ok = too_large_path_controller->read_numerical_key_value(test_file_path, key, &a);
EXPECT_FALSE(is_ok) << "Too long path should be an error";
EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value";
}
TEST(cgroupTest, read_numerical_key_file_not_exist) {
TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist");
const char* test_file_path = "/file-not-found";
const char* key = "something";
julong a = 0xBAD;
bool is_ok = unknown_path_ctrl->read_numerical_key_value(test_file_path, key, &a);
EXPECT_FALSE(is_ok) << "File not found should be an error";
EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value";
}
TEST(cgroupTest, set_cgroupv1_subsystem_path) {
TestCase host = {
"/sys/fs/cgroup/memory", // mount_path
"/", // root_path
"/user.slice/user-1000.slice/user@1000.service", // cgroup_path
"/sys/fs/cgroup/memory/user.slice/user-1000.slice/user@1000.service" // expected_path
};
TestCase container_engine = {
"/sys/fs/cgroup/mem", // mount_path
"/user.slice/user-1000.slice/user@1000.service", // root_path
"/user.slice/user-1000.slice/user@1000.service", // cgroup_path
"/sys/fs/cgroup/mem" // expected_path
};
int length = 2;
TestCase* testCases[] = { &host,
&container_engine };
for (int i = 0; i < length; i++) {
CgroupV1Controller* ctrl = new CgroupV1Controller( (char*)testCases[i]->root_path,
(char*)testCases[i]->mount_path,
true /* read-only mount */);
ctrl->set_subsystem_path((char*)testCases[i]->cgroup_path);
ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path());
}
}
TEST(cgroupTest, set_cgroupv2_subsystem_path) {
TestCase at_mount_root = {
"/sys/fs/cgroup", // mount_path
nullptr, // root_path, ignored
"/", // cgroup_path
"/sys/fs/cgroup" // expected_path
};
TestCase sub_path = {
"/sys/fs/cgroup", // mount_path
nullptr, // root_path, ignored
"/foobar", // cgroup_path
"/sys/fs/cgroup/foobar" // expected_path
};
int length = 2;
TestCase* testCases[] = { &at_mount_root,
&sub_path };
for (int i = 0; i < length; i++) {
CgroupV2Controller* ctrl = new CgroupV2Controller( (char*)testCases[i]->mount_path,
(char*)testCases[i]->cgroup_path,
true /* read-only mount */);
ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path());
}
}
TEST(cgroupTest, cgroupv2_is_hierarchy_walk_needed) {
bool controller_read_only = false; // value irrelevant;
CgroupV2Controller* test = new CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/" /* cgroup_path */,
controller_read_only);
EXPECT_FALSE(test->needs_hierarchy_adjustment());
test = new CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/bar" /* cgroup_path */,
controller_read_only);
EXPECT_TRUE(test->needs_hierarchy_adjustment());
test = new CgroupV2Controller((char*)"/sys/fs/cgroup/b",
(char*)"/a/b" /* cgroup_path */,
controller_read_only);
EXPECT_TRUE(test->needs_hierarchy_adjustment());
CgroupCpuController* test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/" /* cgroup_path */,
controller_read_only));
EXPECT_FALSE(test2->needs_hierarchy_adjustment());
test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/bar" /* cgroup_path */,
controller_read_only));
EXPECT_TRUE(test2->needs_hierarchy_adjustment());
test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup/b",
(char*)"/a/b" /* cgroup_path */,
controller_read_only));
EXPECT_TRUE(test2->needs_hierarchy_adjustment());
CgroupMemoryController* test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/" /* cgroup_path */,
controller_read_only));
EXPECT_FALSE(test3->needs_hierarchy_adjustment());
test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup",
(char*)"/bar" /* cgroup_path */,
controller_read_only));
EXPECT_TRUE(test3->needs_hierarchy_adjustment());
test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup/b",
(char*)"/a/b" /* cgroup_path */,
controller_read_only));
EXPECT_TRUE(test3->needs_hierarchy_adjustment());
}
TEST(cgroupTest, cgroupv1_is_hierarchy_walk_needed) {
bool controller_read_only = true; // shouldn't matter;
CgroupV1Controller* test = new CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/sys/fs/cgroup/memory" /* mount_path */,
controller_read_only);
test->set_subsystem_path((char*)"/a/b/c");
EXPECT_FALSE(test->needs_hierarchy_adjustment());
test->set_subsystem_path((char*)"/");
EXPECT_TRUE(test->needs_hierarchy_adjustment());
test = new CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/"/* mount_path */,
controller_read_only);
test->set_subsystem_path((char*)"/");
EXPECT_TRUE(test->needs_hierarchy_adjustment());
CgroupCpuController* test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/sys/fs/cgroup/memory" /* mount_path */,
controller_read_only));
static_cast<CgroupV1CpuController*>(test2)->set_subsystem_path((char*)"/a/b/c");
EXPECT_FALSE(test2->needs_hierarchy_adjustment());
static_cast<CgroupV1CpuController*>(test2)->set_subsystem_path((char*)"/");
EXPECT_TRUE(test2->needs_hierarchy_adjustment());
test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/"/* mount_path */,
controller_read_only));
static_cast<CgroupV1CpuController*>(test2)->set_subsystem_path((char*)"/");
EXPECT_TRUE(test2->needs_hierarchy_adjustment());
CgroupMemoryController* test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/sys/fs/cgroup/memory" /* mount_path */,
controller_read_only));
static_cast<CgroupV1MemoryController*>(test3)->set_subsystem_path((char*)"/a/b/c");
EXPECT_FALSE(test3->needs_hierarchy_adjustment());
static_cast<CgroupV1MemoryController*>(test3)->set_subsystem_path((char*)"/");
EXPECT_TRUE(test3->needs_hierarchy_adjustment());
test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */,
(char*)"/"/* mount_path */,
controller_read_only));
static_cast<CgroupV1MemoryController*>(test3)->set_subsystem_path((char*)"/");
EXPECT_TRUE(test3->needs_hierarchy_adjustment());
}
#endif // LINUX