mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-14 09:53:18 +00:00
8343191: Cgroup v1 subsystem fails to set subsystem path
Co-authored-by: Severin Gehwolf <sgehwolf@openjdk.org> Reviewed-by: sgehwolf, mbaesken
This commit is contained in:
parent
75f028b46b
commit
de29ef3bf3
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Red Hat, Inc.
|
||||
* Copyright (c) 2024, 2025, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -49,12 +49,18 @@ int CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int host_cpus) {
|
||||
}
|
||||
|
||||
void CgroupUtil::adjust_controller(CgroupMemoryController* mem) {
|
||||
assert(mem->cgroup_path() != nullptr, "invariant");
|
||||
if (strstr(mem->cgroup_path(), "../") != nullptr) {
|
||||
log_warning(os, container)("Cgroup memory controller path at '%s' seems to have moved to '%s', detected limits won't be accurate",
|
||||
mem->mount_point(), mem->cgroup_path());
|
||||
mem->set_subsystem_path("/");
|
||||
return;
|
||||
}
|
||||
if (!mem->needs_hierarchy_adjustment()) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
log_trace(os, container)("Adjusting controller path for memory: %s", mem->subsystem_path());
|
||||
assert(mem->cgroup_path() != nullptr, "invariant");
|
||||
char* orig = os::strdup(mem->cgroup_path());
|
||||
char* cg_path = os::strdup(orig);
|
||||
char* last_slash;
|
||||
@ -62,7 +68,8 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) {
|
||||
julong phys_mem = os::Linux::physical_memory();
|
||||
char* limit_cg_path = nullptr;
|
||||
jlong limit = mem->read_memory_limit_in_bytes(phys_mem);
|
||||
jlong lowest_limit = phys_mem;
|
||||
jlong lowest_limit = limit < 0 ? phys_mem : limit;
|
||||
julong orig_limit = ((julong)lowest_limit) != phys_mem ? lowest_limit : phys_mem;
|
||||
while ((last_slash = strrchr(cg_path, '/')) != cg_path) {
|
||||
*last_slash = '\0'; // strip path
|
||||
// update to shortened path and try again
|
||||
@ -83,7 +90,7 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) {
|
||||
limit_cg_path = os::strdup("/");
|
||||
}
|
||||
assert(lowest_limit >= 0, "limit must be positive");
|
||||
if ((julong)lowest_limit != phys_mem) {
|
||||
if ((julong)lowest_limit != orig_limit) {
|
||||
// we've found a lower limit anywhere in the hierarchy,
|
||||
// set the path to the limit path
|
||||
assert(limit_cg_path != nullptr, "limit path must be set");
|
||||
@ -93,6 +100,7 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) {
|
||||
mem->subsystem_path(),
|
||||
lowest_limit);
|
||||
} else {
|
||||
log_trace(os, container)("Lowest limit was: " JLONG_FORMAT, lowest_limit);
|
||||
log_trace(os, container)("No lower limit found for memory in hierarchy %s, "
|
||||
"adjusting to original path %s",
|
||||
mem->mount_point(), orig);
|
||||
@ -104,19 +112,26 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) {
|
||||
}
|
||||
|
||||
void CgroupUtil::adjust_controller(CgroupCpuController* cpu) {
|
||||
assert(cpu->cgroup_path() != nullptr, "invariant");
|
||||
if (strstr(cpu->cgroup_path(), "../") != nullptr) {
|
||||
log_warning(os, container)("Cgroup cpu controller path at '%s' seems to have moved to '%s', detected limits won't be accurate",
|
||||
cpu->mount_point(), cpu->cgroup_path());
|
||||
cpu->set_subsystem_path("/");
|
||||
return;
|
||||
}
|
||||
if (!cpu->needs_hierarchy_adjustment()) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
log_trace(os, container)("Adjusting controller path for cpu: %s", cpu->subsystem_path());
|
||||
assert(cpu->cgroup_path() != nullptr, "invariant");
|
||||
char* orig = os::strdup(cpu->cgroup_path());
|
||||
char* cg_path = os::strdup(orig);
|
||||
char* last_slash;
|
||||
assert(cg_path[0] == '/', "cgroup path must start with '/'");
|
||||
int host_cpus = os::Linux::active_processor_count();
|
||||
int cpus = CgroupUtil::processor_count(cpu, host_cpus);
|
||||
int lowest_limit = host_cpus;
|
||||
int lowest_limit = cpus < host_cpus ? cpus: host_cpus;
|
||||
int orig_limit = lowest_limit != host_cpus ? lowest_limit : host_cpus;
|
||||
char* limit_cg_path = nullptr;
|
||||
while ((last_slash = strrchr(cg_path, '/')) != cg_path) {
|
||||
*last_slash = '\0'; // strip path
|
||||
@ -138,7 +153,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) {
|
||||
limit_cg_path = os::strdup(cg_path);
|
||||
}
|
||||
assert(lowest_limit >= 0, "limit must be positive");
|
||||
if (lowest_limit != host_cpus) {
|
||||
if (lowest_limit != orig_limit) {
|
||||
// we've found a lower limit anywhere in the hierarchy,
|
||||
// set the path to the limit path
|
||||
assert(limit_cg_path != nullptr, "limit path must be set");
|
||||
@ -148,6 +163,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) {
|
||||
cpu->subsystem_path(),
|
||||
lowest_limit);
|
||||
} else {
|
||||
log_trace(os, container)("Lowest limit was: %d", lowest_limit);
|
||||
log_trace(os, container)("No lower limit found for cpu in hierarchy %s, "
|
||||
"adjusting to original path %s",
|
||||
cpu->mount_point(), orig);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2025, 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
|
||||
@ -37,6 +37,47 @@
|
||||
/*
|
||||
* Set directory to subsystem specific files based
|
||||
* on the contents of the mountinfo and cgroup files.
|
||||
*
|
||||
* The method determines whether it runs in
|
||||
* - host mode
|
||||
* - container mode
|
||||
*
|
||||
* In the host mode, _root is equal to "/" and
|
||||
* the subsystem path is equal to the _mount_point path
|
||||
* joined with cgroup_path.
|
||||
*
|
||||
* In the container mode, it can be two possibilities:
|
||||
* - private namespace (cgroupns=private)
|
||||
* - host namespace (cgroupns=host, default mode in cgroup V1 hosts)
|
||||
*
|
||||
* Private namespace is equivalent to the host mode, i.e.
|
||||
* the subsystem path is set by concatenating
|
||||
* _mount_point and cgroup_path.
|
||||
*
|
||||
* In the host namespace, _root is equal to host's cgroup path
|
||||
* of the control group to which the containerized process
|
||||
* belongs to at the moment of creation. The mountinfo and
|
||||
* cgroup files are mirrored from the host, while the subsystem
|
||||
* specific files are mapped directly at _mount_point, i.e.
|
||||
* at /sys/fs/cgroup/<controller>/, the subsystem path is
|
||||
* then set equal to _mount_point.
|
||||
*
|
||||
* A special case of the subsystem path is when a cgroup path
|
||||
* includes a subgroup, when a containerized process was associated
|
||||
* with an existing cgroup, that is different from cgroup
|
||||
* in which the process has been created.
|
||||
* Here, the _root is equal to the host's initial cgroup path,
|
||||
* cgroup_path will be equal to host's new cgroup path.
|
||||
* As host cgroup hierarchies are not accessible in the container,
|
||||
* it needs to be determined which part of cgroup path
|
||||
* is accessible inside container, i.e. mapped under
|
||||
* /sys/fs/cgroup/<controller>/<subgroup>.
|
||||
* In Docker default setup, host's cgroup path can be
|
||||
* of the form: /docker/<CONTAINER_ID>/<subgroup>,
|
||||
* from which only <subgroup> is mapped.
|
||||
* The method trims cgroup path from left, until the subgroup
|
||||
* component is found. The subsystem path will be set to
|
||||
* the _mount_point joined with the subgroup path.
|
||||
*/
|
||||
void CgroupV1Controller::set_subsystem_path(const char* cgroup_path) {
|
||||
if (_cgroup_path != nullptr) {
|
||||
@ -49,28 +90,36 @@ void CgroupV1Controller::set_subsystem_path(const char* cgroup_path) {
|
||||
_cgroup_path = os::strdup(cgroup_path);
|
||||
stringStream ss;
|
||||
if (_root != nullptr && cgroup_path != nullptr) {
|
||||
ss.print_raw(_mount_point);
|
||||
if (strcmp(_root, "/") == 0) {
|
||||
ss.print_raw(_mount_point);
|
||||
// host processes and containers with cgroupns=private
|
||||
if (strcmp(cgroup_path,"/") != 0) {
|
||||
ss.print_raw(cgroup_path);
|
||||
}
|
||||
_path = os::strdup(ss.base());
|
||||
} else {
|
||||
if (strcmp(_root, cgroup_path) == 0) {
|
||||
ss.print_raw(_mount_point);
|
||||
_path = os::strdup(ss.base());
|
||||
} else {
|
||||
char *p = strstr((char*)cgroup_path, _root);
|
||||
if (p != nullptr && p == _root) {
|
||||
if (strlen(cgroup_path) > strlen(_root)) {
|
||||
ss.print_raw(_mount_point);
|
||||
const char* cg_path_sub = cgroup_path + strlen(_root);
|
||||
ss.print_raw(cg_path_sub);
|
||||
_path = os::strdup(ss.base());
|
||||
// containers with cgroupns=host, default setting is _root==cgroup_path
|
||||
if (strcmp(_root, cgroup_path) != 0) {
|
||||
if (*cgroup_path != '\0' && strcmp(cgroup_path, "/") != 0) {
|
||||
// When moved to a subgroup, between subgroups, the path suffix will change.
|
||||
const char *suffix = cgroup_path;
|
||||
while (suffix != nullptr) {
|
||||
stringStream pp;
|
||||
pp.print_raw(_mount_point);
|
||||
pp.print_raw(suffix);
|
||||
if (os::file_exists(pp.base())) {
|
||||
ss.print_raw(suffix);
|
||||
if (suffix != cgroup_path) {
|
||||
log_trace(os, container)("set_subsystem_path: cgroup v1 path reduced to: %s.", suffix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
log_trace(os, container)("set_subsystem_path: skipped non-existent directory: %s.", suffix);
|
||||
suffix = strchr(suffix + 1, '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_path = os::strdup(ss.base());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2022, Red Hat Inc.
|
||||
* Copyright (c) 2020, 2025, Red Hat Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -292,6 +292,10 @@ jlong memory_swap_limit_value(CgroupV2Controller* ctrl) {
|
||||
}
|
||||
|
||||
void CgroupV2Controller::set_subsystem_path(const char* cgroup_path) {
|
||||
if (_cgroup_path != nullptr) {
|
||||
os::free(_cgroup_path);
|
||||
}
|
||||
_cgroup_path = os::strdup(cgroup_path);
|
||||
if (_path != nullptr) {
|
||||
os::free(_path);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, 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
|
||||
@ -25,6 +25,9 @@
|
||||
|
||||
package jdk.internal.platform.cgroupv1;
|
||||
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import jdk.internal.platform.CgroupSubsystem;
|
||||
import jdk.internal.platform.CgroupSubsystemController;
|
||||
|
||||
@ -44,27 +47,36 @@ public class CgroupV1SubsystemController implements CgroupSubsystemController {
|
||||
|
||||
public void setPath(String cgroupPath) {
|
||||
if (root != null && cgroupPath != null) {
|
||||
String path = mountPoint;
|
||||
if (root.equals("/")) {
|
||||
// host processes and containers with cgroupns=private
|
||||
if (!cgroupPath.equals("/")) {
|
||||
path = mountPoint + cgroupPath;
|
||||
path += cgroupPath;
|
||||
}
|
||||
else {
|
||||
path = mountPoint;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (root.equals(cgroupPath)) {
|
||||
path = mountPoint;
|
||||
}
|
||||
else {
|
||||
if (cgroupPath.startsWith(root)) {
|
||||
if (cgroupPath.length() > root.length()) {
|
||||
String cgroupSubstr = cgroupPath.substring(root.length());
|
||||
path = mountPoint + cgroupSubstr;
|
||||
} else {
|
||||
// containers with cgroupns=host, default setting is _root==cgroup_path
|
||||
if (!cgroupPath.equals(root)) {
|
||||
if (!cgroupPath.equals("") && !cgroupPath.equals("/")) {
|
||||
// When moved to a subgroup, between subgroups, the path suffix will change.
|
||||
Path cgp = Path.of(cgroupPath);
|
||||
int nameCount = cgp.getNameCount();
|
||||
for (int i=0; i < nameCount; i++) {
|
||||
Path dir = Path.of(mountPoint, cgp.toString());
|
||||
if (Files.isDirectory(dir)) {
|
||||
path = dir.toString();
|
||||
if (i > 0) {
|
||||
System.getLogger("jdk.internal.platform").log(Level.DEBUG, String.format(
|
||||
"Cgroup v1 path reduced to: %s.", cgp));
|
||||
}
|
||||
break;
|
||||
}
|
||||
int currentNameCount = cgp.getNameCount();
|
||||
cgp = (currentNameCount > 1) ? cgp.subpath(1, currentNameCount) : Path.of("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
#include "runtime/os.hpp"
|
||||
#include "cgroupSubsystem_linux.hpp"
|
||||
#include "cgroupUtil_linux.hpp"
|
||||
#include "cgroupV1Subsystem_linux.hpp"
|
||||
#include "cgroupV2Subsystem_linux.hpp"
|
||||
#include "unittest.hpp"
|
||||
@ -432,9 +433,16 @@ TEST(cgroupTest, set_cgroupv1_subsystem_path) {
|
||||
"/user.slice/user-1000.slice/user@1000.service", // cgroup_path
|
||||
"/sys/fs/cgroup/mem" // expected_path
|
||||
};
|
||||
int length = 2;
|
||||
TestCase container_moving_cgroup = {
|
||||
"/sys/fs/cgroup/cpu,cpuacct", // mount_path
|
||||
"/system.slice/garden.service/garden/good/2f57368b-0eda-4e52-64d8-af5c", // root_path
|
||||
"/system.slice/garden.service/garden/bad/2f57368b-0eda-4e52-64d8-af5c", // cgroup_path
|
||||
"/sys/fs/cgroup/cpu,cpuacct" // expected_path
|
||||
};
|
||||
int length = 3;
|
||||
TestCase* testCases[] = { &host,
|
||||
&container_engine };
|
||||
&container_engine,
|
||||
&container_moving_cgroup };
|
||||
for (int i = 0; i < length; i++) {
|
||||
CgroupV1Controller* ctrl = new CgroupV1Controller( (char*)testCases[i]->root_path,
|
||||
(char*)testCases[i]->mount_path,
|
||||
@ -444,6 +452,72 @@ TEST(cgroupTest, set_cgroupv1_subsystem_path) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cgroupTest, set_cgroupv1_subsystem_path_adjusted) {
|
||||
TestCase memory = {
|
||||
"/sys/fs/cgroup/memory", // mount_path
|
||||
"/", // root_path
|
||||
"../test1", // cgroup_path
|
||||
"/sys/fs/cgroup/memory" // expected_path
|
||||
};
|
||||
TestCase cpu = {
|
||||
"/sys/fs/cgroup/cpu", // mount_path
|
||||
"/", // root_path
|
||||
"../../test2", // cgroup_path
|
||||
"/sys/fs/cgroup/cpu" // expected_path
|
||||
};
|
||||
CgroupCpuController* ccc = new CgroupV1CpuController(CgroupV1Controller((char*)cpu.root_path,
|
||||
(char*)cpu.mount_path,
|
||||
true /* read-only mount */));
|
||||
ccc->set_subsystem_path((char*)cpu.cgroup_path);
|
||||
EXPECT_TRUE(ccc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupUtil::adjust_controller(ccc);
|
||||
ASSERT_STREQ(cpu.expected_path, ccc->subsystem_path());
|
||||
EXPECT_FALSE(ccc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupMemoryController* cmc = new CgroupV1MemoryController(CgroupV1Controller((char*)memory.root_path,
|
||||
(char*)memory.mount_path,
|
||||
true /* read-only mount */));
|
||||
cmc->set_subsystem_path((char*)memory.cgroup_path);
|
||||
EXPECT_TRUE(cmc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupUtil::adjust_controller(cmc);
|
||||
ASSERT_STREQ(memory.expected_path, cmc->subsystem_path());
|
||||
EXPECT_FALSE(cmc->needs_hierarchy_adjustment());
|
||||
}
|
||||
|
||||
TEST(cgroupTest, set_cgroupv2_subsystem_path_adjusted) {
|
||||
TestCase memory = {
|
||||
"/sys/fs/cgroup", // mount_path
|
||||
"/", // root_path
|
||||
"../test1", // cgroup_path
|
||||
"/sys/fs/cgroup" // expected_path
|
||||
};
|
||||
TestCase cpu = {
|
||||
"/sys/fs/cgroup", // mount_path
|
||||
"/", // root_path
|
||||
"../../test2", // cgroup_path
|
||||
"/sys/fs/cgroup" // expected_path
|
||||
};
|
||||
CgroupCpuController* ccc = new CgroupV2CpuController(CgroupV2Controller((char*)cpu.mount_path,
|
||||
(char*)cpu.cgroup_path,
|
||||
true /* read-only mount */));
|
||||
EXPECT_TRUE(ccc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupUtil::adjust_controller(ccc);
|
||||
ASSERT_STREQ(cpu.expected_path, ccc->subsystem_path());
|
||||
EXPECT_FALSE(ccc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupMemoryController* cmc = new CgroupV2MemoryController(CgroupV2Controller((char*)memory.mount_path,
|
||||
(char*)memory.cgroup_path,
|
||||
true /* read-only mount */));
|
||||
EXPECT_TRUE(cmc->needs_hierarchy_adjustment());
|
||||
|
||||
CgroupUtil::adjust_controller(cmc);
|
||||
ASSERT_STREQ(memory.expected_path, cmc->subsystem_path());
|
||||
EXPECT_FALSE(cmc->needs_hierarchy_adjustment());
|
||||
}
|
||||
|
||||
TEST(cgroupTest, set_cgroupv2_subsystem_path) {
|
||||
TestCase at_mount_root = {
|
||||
"/sys/fs/cgroup", // mount_path
|
||||
|
||||
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2025, BELLSOFT. 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.
|
||||
*/
|
||||
|
||||
import jdk.test.lib.containers.docker.Common;
|
||||
import jdk.test.lib.containers.docker.DockerTestUtils;
|
||||
import jdk.test.lib.containers.docker.DockerRunOptions;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.internal.platform.Metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jtreg.SkippedException;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8343191
|
||||
* @requires os.family == "linux"
|
||||
* @modules java.base/jdk.internal.platform
|
||||
* @library /test/lib
|
||||
* @build jdk.test.whitebox.WhiteBox
|
||||
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
|
||||
* @run main TestMemoryWithSubgroups
|
||||
*/
|
||||
public class TestMemoryWithSubgroups {
|
||||
|
||||
private static final String imageName = Common.imageName("subgroup");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Metrics metrics = Metrics.systemMetrics();
|
||||
if (metrics == null) {
|
||||
System.out.println("Cgroup not configured.");
|
||||
return;
|
||||
}
|
||||
if (!DockerTestUtils.canTestDocker()) {
|
||||
System.out.println("Unable to run docker tests.");
|
||||
return;
|
||||
}
|
||||
Common.prepareWhiteBox();
|
||||
DockerTestUtils.buildJdkContainerImage(imageName);
|
||||
|
||||
if ("cgroupv1".equals(metrics.getProvider())) {
|
||||
try {
|
||||
testMemoryLimitSubgroupV1("200m", "100m", "104857600", false);
|
||||
testMemoryLimitSubgroupV1("1g", "500m", "524288000", false);
|
||||
testMemoryLimitSubgroupV1("200m", "100m", "104857600", true);
|
||||
testMemoryLimitSubgroupV1("1g", "500m", "524288000", true);
|
||||
} finally {
|
||||
DockerTestUtils.removeDockerImage(imageName);
|
||||
}
|
||||
} else if ("cgroupv2".equals(metrics.getProvider())) {
|
||||
try {
|
||||
testMemoryLimitSubgroupV2("200m", "100m", "104857600", false);
|
||||
testMemoryLimitSubgroupV2("1g", "500m", "524288000", false);
|
||||
testMemoryLimitSubgroupV2("200m", "100m", "104857600", true);
|
||||
testMemoryLimitSubgroupV2("1g", "500m", "524288000", true);
|
||||
} finally {
|
||||
DockerTestUtils.removeDockerImage(imageName);
|
||||
}
|
||||
} else {
|
||||
throw new SkippedException("Metrics are from neither cgroup v1 nor v2, skipped for now.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMemoryLimitSubgroupV1(String containerMemorySize, String valueToSet, String expectedValue, boolean privateNamespace)
|
||||
throws Exception {
|
||||
|
||||
Common.logNewTestCase("Cgroup V1 subgroup memory limit: " + valueToSet);
|
||||
|
||||
DockerRunOptions opts = new DockerRunOptions(imageName, "sh", "-c");
|
||||
opts.javaOpts = new ArrayList<>();
|
||||
opts.appendTestJavaOptions = false;
|
||||
opts.addDockerOpts("--privileged")
|
||||
.addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
|
||||
.addDockerOpts("--memory", containerMemorySize);
|
||||
opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
|
||||
"echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.limit_in_bytes ; " +
|
||||
"echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
|
||||
"/jdk/bin/java -Xlog:os+container=trace -version");
|
||||
|
||||
Common.run(opts)
|
||||
.shouldMatch("Lowest limit was:.*" + expectedValue);
|
||||
}
|
||||
|
||||
private static void testMemoryLimitSubgroupV2(String containerMemorySize, String valueToSet, String expectedValue, boolean privateNamespace)
|
||||
throws Exception {
|
||||
|
||||
Common.logNewTestCase("Cgroup V2 subgroup memory limit: " + valueToSet);
|
||||
|
||||
DockerRunOptions opts = new DockerRunOptions(imageName, "sh", "-c");
|
||||
opts.javaOpts = new ArrayList<>();
|
||||
opts.appendTestJavaOptions = false;
|
||||
opts.addDockerOpts("--privileged")
|
||||
.addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
|
||||
.addDockerOpts("--memory", containerMemorySize);
|
||||
opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
|
||||
"echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
|
||||
"echo '+memory' > /sys/fs/cgroup/cgroup.subtree_control ; " +
|
||||
"echo '+memory' > /sys/fs/cgroup/memory/cgroup.subtree_control ; " +
|
||||
"echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.max ; " +
|
||||
"/jdk/bin/java -Xlog:os+container=trace -version");
|
||||
|
||||
Common.run(opts)
|
||||
.shouldMatch("Lowest limit was:.*" + expectedValue);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Red Hat, Inc.
|
||||
* Copyright (c) 2022, 2025, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -64,6 +64,9 @@ public class CgroupV1SubsystemControllerTest {
|
||||
assertEquals(expectedPath, ctrl.path());
|
||||
}
|
||||
|
||||
/*
|
||||
* Less common cases: Containers
|
||||
*/
|
||||
@Test
|
||||
public void testCgPathSubstring() {
|
||||
String root = "/foo/bar/baz";
|
||||
@ -71,8 +74,18 @@ public class CgroupV1SubsystemControllerTest {
|
||||
CgroupV1SubsystemController ctrl = new CgroupV1SubsystemController(root, mountPoint);
|
||||
String cgroupPath = "/foo/bar/baz/some";
|
||||
ctrl.setPath(cgroupPath);
|
||||
String expectedPath = mountPoint + "/some";
|
||||
String expectedPath = mountPoint;
|
||||
assertEquals(expectedPath, ctrl.path());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCgPathToMovedPath() {
|
||||
String root = "/system.slice/garden.service/garden/good/2f57368b-0eda-4e52-64d8-af5c";
|
||||
String mountPoint = "/sys/fs/cgroup/cpu,cpuacct";
|
||||
CgroupV1SubsystemController ctrl = new CgroupV1SubsystemController(root, mountPoint);
|
||||
String cgroupPath = "/system.slice/garden.service/garden/bad/2f57368b-0eda-4e52-64d8-af5c";
|
||||
ctrl.setPath(cgroupPath);
|
||||
String expectedPath = mountPoint;
|
||||
assertEquals(expectedPath, ctrl.path());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2022, Red Hat Inc.
|
||||
* Copyright (c) 2020, 2025, Red Hat Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -44,6 +44,7 @@ import jdk.internal.platform.CgroupSubsystemFactory;
|
||||
import jdk.internal.platform.CgroupSubsystemFactory.CgroupTypeResult;
|
||||
import jdk.internal.platform.CgroupV1MetricsImpl;
|
||||
import jdk.internal.platform.cgroupv1.CgroupV1Subsystem;
|
||||
import jdk.internal.platform.cgroupv1.CgroupV1SubsystemController;
|
||||
import jdk.internal.platform.Metrics;
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.util.FileUtils;
|
||||
@ -75,8 +76,10 @@ public class TestCgroupSubsystemFactory {
|
||||
private Path cgroupv1MntInfoDoubleControllers;
|
||||
private Path cgroupv1MntInfoDoubleControllers2;
|
||||
private Path cgroupv1MntInfoColonsHierarchy;
|
||||
private Path cgroupv1MntInfoNonTrivialRoot;
|
||||
private Path cgroupv1SelfCgroup;
|
||||
private Path cgroupv1SelfColons;
|
||||
private Path cgroupv1SelfNonTrivialRoot;
|
||||
private Path cgroupv2SelfCgroup;
|
||||
private Path cgroupv1SelfCgroupJoinCtrl;
|
||||
private Path cgroupv1CgroupsOnlyCPUCtrl;
|
||||
@ -175,6 +178,7 @@ public class TestCgroupSubsystemFactory {
|
||||
"42 30 0:38 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:14 - cgroup none rw,seclabel,cpuset\n" +
|
||||
"43 30 0:39 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:15 - cgroup none rw,seclabel,blkio\n" +
|
||||
"44 30 0:40 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:16 - cgroup none rw,seclabel,freezer\n";
|
||||
private String mntInfoNonTrivialRoot = "2207 2196 0:43 /system.slice/garden.service/garden/good/2f57368b-0eda-4e52-64d8-af5c /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpu,cpuacct\n";
|
||||
private String cgroupsNonZeroHierarchy =
|
||||
"#subsys_name hierarchy num_cgroups enabled\n" +
|
||||
"cpuset 9 1 1\n" +
|
||||
@ -230,6 +234,7 @@ public class TestCgroupSubsystemFactory {
|
||||
"2:cpu,cpuacct:/\n" +
|
||||
"1:name=systemd:/user.slice/user-1000.slice/user@1000.service/apps.slice/apps-org.gnome.Terminal.slice/vte-spawn-3c00b338-5b65-439f-8e97-135e183d135d.scope\n" +
|
||||
"0::/user.slice/user-1000.slice/user@1000.service/apps.slice/apps-org.gnome.Terminal.slice/vte-spawn-3c00b338-5b65-439f-8e97-135e183d135d.scope\n";
|
||||
private String cgroupv1SelfNTRoot = "11:cpu,cpuacct:/system.slice/garden.service/garden/bad/2f57368b-0eda-4e52-64d8-af5c\n";
|
||||
private String cgroupv2SelfCgroupContent = "0::/user.slice/user-1000.slice/session-2.scope";
|
||||
|
||||
// We have a mix of V1 and V2 controllers, but none of the V1 controllers
|
||||
@ -294,12 +299,18 @@ public class TestCgroupSubsystemFactory {
|
||||
cgroupv1MntInfoColonsHierarchy = Paths.get(existingDirectory.toString(), "mountinfo_colons");
|
||||
Files.writeString(cgroupv1MntInfoColonsHierarchy, mntInfoColons);
|
||||
|
||||
cgroupv1MntInfoNonTrivialRoot = Paths.get(existingDirectory.toString(), "mountinfo_nt_root");
|
||||
Files.writeString(cgroupv1MntInfoNonTrivialRoot, mntInfoNonTrivialRoot);
|
||||
|
||||
cgroupv1SelfCgroup = Paths.get(existingDirectory.toString(), "self_cgroup_cgv1");
|
||||
Files.writeString(cgroupv1SelfCgroup, cgroupv1SelfCgroupContent);
|
||||
|
||||
cgroupv1SelfColons = Paths.get(existingDirectory.toString(), "self_colons_cgv1");
|
||||
Files.writeString(cgroupv1SelfColons, cgroupv1SelfColonsContent);
|
||||
|
||||
cgroupv1SelfNonTrivialRoot = Paths.get(existingDirectory.toString(), "self_nt_root_cgv1");
|
||||
Files.writeString(cgroupv1SelfNonTrivialRoot, cgroupv1SelfNTRoot);
|
||||
|
||||
cgroupv2SelfCgroup = Paths.get(existingDirectory.toString(), "self_cgroup_cgv2");
|
||||
Files.writeString(cgroupv2SelfCgroup, cgroupv2SelfCgroupContent);
|
||||
|
||||
@ -449,6 +460,27 @@ public class TestCgroupSubsystemFactory {
|
||||
assertEquals(memoryInfo.getMountRoot(), memoryInfo.getCgroupPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMountPrefixCgroupsV1() throws IOException {
|
||||
String cgroups = cgroupv1CgInfoNonZeroHierarchy.toString();
|
||||
String mountInfo = cgroupv1MntInfoNonTrivialRoot.toString();
|
||||
String selfCgroup = cgroupv1SelfNonTrivialRoot.toString();
|
||||
Optional<CgroupTypeResult> result = CgroupSubsystemFactory.determineType(mountInfo, cgroups, selfCgroup);
|
||||
|
||||
assertTrue("Expected non-empty cgroup result", result.isPresent());
|
||||
CgroupTypeResult res = result.get();
|
||||
CgroupInfo cpuInfo = res.getInfos().get("cpu");
|
||||
assertEquals(cpuInfo.getCgroupPath(), "/system.slice/garden.service/garden/bad/2f57368b-0eda-4e52-64d8-af5c");
|
||||
String expectedMountPoint = "/sys/fs/cgroup/cpu,cpuacct";
|
||||
assertEquals(expectedMountPoint, cpuInfo.getMountPoint());
|
||||
CgroupV1SubsystemController cgroupv1MemoryController = new CgroupV1SubsystemController(cpuInfo.getMountRoot(), cpuInfo.getMountPoint());
|
||||
cgroupv1MemoryController.setPath(cpuInfo.getCgroupPath());
|
||||
String actualPath = cgroupv1MemoryController.path();
|
||||
assertNotNull(actualPath);
|
||||
String expectedPath = expectedMountPoint;
|
||||
assertEquals("Should be equal to the mount point path", expectedPath, actualPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroHierarchyCgroupsV1() throws IOException {
|
||||
String cgroups = cgroupv1CgInfoZeroHierarchy.toString();
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2025, BELLSOFT. 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.
|
||||
*/
|
||||
|
||||
import jdk.internal.platform.Metrics;
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.containers.docker.Common;
|
||||
import jdk.test.lib.containers.docker.DockerfileConfig;
|
||||
import jdk.test.lib.containers.docker.DockerRunOptions;
|
||||
import jdk.test.lib.containers.docker.DockerTestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jtreg.SkippedException;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8343191
|
||||
* @key cgroups
|
||||
* @summary Cgroup v1 subsystem fails to set subsystem path
|
||||
* @requires container.support
|
||||
* @library /test/lib
|
||||
* @modules java.base/jdk.internal.platform
|
||||
* @build MetricsMemoryTester
|
||||
* @run main TestDockerMemoryMetricsSubgroup
|
||||
*/
|
||||
|
||||
public class TestDockerMemoryMetricsSubgroup {
|
||||
private static final String imageName =
|
||||
DockerfileConfig.getBaseImageName() + ":" +
|
||||
DockerfileConfig.getBaseImageVersion();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Metrics metrics = Metrics.systemMetrics();
|
||||
if (metrics == null) {
|
||||
System.out.println("Cgroup not configured.");
|
||||
return;
|
||||
}
|
||||
if (!DockerTestUtils.canTestDocker()) {
|
||||
System.out.println("Unable to run docker tests.");
|
||||
return;
|
||||
}
|
||||
if ("cgroupv1".equals(metrics.getProvider())) {
|
||||
testMemoryLimitSubgroupV1("200m", "400m", false);
|
||||
testMemoryLimitSubgroupV1("500m", "1G", false);
|
||||
testMemoryLimitSubgroupV1("200m", "400m", true);
|
||||
testMemoryLimitSubgroupV1("500m", "1G", true);
|
||||
} else if ("cgroupv2".equals(metrics.getProvider())) {
|
||||
testMemoryLimitSubgroupV2("200m", "400m", false);
|
||||
testMemoryLimitSubgroupV2("500m", "1G", false);
|
||||
testMemoryLimitSubgroupV2("200m", "400m", true);
|
||||
testMemoryLimitSubgroupV2("500m", "1G", true);
|
||||
} else {
|
||||
throw new SkippedException("Metrics are from neither cgroup v1 nor v2, skipped for now.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMemoryLimitSubgroupV1(String innerSize, String outerGroupMemorySize, boolean privateNamespace) throws Exception {
|
||||
Common.logNewTestCase("testMemoryLimitSubgroup, innerSize = " + innerSize);
|
||||
DockerRunOptions opts =
|
||||
new DockerRunOptions(imageName, "sh", "-c");
|
||||
opts.javaOpts = new ArrayList<>();
|
||||
opts.appendTestJavaOptions = false;
|
||||
opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
|
||||
.addDockerOpts("--volume", Utils.TEST_JDK + ":/jdk")
|
||||
.addDockerOpts("--privileged")
|
||||
.addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
|
||||
.addDockerOpts("--memory", outerGroupMemorySize);
|
||||
opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
|
||||
"echo " + innerSize + " > /sys/fs/cgroup/memory/test/memory.limit_in_bytes ; " +
|
||||
"echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
|
||||
"/jdk/bin/java -cp /test-classes/ " +
|
||||
"--add-exports java.base/jdk.internal.platform=ALL-UNNAMED " +
|
||||
"MetricsMemoryTester memory " + innerSize);
|
||||
|
||||
DockerTestUtils.dockerRunJava(opts).shouldHaveExitValue(0).shouldContain("TEST PASSED!!!");
|
||||
}
|
||||
|
||||
private static void testMemoryLimitSubgroupV2(String innerSize, String outerGroupMemorySize, boolean privateNamespace) throws Exception {
|
||||
Common.logNewTestCase("testMemoryLimitSubgroup, innerSize = " + innerSize);
|
||||
DockerRunOptions opts =
|
||||
new DockerRunOptions(imageName, "sh", "-c");
|
||||
opts.javaOpts = new ArrayList<>();
|
||||
opts.appendTestJavaOptions = false;
|
||||
opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
|
||||
.addDockerOpts("--volume", Utils.TEST_JDK + ":/jdk")
|
||||
.addDockerOpts("--privileged")
|
||||
.addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
|
||||
.addDockerOpts("--memory", outerGroupMemorySize);
|
||||
opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
|
||||
"echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
|
||||
"echo '+memory' > /sys/fs/cgroup/cgroup.subtree_control ; " +
|
||||
"echo '+memory' > /sys/fs/cgroup/memory/cgroup.subtree_control ; " +
|
||||
"echo " + innerSize + " > /sys/fs/cgroup/memory/test/memory.max ; " +
|
||||
"/jdk/bin/java -cp /test-classes/ " +
|
||||
"--add-exports java.base/jdk.internal.platform=ALL-UNNAMED " +
|
||||
"MetricsMemoryTester memory " + innerSize);
|
||||
|
||||
DockerTestUtils.dockerRunJava(opts).shouldHaveExitValue(0).shouldContain("TEST PASSED!!!");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user