Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
* 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