Skip to content

Commit 2910032

Browse files
committed
8370966: Create regression test for the hierarchical memory limit fix in JDK-8370572
Reviewed-by: shade, syan
1 parent 76a1109 commit 2910032

File tree

8 files changed

+159
-37
lines changed

8 files changed

+159
-37
lines changed

test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public static void main(String[] args) throws Exception {
7777
throw new SkippedException("Docker is not supported on this host");
7878
}
7979

80-
if (isPodman() & !Platform.isRoot()) {
80+
if (DockerTestUtils.isPodman() & !Platform.isRoot()) {
8181
throw new SkippedException("test cannot be run under rootless podman configuration");
8282
}
8383

@@ -222,10 +222,4 @@ static File transferRecording(FlightRecorderMXBean bean, long streamId) throws E
222222
}
223223
}
224224

225-
static boolean isPodman() {
226-
String[] parts = Container.ENGINE_COMMAND
227-
.toLowerCase()
228-
.split(File.pathSeparator);
229-
return "podman".equals(parts[parts.length - 1]);
230-
}
231225
}

test/hotspot/jtreg/containers/docker/TestJcmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class TestJcmd {
5757
private static final String IMAGE_NAME = Common.imageName("jcmd");
5858
private static final int TIME_TO_RUN_CONTAINER_PROCESS = (int) (10 * Utils.TIMEOUT_FACTOR); // seconds
5959
private static final String CONTAINER_NAME = "test-container";
60-
private static final boolean IS_PODMAN = Container.ENGINE_COMMAND.contains("podman");
60+
private static final boolean IS_PODMAN = DockerTestUtils.isPodman();
6161
private static final String ROOT_UID = "0";
6262

6363

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (C) 2025, IBM Corporation. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.test.lib.Container;
25+
import jdk.test.lib.containers.docker.Common;
26+
import jdk.test.lib.containers.docker.DockerTestUtils;
27+
import jdk.test.lib.containers.docker.ContainerRuntimeVersionTestUtils;
28+
import jdk.test.lib.containers.docker.DockerRunOptions;
29+
import jdk.test.lib.process.OutputAnalyzer;
30+
import jdk.internal.platform.Metrics;
31+
32+
import java.nio.file.Path;
33+
import java.nio.file.Files;
34+
import java.util.ArrayList;
35+
36+
import jtreg.SkippedException;
37+
38+
/*
39+
* @test
40+
* @bug 8370966
41+
* @requires os.family == "linux"
42+
* @requires !vm.asan
43+
* @modules java.base/jdk.internal.platform
44+
* @library /test/lib
45+
* @run main TestMemoryInvisibleParent
46+
*/
47+
public class TestMemoryInvisibleParent {
48+
private static final String UNLIMITED = "-1";
49+
private static final String imageName = Common.imageName("invisible-parent");
50+
51+
public static void main(String[] args) throws Exception {
52+
Metrics metrics = Metrics.systemMetrics();
53+
if (metrics == null) {
54+
System.out.println("Cgroup not configured.");
55+
return;
56+
}
57+
if (!DockerTestUtils.canTestDocker()) {
58+
System.out.println("Unable to run docker tests.");
59+
return;
60+
}
61+
62+
ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();
63+
64+
if (DockerTestUtils.isRootless()) {
65+
throw new SkippedException("Test skipped in rootless mode");
66+
}
67+
DockerTestUtils.buildJdkContainerImage(imageName);
68+
69+
if ("cgroupv1".equals(metrics.getProvider())) {
70+
try {
71+
testMemoryLimitHiddenParent("104857600", "104857600");
72+
testMemoryLimitHiddenParent("209715200", "209715200");
73+
} finally {
74+
DockerTestUtils.removeDockerImage(imageName);
75+
}
76+
} else {
77+
throw new SkippedException("cgroup v1 - only test! This is " + metrics.getProvider());
78+
}
79+
}
80+
81+
private static void testMemoryLimitHiddenParent(String valueToSet, String expectedValue)
82+
throws Exception {
83+
84+
Common.logNewTestCase("Cgroup V1 hidden parent memory limit: " + valueToSet);
85+
86+
try {
87+
String cgroupParent = setParentWithLimit(valueToSet);
88+
DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "-version", "-Xlog:os+container=trace");
89+
opts.appendTestJavaOptions = false;
90+
if (DockerTestUtils.isPodman()) {
91+
// Podman needs to run this test with engine option --cgroup-manager=cgroupfs
92+
opts.addEngineOpts("--cgroup-manager", "cgroupfs");
93+
}
94+
opts.addDockerOpts("--cgroup-parent=/" + cgroupParent);
95+
Common.run(opts)
96+
.shouldContain("Hierarchical Memory Limit is: " + expectedValue);
97+
} finally {
98+
// Reset the parent memory limit to unlimited (-1)
99+
setParentWithLimit(UNLIMITED);
100+
}
101+
}
102+
103+
private static String setParentWithLimit(String memLimit) throws Exception {
104+
String cgroupParent = "hidden-parent-" + TestMemoryInvisibleParent.class.getSimpleName() + Runtime.version().feature();
105+
Path sysFsMemory = Path.of("/", "sys", "fs", "cgroup", "memory");
106+
Path cgroupParentPath = sysFsMemory.resolve(cgroupParent);
107+
ProcessBuilder pb = new ProcessBuilder("mkdir", "-p", cgroupParentPath.toString());
108+
OutputAnalyzer out = new OutputAnalyzer(pb.start())
109+
.shouldHaveExitValue(0);
110+
Path memoryLimitsFile = cgroupParentPath.resolve("memory.limit_in_bytes");
111+
Files.writeString(memoryLimitsFile, memLimit);
112+
System.out.println("Cgroup parent is: /" + cgroupParentPath.getFileName() +
113+
" at " + sysFsMemory.toString());
114+
return cgroupParent;
115+
}
116+
117+
}

