8333446: Add tests for hierarchical container support
Reviewed-by: mbaesken, zzambers
This commit is contained in:
parent
bfe7f9205b
commit
d9fdf69c34
@ -2497,6 +2497,13 @@ WB_ENTRY(jint, WB_ValidateCgroup(JNIEnv* env,
|
|||||||
return ret;
|
return ret;
|
||||||
WB_END
|
WB_END
|
||||||
|
|
||||||
|
// Available cpus of the host machine, Linux only.
|
||||||
|
// Used in container testing.
|
||||||
|
WB_ENTRY(jint, WB_HostCPUs(JNIEnv* env, jobject o))
|
||||||
|
LINUX_ONLY(return os::Linux::active_processor_count();)
|
||||||
|
return -1; // Not used/implemented on other platforms
|
||||||
|
WB_END
|
||||||
|
|
||||||
WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o))
|
WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o))
|
||||||
os::print_os_info(tty);
|
os::print_os_info(tty);
|
||||||
WB_END
|
WB_END
|
||||||
@ -2938,6 +2945,7 @@ static JNINativeMethod methods[] = {
|
|||||||
(void*)&WB_ValidateCgroup },
|
(void*)&WB_ValidateCgroup },
|
||||||
{CC"hostPhysicalMemory", CC"()J", (void*)&WB_HostPhysicalMemory },
|
{CC"hostPhysicalMemory", CC"()J", (void*)&WB_HostPhysicalMemory },
|
||||||
{CC"hostPhysicalSwap", CC"()J", (void*)&WB_HostPhysicalSwap },
|
{CC"hostPhysicalSwap", CC"()J", (void*)&WB_HostPhysicalSwap },
|
||||||
|
{CC"hostCPUs", CC"()I", (void*)&WB_HostCPUs },
|
||||||
{CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo },
|
{CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo },
|
||||||
{CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache },
|
{CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache },
|
||||||
{CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount },
|
{CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount },
|
||||||
|
@ -87,6 +87,7 @@ requires.properties= \
|
|||||||
vm.musl \
|
vm.musl \
|
||||||
vm.flagless \
|
vm.flagless \
|
||||||
docker.support \
|
docker.support \
|
||||||
|
systemd.support \
|
||||||
jdk.containerized
|
jdk.containerized
|
||||||
|
|
||||||
# Minimum jtreg version
|
# Minimum jtreg version
|
||||||
|
28
test/hotspot/jtreg/containers/systemd/HelloSystemd.java
Normal file
28
test/hotspot/jtreg/containers/systemd/HelloSystemd.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024, 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
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class HelloSystemd {
|
||||||
|
public static void main(String args[]) {
|
||||||
|
System.out.println("Hello Systemd");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024, 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
|
||||||
|
* 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.systemd.SystemdRunOptions;
|
||||||
|
import jdk.test.lib.containers.systemd.SystemdTestUtils;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.whitebox.WhiteBox;
|
||||||
|
import jtreg.SkippedException;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8322420 8217338
|
||||||
|
* @summary Memory/CPU awareness test for JDK-under-test inside a systemd slice.
|
||||||
|
* @requires systemd.support
|
||||||
|
* @library /test/lib
|
||||||
|
* @modules java.base/jdk.internal.platform
|
||||||
|
* @build HelloSystemd jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
|
||||||
|
* @run main/othervm -Xbootclasspath/a:whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SystemdMemoryAwarenessTest
|
||||||
|
*/
|
||||||
|
public class SystemdMemoryAwarenessTest {
|
||||||
|
|
||||||
|
private static final int MB = 1024 * 1024;
|
||||||
|
private static final WhiteBox wb = WhiteBox.getWhiteBox();
|
||||||
|
private static final String TEST_SLICE_NAME = SystemdMemoryAwarenessTest.class.getSimpleName() + "HS";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
testHelloSystemd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testHelloSystemd() throws Exception {
|
||||||
|
SystemdRunOptions opts = SystemdTestUtils.newOpts("HelloSystemd");
|
||||||
|
// 1 GB memory, but the limit in the lower hierarchy is 512M
|
||||||
|
opts.memoryLimit("1024M");
|
||||||
|
int expectedMemLimit = 512;
|
||||||
|
// expected detected limit we test for, 512MB
|
||||||
|
opts.sliceDMemoryLimit(String.format("%dM", expectedMemLimit));
|
||||||
|
int physicalCpus = wb.hostCPUs();
|
||||||
|
if (physicalCpus < 2) {
|
||||||
|
System.err.println("WARNING: host system only has " + physicalCpus + " cpus. Expected >= 2");
|
||||||
|
System.err.println("The active_processor_count assertion will trivially pass.");
|
||||||
|
}
|
||||||
|
// Use a CPU core limit of 1 for best coverage
|
||||||
|
int coreLimit = 1;
|
||||||
|
System.out.println("DEBUG: Running test with a CPU limit of " + coreLimit);
|
||||||
|
opts.cpuLimit(String.format("%d%%", coreLimit * 100));
|
||||||
|
opts.sliceName(TEST_SLICE_NAME);
|
||||||
|
|
||||||
|
OutputAnalyzer out = SystemdTestUtils.buildAndRunSystemdJava(opts);
|
||||||
|
out.shouldHaveExitValue(0)
|
||||||
|
.shouldContain("Hello Systemd")
|
||||||
|
.shouldContain(String.format("Memory Limit is: %d", (expectedMemLimit * MB)));
|
||||||
|
try {
|
||||||
|
out.shouldContain("OSContainer::active_processor_count: " + coreLimit);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// CPU delegation needs to be enabled when run as user on cg v2
|
||||||
|
if (SystemdTestUtils.RUN_AS_USER) {
|
||||||
|
String hint = "When run as user on cg v2 cpu delegation needs to be configured!";
|
||||||
|
throw new SkippedException(hint);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -101,6 +101,7 @@ requires.properties= \
|
|||||||
vm.jvmti \
|
vm.jvmti \
|
||||||
vm.cpu.features \
|
vm.cpu.features \
|
||||||
docker.support \
|
docker.support \
|
||||||
|
systemd.support \
|
||||||
release.implementor \
|
release.implementor \
|
||||||
jdk.containerized \
|
jdk.containerized \
|
||||||
jdk.foreign.linker
|
jdk.foreign.linker
|
||||||
|
@ -133,6 +133,7 @@ public class VMProps implements Callable<Map<String, String>> {
|
|||||||
map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
|
map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
|
||||||
map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
|
map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
|
||||||
map.put("docker.support", this::dockerSupport);
|
map.put("docker.support", this::dockerSupport);
|
||||||
|
map.put("systemd.support", this::systemdSupport);
|
||||||
map.put("vm.musl", this::isMusl);
|
map.put("vm.musl", this::isMusl);
|
||||||
map.put("release.implementor", this::implementor);
|
map.put("release.implementor", this::implementor);
|
||||||
map.put("jdk.containerized", this::jdkContainerized);
|
map.put("jdk.containerized", this::jdkContainerized);
|
||||||
@ -610,7 +611,7 @@ public class VMProps implements Callable<Map<String, String>> {
|
|||||||
|
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
try {
|
try {
|
||||||
isSupported = checkDockerSupport();
|
isSupported = checkProgramSupport("checkDockerSupport()", Container.ENGINE_COMMAND);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
isSupported = false;
|
isSupported = false;
|
||||||
}
|
}
|
||||||
@ -620,6 +621,27 @@ public class VMProps implements Callable<Map<String, String>> {
|
|||||||
return "" + isSupported;
|
return "" + isSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple check for systemd support
|
||||||
|
*
|
||||||
|
* @return true if systemd is supported in a given environment
|
||||||
|
*/
|
||||||
|
protected String systemdSupport() {
|
||||||
|
log("Entering systemdSupport()");
|
||||||
|
|
||||||
|
boolean isSupported = Platform.isLinux();
|
||||||
|
if (isSupported) {
|
||||||
|
try {
|
||||||
|
isSupported = checkProgramSupport("checkSystemdSupport()", "systemd-run");
|
||||||
|
} catch (Exception e) {
|
||||||
|
isSupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log("systemdSupport(): returning isSupported = " + isSupported);
|
||||||
|
return "" + isSupported;
|
||||||
|
}
|
||||||
|
|
||||||
// Configures process builder to redirect process stdout and stderr to a file.
|
// Configures process builder to redirect process stdout and stderr to a file.
|
||||||
// Returns file names for stdout and stderr.
|
// Returns file names for stdout and stderr.
|
||||||
private Map<String, String> redirectOutputToLogFile(String msg, ProcessBuilder pb, String fileNameBase) {
|
private Map<String, String> redirectOutputToLogFile(String msg, ProcessBuilder pb, String fileNameBase) {
|
||||||
@ -654,17 +676,17 @@ public class VMProps implements Callable<Map<String, String>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkDockerSupport() throws IOException, InterruptedException {
|
private boolean checkProgramSupport(String logString, String cmd) throws IOException, InterruptedException {
|
||||||
log("checkDockerSupport(): entering");
|
log(logString + ": entering");
|
||||||
ProcessBuilder pb = new ProcessBuilder("which", Container.ENGINE_COMMAND);
|
ProcessBuilder pb = new ProcessBuilder("which", cmd);
|
||||||
Map<String, String> logFileNames =
|
Map<String, String> logFileNames =
|
||||||
redirectOutputToLogFile("checkDockerSupport(): which " + Container.ENGINE_COMMAND,
|
redirectOutputToLogFile(logString + ": which " + cmd,
|
||||||
pb, "which-container");
|
pb, "which-cmd");
|
||||||
Process p = pb.start();
|
Process p = pb.start();
|
||||||
p.waitFor(10, TimeUnit.SECONDS);
|
p.waitFor(10, TimeUnit.SECONDS);
|
||||||
int exitValue = p.exitValue();
|
int exitValue = p.exitValue();
|
||||||
|
|
||||||
log(String.format("checkDockerSupport(): exitValue = %s, pid = %s", exitValue, p.pid()));
|
log(String.format("%s: exitValue = %s, pid = %s", logString, exitValue, p.pid()));
|
||||||
if (exitValue != 0) {
|
if (exitValue != 0) {
|
||||||
printLogfileContent(logFileNames);
|
printLogfileContent(logFileNames);
|
||||||
}
|
}
|
||||||
|
152
test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java
Normal file
152
test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024, 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
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.test.lib.containers.systemd;
|
||||||
|
|
||||||
|
import static jdk.test.lib.Asserts.assertNotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
|
||||||
|
// This class represents options for running java inside systemd slices
|
||||||
|
// in test environment.
|
||||||
|
public class SystemdRunOptions {
|
||||||
|
public ArrayList<String> javaOpts = new ArrayList<>();
|
||||||
|
public String classToRun; // class or "-version"
|
||||||
|
public ArrayList<String> classParams = new ArrayList<>();
|
||||||
|
public String memoryLimit; // used in slice for MemoryLimit property
|
||||||
|
public String cpuLimit; // used in slice for CPUQuota property
|
||||||
|
public String sliceName; // name of the slice (nests CPU in memory)
|
||||||
|
public String sliceDMemoryLimit; // used in jdk_internal.slice.d
|
||||||
|
public String sliceDCpuLimit; // used in jdk_internal.slice.d
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience constructor for most common use cases in testing.
|
||||||
|
* @param classToRun a class to run, or "-version"
|
||||||
|
* @param javaOpts java options to use
|
||||||
|
*
|
||||||
|
* @return Default docker run options
|
||||||
|
*/
|
||||||
|
public SystemdRunOptions(String classToRun, String... javaOpts) {
|
||||||
|
this.classToRun = classToRun;
|
||||||
|
Collections.addAll(this.javaOpts, javaOpts);
|
||||||
|
this.sliceName = defaultSliceName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String defaultSliceName() {
|
||||||
|
// Create a unique name for a systemd slice
|
||||||
|
// jtreg guarantees that test.name is unique among all concurrently executing
|
||||||
|
// tests. For example, if you have two test roots:
|
||||||
|
//
|
||||||
|
// $ find test -type f
|
||||||
|
// test/foo/TEST.ROOT
|
||||||
|
// test/foo/my/TestCase.java
|
||||||
|
// test/bar/TEST.ROOT
|
||||||
|
// test/bar/my/TestCase.java
|
||||||
|
// $ jtreg -concur:2 test/foo test/bar
|
||||||
|
//
|
||||||
|
// jtreg will first run all the tests under test/foo. When they are all finished, then
|
||||||
|
// jtreg will run all the tests under test/bar. So you will never have two concurrent
|
||||||
|
// test cases whose test.name is "my/TestCase.java"
|
||||||
|
String testname = System.getProperty("test.name");
|
||||||
|
assertNotNull(testname, "must be set by jtreg");
|
||||||
|
testname = testname.replace(".java", "");
|
||||||
|
testname = testname.replace("/", "_");
|
||||||
|
testname = testname.replace("\\", "_");
|
||||||
|
testname = testname.replace("-", "_");
|
||||||
|
|
||||||
|
// Example:
|
||||||
|
// Memory: "test_containers_systemd_TestMemoryAwareness"
|
||||||
|
// CPU: "test_containers_systemd_TestMemoryAwareness-cpu" => derived
|
||||||
|
return testname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The memory limit set with a .slice file in the systemd
|
||||||
|
* config directory.
|
||||||
|
*
|
||||||
|
* @param memLimit The memory limit to set (e.g. 1000M).
|
||||||
|
* @return The run options.
|
||||||
|
*/
|
||||||
|
public SystemdRunOptions memoryLimit(String memLimit) {
|
||||||
|
this.memoryLimit = memLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The memory limit to set in the top-level jdk_internal.slice.d
|
||||||
|
* systemd config directory.
|
||||||
|
*
|
||||||
|
* @param memoryLimit The memory limit to set.
|
||||||
|
* @return The run options.
|
||||||
|
*/
|
||||||
|
public SystemdRunOptions sliceDMemoryLimit(String memoryLimit) {
|
||||||
|
this.sliceDMemoryLimit = memoryLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CPU limit set with a .slice file in the systemd
|
||||||
|
* config directory.
|
||||||
|
*
|
||||||
|
* @param cpuLimit
|
||||||
|
* @return The run options.
|
||||||
|
*/
|
||||||
|
public SystemdRunOptions cpuLimit(String cpuLimit) {
|
||||||
|
this.cpuLimit = cpuLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Cpu limit set in the top-level jdk_internal.slice.d
|
||||||
|
* systemd config directory.
|
||||||
|
*
|
||||||
|
* @param cpuLimit The CPU limit to set to.
|
||||||
|
* @return The run options.
|
||||||
|
*/
|
||||||
|
public SystemdRunOptions sliceDCpuLimit(String cpuLimit) {
|
||||||
|
this.sliceDCpuLimit = cpuLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemdRunOptions sliceName(String name) {
|
||||||
|
this.sliceName = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemdRunOptions addJavaOpts(String... opts) {
|
||||||
|
Collections.addAll(javaOpts, opts);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemdRunOptions addClassOptions(String... opts) {
|
||||||
|
Collections.addAll(classParams,opts);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSliceDLimit() {
|
||||||
|
return this.sliceDMemoryLimit != null ||
|
||||||
|
this.sliceDCpuLimit != null;
|
||||||
|
}
|
||||||
|
}
|
350
test/lib/jdk/test/lib/containers/systemd/SystemdTestUtils.java
Normal file
350
test/lib/jdk/test/lib/containers/systemd/SystemdTestUtils.java
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024, 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
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.test.lib.containers.systemd;
|
||||||
|
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.internal.platform.Metrics;
|
||||||
|
import jdk.test.lib.Platform;
|
||||||
|
import jdk.test.lib.Utils;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.lib.util.FileUtils;
|
||||||
|
import jtreg.SkippedException;
|
||||||
|
|
||||||
|
public class SystemdTestUtils {
|
||||||
|
|
||||||
|
private static final String CGROUPS_PROVIDER = Metrics.systemMetrics().getProvider();
|
||||||
|
private static boolean CGROUPS_V2 = "cgroupv2".equals(CGROUPS_PROVIDER);
|
||||||
|
public static boolean RUN_AS_USER = !Platform.isRoot() && CGROUPS_V2;
|
||||||
|
private static final String SLICE_NAMESPACE_PREFIX = "jdk_internal";
|
||||||
|
private static final String SLICE_D_MEM_CONFIG_FILE = "memory-limit.conf";
|
||||||
|
private static final String SLICE_D_CPU_CONFIG_FILE = "cpu-limit.conf";
|
||||||
|
private static final String USER_HOME = System.getProperty("user.home");
|
||||||
|
private static final Path SYSTEMD_CONFIG_HOME_ROOT = Path.of("/", "etc", "systemd", "system");
|
||||||
|
private static final Path SYSTEMD_CONFIG_HOME_USER = Path.of(USER_HOME, ".config", "systemd", "user");
|
||||||
|
private static final Path SYSTEMD_CONFIG_HOME = Platform.isRoot() ? SYSTEMD_CONFIG_HOME_ROOT : SYSTEMD_CONFIG_HOME_USER;
|
||||||
|
|
||||||
|
// Specifies how many lines to copy from child STDOUT to main test output.
|
||||||
|
// Having too many lines in the main test output will result
|
||||||
|
// in JT harness trimming the output, and can lead to loss of useful
|
||||||
|
// diagnostic information.
|
||||||
|
private static final int MAX_LINES_TO_COPY_FOR_CHILD_STDOUT = 100;
|
||||||
|
|
||||||
|
public record ResultFiles(Path memory, Path cpu, Path sliceDotDDir) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create commonly used options with the class to be launched inside the
|
||||||
|
* systemd slice
|
||||||
|
*
|
||||||
|
* @param testClass The test class or {@code -version}
|
||||||
|
* @return The basic options.
|
||||||
|
*/
|
||||||
|
public static SystemdRunOptions newOpts(String testClass) {
|
||||||
|
return new SystemdRunOptions(testClass,
|
||||||
|
"-Xlog:os+container=trace",
|
||||||
|
"-cp",
|
||||||
|
Utils.TEST_CLASSES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run Java inside a systemd slice with specified parameters and options.
|
||||||
|
*
|
||||||
|
* @param opts The systemd slice options when running java
|
||||||
|
* @return An OutputAnalyzer of the output of the command than ran.
|
||||||
|
* @throws Exception If something went wrong.
|
||||||
|
* @throws SkippedException If the test cannot be run (i.e. non-root user
|
||||||
|
* on cgroups v1).
|
||||||
|
*/
|
||||||
|
public static OutputAnalyzer buildAndRunSystemdJava(SystemdRunOptions opts) throws Exception, SkippedException {
|
||||||
|
if (!Platform.isRoot() && !CGROUPS_V2) {
|
||||||
|
throw new SkippedException("Systemd tests require root on cgroup v1. Test skipped!");
|
||||||
|
}
|
||||||
|
ResultFiles files = SystemdTestUtils.buildSystemdSlices(opts);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SystemdTestUtils.systemdRunJava(opts);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (files.memory() != null) {
|
||||||
|
Files.delete(files.memory());
|
||||||
|
}
|
||||||
|
if (files.cpu() != null) {
|
||||||
|
Files.delete(files.cpu());
|
||||||
|
}
|
||||||
|
if (files.sliceDotDDir() != null) {
|
||||||
|
FileUtils.deleteFileTreeUnchecked(files.sliceDotDDir());
|
||||||
|
}
|
||||||
|
} catch (NoSuchFileException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputAnalyzer systemdRunJava(SystemdRunOptions opts) throws Exception {
|
||||||
|
return execute(buildJavaCommand(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create systemd slice files under /etc/systemd/system.
|
||||||
|
*
|
||||||
|
* The JDK will then run within that slice as provided by the SystemdRunOptions.
|
||||||
|
*
|
||||||
|
* @param runOpts The systemd slice options to use when running the test.
|
||||||
|
* @return The systemd slice files (for cleanup-purposes later).
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static ResultFiles buildSystemdSlices(SystemdRunOptions runOpts) throws Exception {
|
||||||
|
String sliceName = sliceName(runOpts);
|
||||||
|
String sliceNameCpu = sliceNameCpu(runOpts);
|
||||||
|
|
||||||
|
// Generate systemd slices for cpu/memory
|
||||||
|
String memorySliceContent = getMemorySlice(runOpts, sliceName);
|
||||||
|
String cpuSliceContent = getCpuSlice(runOpts, sliceName);
|
||||||
|
|
||||||
|
// Ensure base directory exists
|
||||||
|
Files.createDirectories(SYSTEMD_CONFIG_HOME);
|
||||||
|
Path sliceDotDDir = null;
|
||||||
|
if (runOpts.hasSliceDLimit()) {
|
||||||
|
String dirName = String.format("%s.slice.d", SLICE_NAMESPACE_PREFIX);
|
||||||
|
sliceDotDDir = SYSTEMD_CONFIG_HOME.resolve(Path.of(dirName));
|
||||||
|
Files.createDirectory(sliceDotDDir);
|
||||||
|
|
||||||
|
if (runOpts.sliceDMemoryLimit != null) {
|
||||||
|
Path memoryConfig = sliceDotDDir.resolve(Path.of(SLICE_D_MEM_CONFIG_FILE));
|
||||||
|
Files.writeString(memoryConfig, getMemoryDSliceContent(runOpts));
|
||||||
|
}
|
||||||
|
if (runOpts.sliceDCpuLimit != null) {
|
||||||
|
Path cpuConfig = sliceDotDDir.resolve(Path.of(SLICE_D_CPU_CONFIG_FILE));
|
||||||
|
Files.writeString(cpuConfig, getCPUDSliceContent(runOpts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Path memory, cpu;
|
||||||
|
try {
|
||||||
|
// memory slice
|
||||||
|
memory = SYSTEMD_CONFIG_HOME.resolve(Path.of(sliceFileName(sliceName)));
|
||||||
|
// cpu slice nested in memory
|
||||||
|
cpu = SYSTEMD_CONFIG_HOME.resolve(Path.of(sliceFileName(sliceNameCpu)));
|
||||||
|
Files.writeString(memory, memorySliceContent);
|
||||||
|
Files.writeString(cpu, cpuSliceContent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("Failed to write systemd slice files");
|
||||||
|
}
|
||||||
|
|
||||||
|
systemdDaemonReload(cpu);
|
||||||
|
|
||||||
|
return new ResultFiles(memory, cpu, sliceDotDDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sliceName(SystemdRunOptions runOpts) {
|
||||||
|
// Slice name may include '-' which is a hierarchical slice indicator.
|
||||||
|
// Replace '-' with '_' to avoid side-effects.
|
||||||
|
return SLICE_NAMESPACE_PREFIX + "-" + runOpts.sliceName.replace("-", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sliceNameCpu(SystemdRunOptions runOpts) {
|
||||||
|
String slice = sliceName(runOpts);
|
||||||
|
return String.format("%s-cpu", slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void systemdDaemonReload(Path cpu) throws Exception {
|
||||||
|
List<String> daemonReload = systemCtl();
|
||||||
|
daemonReload.add("daemon-reload");
|
||||||
|
|
||||||
|
if (execute(daemonReload).getExitValue() != 0) {
|
||||||
|
throw new AssertionError("Failed to reload systemd daemon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> systemCtl() {
|
||||||
|
return commandWithUser("systemctl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'baseCommand' or 'baseCommand --user' as list, depending on the cgroups
|
||||||
|
* version and running user.
|
||||||
|
*
|
||||||
|
* @return 'baseCommand' if we are the root user, 'systemctl --user' if
|
||||||
|
* the current user is non-root and we are on cgroups v2. Note:
|
||||||
|
* Cgroups v1 and non-root is not possible as tests are skipped then.
|
||||||
|
*/
|
||||||
|
private static List<String> commandWithUser(String baseCommand) {
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(baseCommand);
|
||||||
|
if (RUN_AS_USER) {
|
||||||
|
command.add("--user");
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCpuSlice(SystemdRunOptions runOpts, String sliceName) {
|
||||||
|
String basicSliceFormat = getBasicSliceFormat();
|
||||||
|
return String.format(basicSliceFormat, sliceName, getCPUSliceContent(runOpts));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCPUSliceContent(SystemdRunOptions runOpts) {
|
||||||
|
String format = basicCPUContentFormat();
|
||||||
|
return String.format(format, runOpts.cpuLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMemorySlice(SystemdRunOptions runOpts, String sliceName) {
|
||||||
|
String basicSliceFormat = getBasicSliceFormat();
|
||||||
|
return String.format(basicSliceFormat, sliceName, getMemorySliceContent(runOpts));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMemoryDSliceContent(SystemdRunOptions runOpts) {
|
||||||
|
String format = "[Slice]\n" + basicMemoryContentFormat();
|
||||||
|
return String.format(format, runOpts.sliceDMemoryLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCPUDSliceContent(SystemdRunOptions runOpts) {
|
||||||
|
String format = "[Slice]\n" + basicCPUContentFormat();
|
||||||
|
return String.format(format, runOpts.sliceDCpuLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String basicCPUContentFormat() {
|
||||||
|
return """
|
||||||
|
CPUAccounting=true
|
||||||
|
CPUQuota=%s
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String basicMemoryContentFormat() {
|
||||||
|
return """
|
||||||
|
MemoryAccounting=true
|
||||||
|
MemoryLimit=%s
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMemorySliceContent(SystemdRunOptions runOpts) {
|
||||||
|
String format = basicMemoryContentFormat();
|
||||||
|
|
||||||
|
return String.format(format, runOpts.memoryLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getBasicSliceFormat() {
|
||||||
|
return """
|
||||||
|
[Unit]
|
||||||
|
Description=OpenJDK Tests Slice for %s
|
||||||
|
Before=slices.target
|
||||||
|
|
||||||
|
[Slice]
|
||||||
|
%s
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sliceFileName(String sliceName) {
|
||||||
|
return String.format("%s.slice", sliceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the java command to run inside a systemd slice
|
||||||
|
*
|
||||||
|
* @param SystemdRunOptions options for running the systemd slice test
|
||||||
|
*
|
||||||
|
* @return command
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static List<String> buildJavaCommand(SystemdRunOptions opts) throws Exception {
|
||||||
|
// systemd-run [--user] --slice <slice-name>.slice --scope <java>
|
||||||
|
List<String> javaCmd = systemdRun();
|
||||||
|
javaCmd.add("--slice");
|
||||||
|
javaCmd.add(sliceFileName(sliceNameCpu(opts)));
|
||||||
|
javaCmd.add("--scope");
|
||||||
|
javaCmd.add(Path.of(Utils.TEST_JDK, "bin", "java").toString());
|
||||||
|
javaCmd.addAll(opts.javaOpts);
|
||||||
|
javaCmd.add(opts.classToRun);
|
||||||
|
javaCmd.addAll(opts.classParams);
|
||||||
|
return javaCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> systemdRun() {
|
||||||
|
return commandWithUser("systemd-run");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a specified command in a process, report diagnostic info.
|
||||||
|
*
|
||||||
|
* @param command to be executed
|
||||||
|
* @return The output from the process
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static OutputAnalyzer execute(List<String> command) throws Exception {
|
||||||
|
return execute(command.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a specified command in a process, report diagnostic info.
|
||||||
|
*
|
||||||
|
* @param command to be executed
|
||||||
|
* @return The output from the process
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static OutputAnalyzer execute(String... command) throws Exception {
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
|
System.out.println("[COMMAND]\n" + Utils.getCommandLine(pb));
|
||||||
|
|
||||||
|
Process p = pb.start();
|
||||||
|
long pid = p.pid();
|
||||||
|
OutputAnalyzer output = new OutputAnalyzer(p);
|
||||||
|
|
||||||
|
int max = MAX_LINES_TO_COPY_FOR_CHILD_STDOUT;
|
||||||
|
String stdout = output.getStdout();
|
||||||
|
String stdoutLimited = limitLines(stdout, max);
|
||||||
|
System.out.println("[STDERR]\n" + output.getStderr());
|
||||||
|
System.out.println("[STDOUT]\n" + stdoutLimited);
|
||||||
|
if (stdout != stdoutLimited) {
|
||||||
|
System.out.printf("Child process STDOUT is limited to %d lines\n",
|
||||||
|
max);
|
||||||
|
}
|
||||||
|
|
||||||
|
String stdoutLogFile = String.format("systemd-stdout-%d.log", pid);
|
||||||
|
writeOutputToFile(stdout, stdoutLogFile);
|
||||||
|
System.out.println("Full child process STDOUT was saved to " + stdoutLogFile);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeOutputToFile(String output, String fileName) throws Exception {
|
||||||
|
try (FileWriter fw = new FileWriter(fileName)) {
|
||||||
|
fw.write(output, 0, output.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String limitLines(String buffer, int nrOfLines) {
|
||||||
|
List<String> l = Arrays.asList(buffer.split("\\R"));
|
||||||
|
if (l.size() < nrOfLines) {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.join("\n", l.subList(0, nrOfLines));
|
||||||
|
}
|
||||||
|
}
|
@ -769,6 +769,7 @@ public class WhiteBox {
|
|||||||
public native void printOsInfo();
|
public native void printOsInfo();
|
||||||
public native long hostPhysicalMemory();
|
public native long hostPhysicalMemory();
|
||||||
public native long hostPhysicalSwap();
|
public native long hostPhysicalSwap();
|
||||||
|
public native int hostCPUs();
|
||||||
|
|
||||||
// Decoder
|
// Decoder
|
||||||
public native void disableElfSectionCache();
|
public native void disableElfSectionCache();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user