Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static void main(String[] args) throws Exception {
throw new SkippedException("Docker is not supported on this host");
}

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

Expand Down Expand Up @@ -222,10 +222,4 @@ static File transferRecording(FlightRecorderMXBean bean, long streamId) throws E
}
}

static boolean isPodman() {
String[] parts = Container.ENGINE_COMMAND
.toLowerCase()
.split(File.pathSeparator);
return "podman".equals(parts[parts.length - 1]);
}
}
2 changes: 1 addition & 1 deletion test/hotspot/jtreg/containers/docker/TestJcmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class TestJcmd {
private static final String IMAGE_NAME = Common.imageName("jcmd");
private static final int TIME_TO_RUN_CONTAINER_PROCESS = (int) (10 * Utils.TIMEOUT_FACTOR); // seconds
private static final String CONTAINER_NAME = "test-container";
private static final boolean IS_PODMAN = Container.ENGINE_COMMAND.contains("podman");
private static final boolean IS_PODMAN = DockerTestUtils.isPodman();
private static final String ROOT_UID = "0";


Expand Down
117 changes: 117 additions & 0 deletions test/hotspot/jtreg/containers/docker/TestMemoryInvisibleParent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2025, IBM Corporation. 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.Container;
import jdk.test.lib.containers.docker.Common;
import jdk.test.lib.containers.docker.DockerTestUtils;
import jdk.test.lib.containers.docker.ContainerRuntimeVersionTestUtils;
import jdk.test.lib.containers.docker.DockerRunOptions;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.internal.platform.Metrics;

import java.nio.file.Path;
import java.nio.file.Files;
import java.util.ArrayList;

import jtreg.SkippedException;

/*
* @test
* @bug 8370966
* @requires os.family == "linux"
* @requires !vm.asan
* @modules java.base/jdk.internal.platform
* @library /test/lib
* @run main TestMemoryInvisibleParent
*/
public class TestMemoryInvisibleParent {
private static final String UNLIMITED = "-1";
private static final String imageName = Common.imageName("invisible-parent");

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;
}

ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();

if (DockerTestUtils.isRootless()) {
throw new SkippedException("Test skipped in rootless mode");
}
DockerTestUtils.buildJdkContainerImage(imageName);

if ("cgroupv1".equals(metrics.getProvider())) {
try {
testMemoryLimitHiddenParent("104857600", "104857600");
testMemoryLimitHiddenParent("209715200", "209715200");
} finally {
DockerTestUtils.removeDockerImage(imageName);
}
} else {
throw new SkippedException("cgroup v1 - only test! This is " + metrics.getProvider());
}
}

private static void testMemoryLimitHiddenParent(String valueToSet, String expectedValue)
throws Exception {

Common.logNewTestCase("Cgroup V1 hidden parent memory limit: " + valueToSet);

try {
String cgroupParent = setParentWithLimit(valueToSet);
DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "-version", "-Xlog:os+container=trace");
opts.appendTestJavaOptions = false;
if (DockerTestUtils.isPodman()) {
// Podman needs to run this test with engine option --cgroup-manager=cgroupfs
opts.addEngineOpts("--cgroup-manager", "cgroupfs");
}
opts.addDockerOpts("--cgroup-parent=/" + cgroupParent);
Common.run(opts)
.shouldContain("Hierarchical Memory Limit is: " + expectedValue);
} finally {
// Reset the parent memory limit to unlimited (-1)
setParentWithLimit(UNLIMITED);
}
}