test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,6 @@
4646
public class TestMemoryWithSubgroups {
4747
private static final String imageName = Common.imageName("subgroup");
4848

49-
static String getEngineInfo(String format) throws Exception {
50-
return DockerTestUtils.execute(Container.ENGINE_COMMAND, "info", "-f", format)
51-
.getStdout();
52-
}
53-
54-
static boolean isRootless() throws Exception {
55-
// Docker and Podman have different INFO structures.
56-
// The node path for Podman is .Host.Security.Rootless, that also holds for
57-
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
58-
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
59-
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
60-
}
61-
6249
public static void main(String[] args) throws Exception {
6350
Metrics metrics = Metrics.systemMetrics();
6451
if (metrics == null) {
@@ -72,7 +59,7 @@ public static void main(String[] args) throws Exception {
7259

7360
ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();
7461

75-
if (isRootless()) {
62+
if (DockerTestUtils.isRootless()) {
7663
throw new SkippedException("Test skipped in rootless mode");
7764
}
7865
Common.prepareWhiteBox();

test/hotspot/jtreg/containers/docker/TestPids.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
public class TestPids {
4949
private static final String imageName = Common.imageName("pids");
50-
private static final boolean IS_PODMAN = Container.ENGINE_COMMAND.contains("podman");
50+
private static final boolean IS_PODMAN = DockerTestUtils.isPodman();
5151
private static final int UNLIMITED_PIDS_PODMAN = 0;
5252
private static final int UNLIMITED_PIDS_DOCKER = -1;
5353

test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,6 @@ public class TestDockerMemoryMetricsSubgroup {
5252
DockerfileConfig.getBaseImageName() + ":" +
5353
DockerfileConfig.getBaseImageVersion();
5454

55-
static String getEngineInfo(String format) throws Exception {
56-
return DockerTestUtils.execute(Container.ENGINE_COMMAND, "info", "-f", format)
57-
.getStdout();
58-
}
59-
60-
static boolean isRootless() throws Exception {
61-
// Docker and Podman have different INFO structures.
62-
// The node path for Podman is .Host.Security.Rootless, that also holds for
63-
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
64-
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
65-
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
66-
}
67-
6855
public static void main(String[] args) throws Exception {
6956
Metrics metrics = Metrics.systemMetrics();
7057
if (metrics == null) {
@@ -78,7 +65,7 @@ public static void main(String[] args) throws Exception {
7865

7966
ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();
8067

81-
if (isRootless()) {
68+
if (DockerTestUtils.isRootless()) {
8269
throw new SkippedException("Test skipped in rootless mode");
8370
}
8471

test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
// in test environment.
3232
public class DockerRunOptions {
3333
public String imageNameAndTag;
34+
public ArrayList<String> engineOpts = new ArrayList<>();
3435
public ArrayList<String> dockerOpts = new ArrayList<>();
3536
public String command; // normally a full path to java
3637
public ArrayList<String> javaOpts = new ArrayList<>();
@@ -70,6 +71,11 @@ public final DockerRunOptions addDockerOpts(String... opts) {
7071
return this;
7172
}
7273

74+
public final DockerRunOptions addEngineOpts(String... opts) {
75+
Collections.addAll(engineOpts, opts);
76+
return this;
77+
}
78+
7379
public final DockerRunOptions addJavaOpts(String... opts) {
7480
Collections.addAll(javaOpts, opts);
7581
return this;

test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ public static boolean isDockerEngineAvailable() throws Exception {
8383
return isDockerEngineAvailable;
8484
}
8585

86+
/**
87+
* Checks if the actual engine command is podman.
88+
*
89+
* @return {@code true} if engine is podman. {@code false} otherwise.
90+
*/
91+
public static boolean isPodman() {
92+
return Container.ENGINE_COMMAND.contains("podman");
93+
}
8694

8795
/**
8896
* Convenience method, will check if docker engine is available and usable;
@@ -121,6 +129,26 @@ private static boolean isDockerEngineAvailableCheck() throws Exception {
121129
return true;
122130
}
123131

132+
private static String getEngineInfo(String format) throws Exception {
133+
return execute(Container.ENGINE_COMMAND, "info", "-f", format).getStdout();
134+
}
135+
136+
/**
137+
* Determine if the engine is running in root-less mode.
138+
*
139+
* @return {@code true} when running root-less (podman or docker). {@code false}
140+
* otherwise.
141+
*
142+
* @throws Exception
143+
*/
144+
public static boolean isRootless() throws Exception {
145+
// Docker and Podman have different INFO structures.
146+
// The node path for Podman is .Host.Security.Rootless, that also holds for
147+
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
148+
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
149+
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
150+
}
151+
124152
/**
125153
* Build a container image that contains JDK under test.
126154
* The jdk will be placed under the "/jdk/" folder inside the image/container file system.
@@ -202,6 +230,9 @@ private static void buildImage(String imageName, Path buildDir) throws Exception
202230
*/
203231
public static List<String> buildJavaCommand(DockerRunOptions opts) throws Exception {
204232
List<String> cmd = buildContainerCommand();
233+
if (!opts.engineOpts.isEmpty()) {
234+
cmd.addAll(opts.engineOpts);
235+
}
205236
cmd.add("run");
206237
if (opts.tty)
207238
cmd.add("--tty=true");

0 commit comments

Comments
 (0)