1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 /*
  26  * @test
  27  * @summary Test JCMD with containers.
  28  *          Specifically, start the test JVM inside a container, then use jcmd to find it and
  29  *          send a simple command to that JVM.
  30  * @requires docker.support
  31  * @library /test/lib
  32  * @modules java.base/jdk.internal.misc
  33  *          java.management
  34  *          jdk.jartool/sun.tools.jar
  35  *          jdk.security.auth
  36  * @build SimpleLoop
  37  * @run driver TestJcmd
  38  */
  39 import com.sun.security.auth.module.UnixSystem;
  40 import jdk.test.lib.JDKToolFinder;
  41 import jdk.test.lib.Utils;
  42 import jdk.test.lib.containers.docker.Common;
  43 import jdk.test.lib.containers.docker.DockerRunOptions;
  44 import jdk.test.lib.containers.docker.DockerTestUtils;
  45 import jdk.test.lib.process.OutputAnalyzer;
  46 
  47 
  48 public class TestJcmd {
  49     private static final String IMAGE_NAME = Common.imageName("jcmd");
  50     private static final int TIME_TO_RUN_CHILD_PROCESS = (int) (20 * Utils.TIMEOUT_FACTOR); // seconds
  51 
  52     public static void main(String[] args) throws Exception {
  53         if (!DockerTestUtils.canTestDocker()) {
  54             return;
  55         }
  56 
  57         // In order for jcmd to work, the USER name and UID of the observer
  58         // need to match the USERNAME/UID of the observed JVM process
  59         String additionalDockerFileContent =
  60             String.format("RUN useradd %s --uid %d \n", getCurrentUserName(), getCurrentUserId()) +
  61             String.format("USER %s \n", getCurrentUserName());
  62 
  63         DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest",
  64                                             "jdk-docker", additionalDockerFileContent);
  65 
  66         String name = "java-simple-loop";
  67         try {
  68             ProcessBuilder pb = containerCommand(name);
  69             Process p = pb.start();
  70 
  71             DockerTestUtils.waitForContainerToStart(name, 2000, 500, 10);
  72 
  73             // jcmd -l
  74             OutputAnalyzer jcmdOut = testJcmdList();
  75             long pid = Common.findPidFromJcmdOutput(jcmdOut, "SimpleLoop");
  76             System.out.println("PID = " + pid);
  77 
  78             // jcmd <pid> help
  79             // This test case currently fails due to JDK-8228343
  80             // testJcmdHelp(pid);
  81 
  82             p.waitFor();
  83             logProcess("container", pb, new OutputAnalyzer(p));
  84         } finally {
  85             DockerTestUtils.removeDockerImage(IMAGE_NAME);
  86         }
  87     }
  88 
  89     private static String getCurrentUserName() {
  90         String name = System.getProperty("user.name");
  91         System.out.println("getCurrentUserName(): returning " + name);
  92         return name;
  93     }
  94 
  95     private static long getCurrentUserId() {
  96         long uid = (new UnixSystem()).getUid();
  97         System.out.println("getCurrentUserId(): returning " + uid);
  98         return uid;
  99     }
 100 
 101     // Run "jcmd -l"
 102     private static OutputAnalyzer testJcmdList() throws Exception {
 103         ProcessBuilder pb = new ProcessBuilder(JDKToolFinder.getJDKTool("jcmd"), "-l");
 104         OutputAnalyzer out = new OutputAnalyzer(pb.start());
 105         logProcess("testJcmdList", pb, out);
 106 
 107         out.shouldHaveExitValue(0)
 108             .shouldContain("SimpleLoop");
 109 
 110         return out;
 111     }
 112 
 113     // run "jcmd <pid> help"
 114     private static void testJcmdHelp(long pid) throws Exception {
 115         ProcessBuilder pb = new ProcessBuilder(JDKToolFinder.getJDKTool("jcmd"), "" + pid, "help");
 116         OutputAnalyzer out = new OutputAnalyzer(pb.start());
 117         logProcess("testJcmdHelp", pb, out);
 118 
 119         out.shouldHaveExitValue(0)
 120             .shouldContain("VM.version");
 121     }
 122 
 123     private static void logProcess(String logToken, ProcessBuilder pb, OutputAnalyzer out) {
 124         System.out.printf("%s: [COMMAND]:\n%s\n", logToken, Utils.getCommandLine(pb));
 125         System.out.printf("%s: [STDERR]:\n%s\n", logToken, out.getStderr());
 126         System.out.printf("%s: [STDOUT]:\n%s\n", logToken, out.getStdout());
 127     }
 128 
 129     private static ProcessBuilder containerCommand(String name) throws Exception {
 130         // start container with monitored JVM inside
 131         DockerRunOptions opts = new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", "SimpleLoop")
 132             .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
 133             .addJavaOpts("-cp", "/test-classes/")
 134             .addDockerOpts("--cap-add=SYS_PTRACE")
 135             .addDockerOpts("--name", name)
 136             .addDockerOpts("--sig-proxy=true")
 137             .addJavaOpts("-XX:+UsePerfData")
 138             .addClassOptions("" + TIME_TO_RUN_CHILD_PROCESS);
 139 
 140         return new ProcessBuilder(DockerTestUtils.buildJavaCommand(opts));
 141     }
 142 }