jdk-24/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java
2021-09-13 17:36:23 +00:00

257 lines
9.8 KiB
Java

/*
* Copyright (c) 2019, 2021, 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
* 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.
*/
/*
* @test
* @summary Test JCMD with side car pattern.
* Sidecar is a common pattern used in the cloud environments for monitoring
* and other uses. In side car pattern the main application/service container
* is paired with a sidecar container by sharing certain aspects of container
* namespace such as PID namespace, specific sub-directories, IPC and more.
* @requires docker.support
* @requires vm.flagless
* @modules java.base/jdk.internal.misc
* java.management
* jdk.jartool/sun.tools.jar
* @library /test/lib
* @build EventGeneratorLoop
* @run driver TestJcmdWithSideCar
*/
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import jdk.test.lib.Container;
import jdk.test.lib.Utils;
import jdk.test.lib.containers.docker.Common;
import jdk.test.lib.containers.docker.DockerRunOptions;
import jdk.test.lib.containers.docker.DockerTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class TestJcmdWithSideCar {
private static final String IMAGE_NAME = Common.imageName("jfr-jcmd");
private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds
private static final long TIME_TO_WAIT_FOR_MAIN_METHOD_START = 50 * 1000; // milliseconds
private static final String MAIN_CONTAINER_NAME = "test-container-main";
public static void main(String[] args) throws Exception {
if (!DockerTestUtils.canTestDocker()) {
return;
}
DockerTestUtils.buildJdkContainerImage(IMAGE_NAME);
try {
// Start the loop process in the "main" container, then run test cases
// using a sidecar container.
MainContainer mainContainer = new MainContainer();
mainContainer.start();
mainContainer.waitForMainMethodStart(TIME_TO_WAIT_FOR_MAIN_METHOD_START);
long mainProcPid = testCase01();
// Excluding the test case below until JDK-8228850 is fixed
// JDK-8228850: jhsdb jinfo fails with ClassCastException:
// s.j.h.oops.TypeArray cannot be cast to s.j.h.oops.Instance
// mainContainer.assertIsAlive();
// testCase02(mainProcPid);
mainContainer.assertIsAlive();
testCase03(mainProcPid);
mainContainer.waitForAndCheck(TIME_TO_RUN_MAIN_PROCESS * 1000);
} finally {
DockerTestUtils.removeDockerImage(IMAGE_NAME);
}
}
// Run "jcmd -l" in a sidecar container, find a target process.
private static long testCase01() throws Exception {
OutputAnalyzer out = runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l")
.shouldHaveExitValue(0)
.shouldContain("sun.tools.jcmd.JCmd");
long pid = findProcess(out, "EventGeneratorLoop");
if (pid == -1) {
throw new RuntimeException("Could not find specified process");
}
return pid;
}
// run jhsdb jinfo <PID> (jhsdb uses PTRACE)
private static void testCase02(long pid) throws Exception {
runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid)
.shouldHaveExitValue(0)
.shouldContain("Java System Properties")
.shouldContain("VM Flags");
}
// test jcmd with some commands (help, start JFR recording)
// JCMD will use signal mechanism and Unix Socket
private static void testCase03(long pid) throws Exception {
runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "help")
.shouldHaveExitValue(0)
.shouldContain("VM.version");
runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start")
.shouldHaveExitValue(0)
.shouldContain("Started recording");
}
// JCMD relies on the attach mechanism (com.sun.tools.attach),
// which in turn relies on JVMSTAT mechanism, which puts its mapped
// buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar
// we mount /tmp via --volumes-from from the main container.
private static OutputAnalyzer runSideCar(String mainContainerName, String whatToRun,
String... args) throws Exception {
List<String> cmd = new ArrayList<>();
String[] command = new String[] {
Container.ENGINE_COMMAND, "run",
"--tty=true", "--rm",
"--cap-add=SYS_PTRACE", "--sig-proxy=true",
"--pid=container:" + mainContainerName,
"--volumes-from", mainContainerName,
IMAGE_NAME, whatToRun
};
cmd.addAll(Arrays.asList(command));
cmd.addAll(Arrays.asList(args));
return DockerTestUtils.execute(cmd);
}
// Returns PID of a matching process, or -1 if not found.
private static long findProcess(OutputAnalyzer out, String name) throws Exception {
List<String> l = out.asLines()
.stream()
.filter(s -> s.contains(name))
.collect(Collectors.toList());
if (l.isEmpty()) {
return -1;
}
String psInfo = l.get(0);
System.out.println("findProcess(): psInfo: " + psInfo);
String pid = psInfo.substring(0, psInfo.indexOf(' '));
System.out.println("findProcess(): pid: " + pid);
return Long.parseLong(pid);
}
private static DockerRunOptions commonDockerOpts(String className) {
return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className)
.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
.addJavaOpts("-cp", "/test-classes/");
}
private static void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.out.println("InterruptedException" + e.getMessage());
}
}
static class MainContainer {
boolean mainMethodStarted;
Process p;
private Consumer<String> outputConsumer = s -> {
if (!mainMethodStarted && s.contains(EventGeneratorLoop.MAIN_METHOD_STARTED)) {
System.out.println("MainContainer: setting mainMethodStarted");
mainMethodStarted = true;
}
};
public Process start() throws Exception {
// start "main" container (the observee)
DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop");
opts.addDockerOpts("--cap-add=SYS_PTRACE")
.addDockerOpts("--name", MAIN_CONTAINER_NAME)
.addDockerOpts("--volume", "/tmp")
.addDockerOpts("--volume", Paths.get(".").toAbsolutePath() + ":/workdir/")
.addJavaOpts("-XX:+UsePerfData")
.addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS);
// avoid large Xmx
opts.appendTestJavaOptions = false;
List<String> cmd = DockerTestUtils.buildJavaCommand(opts);
ProcessBuilder pb = new ProcessBuilder(cmd);
p = ProcessTools.startProcess("main-container-process",
pb,
outputConsumer);
return p;
}
public void waitForMainMethodStart(long howLong) {
long expiration = System.currentTimeMillis() + howLong;
do {
if (mainMethodStarted) {
return;
}
sleep(200);
} while (System.currentTimeMillis() < expiration);
throw new RuntimeException("Timed out while waiting for main() to start");
}
public void assertIsAlive() throws Exception {
if (!p.isAlive()) {
throw new RuntimeException("Main container process stopped unexpectedly, exit value: "
+ p.exitValue());
}
}
public void waitFor(long timeout) throws Exception {
p.waitFor(timeout, TimeUnit.MILLISECONDS);
}
public void waitForAndCheck(long timeout) throws Exception {
int exitValue = -1;
int retryCount = 3;
do {
waitFor(timeout);
try {
exitValue = p.exitValue();
} catch(IllegalThreadStateException ex) {
System.out.println("IllegalThreadStateException occured when calling exitValue()");
retryCount--;
}
} while (exitValue == -1 && retryCount > 0);
if (exitValue != 0) {
throw new RuntimeException("DockerThread stopped unexpectedly, non-zero exit value is " + exitValue);
}
}
}
}