Path: blob/master/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java
40948 views
/*1* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/222324/*25* @test26* @summary Test JCMD with side car pattern.27* Sidecar is a common pattern used in the cloud environments for monitoring28* and other uses. In side car pattern the main application/service container29* is paired with a sidecar container by sharing certain aspects of container30* namespace such as PID namespace, specific sub-directories, IPC and more.31* @requires docker.support32* @requires vm.flagless33* @modules java.base/jdk.internal.misc34* java.management35* jdk.jartool/sun.tools.jar36* @library /test/lib37* @build EventGeneratorLoop38* @run driver TestJcmdWithSideCar39*/40import java.nio.file.Paths;41import java.util.Arrays;42import java.util.ArrayList;43import java.util.List;44import java.util.concurrent.TimeUnit;45import java.util.function.Consumer;46import java.util.stream.Collectors;47import jdk.test.lib.Container;48import jdk.test.lib.Utils;49import jdk.test.lib.containers.docker.Common;50import jdk.test.lib.containers.docker.DockerRunOptions;51import jdk.test.lib.containers.docker.DockerTestUtils;52import jdk.test.lib.process.OutputAnalyzer;53import jdk.test.lib.process.ProcessTools;545556public class TestJcmdWithSideCar {57private static final String IMAGE_NAME = Common.imageName("jfr-jcmd");58private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds59private static final long TIME_TO_WAIT_FOR_MAIN_METHOD_START = 50 * 1000; // milliseconds60private static final String MAIN_CONTAINER_NAME = "test-container-main";6162public static void main(String[] args) throws Exception {63if (!DockerTestUtils.canTestDocker()) {64return;65}6667DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker");6869try {70// Start the loop process in the "main" container, then run test cases71// using a sidecar container.72MainContainer mainContainer = new MainContainer();73mainContainer.start();74mainContainer.waitForMainMethodStart(TIME_TO_WAIT_FOR_MAIN_METHOD_START);7576long mainProcPid = testCase01();7778// Excluding the test case below until JDK-8228850 is fixed79// JDK-8228850: jhsdb jinfo fails with ClassCastException:80// s.j.h.oops.TypeArray cannot be cast to s.j.h.oops.Instance81// mainContainer.assertIsAlive();82// testCase02(mainProcPid);8384// JCMD does not work in sidecar configuration, except for "jcmd -l".85// Including this test case to assist in reproduction of the problem.86// mainContainer.assertIsAlive();87// testCase03(mainProcPid);8889mainContainer.waitForAndCheck(TIME_TO_RUN_MAIN_PROCESS * 1000);90} finally {91DockerTestUtils.removeDockerImage(IMAGE_NAME);92}93}949596// Run "jcmd -l" in a sidecar container, find a target process.97private static long testCase01() throws Exception {98OutputAnalyzer out = runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l")99.shouldHaveExitValue(0)100.shouldContain("sun.tools.jcmd.JCmd");101long pid = findProcess(out, "EventGeneratorLoop");102if (pid == -1) {103throw new RuntimeException("Could not find specified process");104}105106return pid;107}108109// run jhsdb jinfo <PID> (jhsdb uses PTRACE)110private static void testCase02(long pid) throws Exception {111runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid)112.shouldHaveExitValue(0)113.shouldContain("Java System Properties")114.shouldContain("VM Flags");115}116117// test jcmd with some commands (help, start JFR recording)118// JCMD will use signal mechanism and Unix Socket119private static void testCase03(long pid) throws Exception {120runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "help")121.shouldHaveExitValue(0)122.shouldContain("VM.version");123runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start")124.shouldHaveExitValue(0)125.shouldContain("Started recording");126}127128129// JCMD relies on the attach mechanism (com.sun.tools.attach),130// which in turn relies on JVMSTAT mechanism, which puts its mapped131// buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar132// we mount /tmp via --volumes-from from the main container.133private static OutputAnalyzer runSideCar(String mainContainerName, String whatToRun,134String... args) throws Exception {135List<String> cmd = new ArrayList<>();136String[] command = new String[] {137Container.ENGINE_COMMAND, "run",138"--tty=true", "--rm",139"--cap-add=SYS_PTRACE", "--sig-proxy=true",140"--pid=container:" + mainContainerName,141"--volumes-from", mainContainerName,142IMAGE_NAME, whatToRun143};144145cmd.addAll(Arrays.asList(command));146cmd.addAll(Arrays.asList(args));147return DockerTestUtils.execute(cmd);148}149150// Returns PID of a matching process, or -1 if not found.151private static long findProcess(OutputAnalyzer out, String name) throws Exception {152List<String> l = out.asLines()153.stream()154.filter(s -> s.contains(name))155.collect(Collectors.toList());156if (l.isEmpty()) {157return -1;158}159String psInfo = l.get(0);160System.out.println("findProcess(): psInfo: " + psInfo);161String pid = psInfo.substring(0, psInfo.indexOf(' '));162System.out.println("findProcess(): pid: " + pid);163return Long.parseLong(pid);164}165166private static DockerRunOptions commonDockerOpts(String className) {167return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className)168.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")169.addJavaOpts("-cp", "/test-classes/");170}171172private static void sleep(long delay) {173try {174Thread.sleep(delay);175} catch (InterruptedException e) {176System.out.println("InterruptedException" + e.getMessage());177}178}179180181static class MainContainer {182boolean mainMethodStarted;183Process p;184185private Consumer<String> outputConsumer = s -> {186if (!mainMethodStarted && s.contains(EventGeneratorLoop.MAIN_METHOD_STARTED)) {187System.out.println("MainContainer: setting mainMethodStarted");188mainMethodStarted = true;189}190};191192public Process start() throws Exception {193// start "main" container (the observee)194DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop");195opts.addDockerOpts("--cap-add=SYS_PTRACE")196.addDockerOpts("--name", MAIN_CONTAINER_NAME)197.addDockerOpts("--volume", "/tmp")198.addDockerOpts("--volume", Paths.get(".").toAbsolutePath() + ":/workdir/")199.addJavaOpts("-XX:+UsePerfData")200.addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS);201// avoid large Xmx202opts.appendTestJavaOptions = false;203204List<String> cmd = DockerTestUtils.buildJavaCommand(opts);205ProcessBuilder pb = new ProcessBuilder(cmd);206p = ProcessTools.startProcess("main-container-process",207pb,208outputConsumer);209return p;210}211212public void waitForMainMethodStart(long howLong) {213long expiration = System.currentTimeMillis() + howLong;214215do {216if (mainMethodStarted) {217return;218}219sleep(200);220} while (System.currentTimeMillis() < expiration);221222throw new RuntimeException("Timed out while waiting for main() to start");223}224225public void assertIsAlive() throws Exception {226if (!p.isAlive()) {227throw new RuntimeException("Main container process stopped unexpectedly, exit value: "228+ p.exitValue());229}230}231232public void waitFor(long timeout) throws Exception {233p.waitFor(timeout, TimeUnit.MILLISECONDS);234}235236public void waitForAndCheck(long timeout) throws Exception {237int exitValue = -1;238int retryCount = 3;239240do {241waitFor(timeout);242try {243exitValue = p.exitValue();244} catch(IllegalThreadStateException ex) {245System.out.println("IllegalThreadStateException occured when calling exitValue()");246retryCount--;247}248} while (exitValue == -1 && retryCount > 0);249250if (exitValue != 0) {251throw new RuntimeException("DockerThread stopped unexpectedly, non-zero exit value is " + exitValue);252}253}254255}256257}258259260