private static String setParentWithLimit(String memLimit) throws Exception {
String cgroupParent = "hidden-parent-" + TestMemoryInvisibleParent.class.getSimpleName() + Runtime.version().feature();
Path sysFsMemory = Path.of("/", "sys", "fs", "cgroup", "memory");
Path cgroupParentPath = sysFsMemory.resolve(cgroupParent);
ProcessBuilder pb = new ProcessBuilder("mkdir", "-p", cgroupParentPath.toString());
OutputAnalyzer out = new OutputAnalyzer(pb.start())
.shouldHaveExitValue(0);
Path memoryLimitsFile = cgroupParentPath.resolve("memory.limit_in_bytes");
Files.writeString(memoryLimitsFile, memLimit);
System.out.println("Cgroup parent is: /" + cgroupParentPath.getFileName() +
" at " + sysFsMemory.toString());
return cgroupParent;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,6 @@
public class TestMemoryWithSubgroups {
private static final String imageName = Common.imageName("subgroup");

static String getEngineInfo(String format) throws Exception {
return DockerTestUtils.execute(Container.ENGINE_COMMAND, "info", "-f", format)
.getStdout();
}

static boolean isRootless() throws Exception {
// Docker and Podman have different INFO structures.
// The node path for Podman is .Host.Security.Rootless, that also holds for
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
}

public static void main(String[] args) throws Exception {
Metrics metrics = Metrics.systemMetrics();
if (metrics == null) {
Expand All @@ -72,7 +59,7 @@ public static void main(String[] args) throws Exception {

ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();

if (isRootless()) {
if (DockerTestUtils.isRootless()) {
throw new SkippedException("Test skipped in rootless mode");
}
Common.prepareWhiteBox();
Expand Down
2 changes: 1 addition & 1 deletion test/hotspot/jtreg/containers/docker/TestPids.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,6 @@ public class TestDockerMemoryMetricsSubgroup {
DockerfileConfig.getBaseImageName() + ":" +
DockerfileConfig.getBaseImageVersion();

static String getEngineInfo(String format) throws Exception {
return DockerTestUtils.execute(Container.ENGINE_COMMAND, "info", "-f", format)
.getStdout();
}

static boolean isRootless() throws Exception {
// Docker and Podman have different INFO structures.
// The node path for Podman is .Host.Security.Rootless, that also holds for
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
}

public static void main(String[] args) throws Exception {
Metrics metrics = Metrics.systemMetrics();
if (metrics == null) {
Expand All @@ -78,7 +65,7 @@ public static void main(String[] args) throws Exception {

ContainerRuntimeVersionTestUtils.checkContainerVersionSupported();

if (isRootless()) {
if (DockerTestUtils.isRootless()) {
throw new SkippedException("Test skipped in rootless mode");
}

Expand Down
6 changes: 6 additions & 0 deletions test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
// in test environment.
public class DockerRunOptions {
public String imageNameAndTag;
public ArrayList<String> engineOpts = new ArrayList<>();
public ArrayList<String> dockerOpts = new ArrayList<>();
public String command; // normally a full path to java
public ArrayList<String> javaOpts = new ArrayList<>();
Expand Down Expand Up @@ -70,6 +71,11 @@ public final DockerRunOptions addDockerOpts(String... opts) {
return this;
}

public final DockerRunOptions addEngineOpts(String... opts) {
Collections.addAll(engineOpts, opts);
return this;
}

public final DockerRunOptions addJavaOpts(String... opts) {
Collections.addAll(javaOpts, opts);
return this;
Expand Down
31 changes: 31 additions & 0 deletions test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ public static boolean isDockerEngineAvailable() throws Exception {
return isDockerEngineAvailable;
}

/**
* Checks if the actual engine command is podman.
*
* @return {@code true} if engine is podman. {@code false} otherwise.
*/
public static boolean isPodman() {
return Container.ENGINE_COMMAND.contains("podman");
}

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

private static String getEngineInfo(String format) throws Exception {
return execute(Container.ENGINE_COMMAND, "info", "-f", format).getStdout();
}

/**
* Determine if the engine is running in root-less mode.
*
* @return {@code true} when running root-less (podman or docker). {@code false}
* otherwise.
*
* @throws Exception
*/
public static boolean isRootless() throws Exception {
// Docker and Podman have different INFO structures.
// The node path for Podman is .Host.Security.Rootless, that also holds for
// Podman emulating Docker CLI. The node path for Docker is .SecurityOptions.
return (getEngineInfo("{{.Host.Security.Rootless}}").contains("true") ||
getEngineInfo("{{.SecurityOptions}}").contains("name=rootless"));
}

/**
* Build a container image that contains JDK under test.
* The jdk will be placed under the "/jdk/" folder inside the image/container file system.
Expand Down Expand Up @@ -202,6 +230,9 @@ private static void buildImage(String imageName, Path buildDir) throws Exception
*/
public static List<String> buildJavaCommand(DockerRunOptions opts) throws Exception {
List<String> cmd = buildContainerCommand();
if (!opts.engineOpts.isEmpty()) {
cmd.addAll(opts.engineOpts);
}
cmd.add("run");
if (opts.tty)
cmd.add("--tty=true");
Expand Down