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 side car pattern. 28 * Sidecar is a common pattern used in the cloud environments for monitoring 29 * and other uses. In side car pattern the main application/service container 30 * is paired with a sidecar container by sharing certain aspects of container 31 * namespace such as PID namespace, specific sub-directories, IPC and more. 32 * @requires docker.support 33 * @library /test/lib 34 * @modules java.base/jdk.internal.misc 35 * java.management 36 * jdk.jartool/sun.tools.jar 37 * @build EventGeneratorLoop 38 * @run driver TestJcmdWithSideCar 39 */ 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.Arrays; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.stream.Collectors; 46 import jdk.test.lib.Utils; 47 import jdk.test.lib.containers.docker.Common; 48 import jdk.test.lib.containers.docker.DockerRunOptions; 49 import jdk.test.lib.containers.docker.DockerTestUtils; 50 import jdk.test.lib.process.OutputAnalyzer; 51 52 53 // Important: for this test to work in selinux environment make 54 // sure that docker has access to the JTwork folders. E.g.: 55 // sudo chcon -Rt svirt_sandbox_file_t $JTWORK_DIR 56 public class TestJcmdWithSideCar { 57 private static final String IMAGE_NAME = Common.imageName("jfr-jcmd"); 58 private static final Path WORK_DIR = Paths.get(".").toAbsolutePath(); 59 private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (15 * Utils.TIMEOUT_FACTOR); // seconds 60 private static final String MAIN_CONTAINER_NAME = "test-container-main"; 61 62 public static void main(String[] args) throws Exception { 63 if (!DockerTestUtils.canTestDocker()) { 64 return; 65 } 66 67 DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker"); 68 69 try { 70 // Start the loop process in the "main" container, then run test cases 71 // using a sidecar container. 72 DockerThread t = startMainContainer(); 73 74 OutputAnalyzer jcmdOut = testCase01(); 75 long mainProcPid = findProcess(jcmdOut, "EventGeneratorLoop"); 76 77 t.assertIsAlive(); 78 testCase02(mainProcPid); 79 80 // JCMD does not work in sidecar configuration, except for "jcmd -l". 81 // Including this test case to assist in reproduction of the problem. 82 // t.assertIsAlive(); 83 // testCase03(mainProcPid); 84 85 t.join(TIME_TO_RUN_MAIN_PROCESS * 1000); 86 t.checkForErrors(); 87 t.out.shouldHaveExitValue(0); 88 } finally { 89 DockerTestUtils.removeDockerImage(IMAGE_NAME); 90 } 91 } 92 93 94 // Run "jcmd -l" in a sidecar container and find a process that runs EventGeneratorLoop 95 private static OutputAnalyzer testCase01() throws Exception { 96 return runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l") 97 .shouldHaveExitValue(0) 98 .shouldContain("sun.tools.jcmd.JCmd") 99 .shouldContain("EventGeneratorLoop"); 100 } 101 102 // run jhsdb jinfo <PID> (jhsdb uses PTRACE) 103 private static void testCase02(long pid) throws Exception { 104 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid) 105 .shouldHaveExitValue(0) 106 .shouldContain("Java System Properties") 107 .shouldContain("VM Flags"); 108 } 109 110 // test jcmd with some commands (help, start JFR recording) 111 // JCMD will use signal mechanism and Unix Socket 112 private static void testCase03(long pid) throws Exception { 113 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "help") 114 .shouldHaveExitValue(0) 115 .shouldContain("VM.version"); 116 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start") 117 .shouldHaveExitValue(0) 118 .shouldContain("Started recording"); 119 } 120 121 private static DockerThread startMainContainer() throws Exception { 122 // start "main" container (the observee) 123 DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop"); 124 opts.addDockerOpts("--cap-add=SYS_PTRACE", "--ipc=shareable") 125 .addDockerOpts("--name", MAIN_CONTAINER_NAME) 126 .addDockerOpts("-v", WORK_DIR + ":/tmp/") 127 .addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS); 128 DockerThread t = new DockerThread(opts); 129 t.start(); 130 131 // sleep couple of seconds while the main container is starting up 132 try { 133 Thread.sleep(2*1000); 134 } catch (InterruptedException e) {} 135 136 t.assertIsAlive(); 137 t.checkForErrors(); 138 139 return t; 140 } 141 142 private static OutputAnalyzer runSideCar(String MAIN_CONTAINER_NAME, String whatToRun, 143 String... args) throws Exception { 144 List<String> cmd = new ArrayList<>(); 145 String[] command = new String[] { 146 DockerTestUtils.DOCKER_COMMAND, "run", 147 "--tty=true", "--rm", 148 "--cap-add=SYS_PTRACE", "--sig-proxy=true", 149 "--pid=container:" + MAIN_CONTAINER_NAME, 150 "--pid=container:" + MAIN_CONTAINER_NAME, 151 "--ipc=container:" + MAIN_CONTAINER_NAME, 152 "-v", WORK_DIR + ":/tmp/", 153 IMAGE_NAME, whatToRun 154 }; 155 156 cmd.addAll(Arrays.asList(command)); 157 cmd.addAll(Arrays.asList(args)); 158 return DockerTestUtils.execute(cmd); 159 } 160 161 private static long findProcess(OutputAnalyzer out, String name) throws Exception { 162 List<String> l = out.asLines() 163 .stream() 164 .filter(s -> s.contains(name)) 165 .collect(Collectors.toList()); 166 if (l.isEmpty()) { 167 throw new RuntimeException("Could not find matching process"); 168 } 169 String psInfo = l.get(0); 170 System.out.println("findProcess(): psInfo: " + psInfo); 171 String pid = psInfo.substring(0, psInfo.indexOf(' ')); 172 System.out.println("findProcess(): pid: " + pid); 173 return Long.parseLong(pid); 174 } 175 176 private static DockerRunOptions commonDockerOpts(String className) { 177 return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className) 178 .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/") 179 .addJavaOpts("-cp", "/test-classes/"); 180 } 181 182 183 static class DockerThread extends Thread { 184 DockerRunOptions runOpts; 185 OutputAnalyzer out; 186 Exception exception; 187 188 DockerThread(DockerRunOptions opts) { 189 runOpts = opts; 190 } 191 192 public void run() { 193 try { 194 out = DockerTestUtils.dockerRunJava(runOpts); 195 } catch (Exception e) { 196 exception = e; 197 } 198 } 199 200 public void assertIsAlive() throws Exception { 201 if (!isAlive()) { 202 throw new RuntimeException("DockerThread stopped unexpectedly"); 203 } 204 } 205 206 public void checkForErrors() throws Exception { 207 if (exception != null) { 208 throw new RuntimeException("DockerThread threw exception" 209 + exception.getMessage()); 210 } 211 } 212 } 213 214 }