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.Container; 47 import jdk.test.lib.Utils; 48 import jdk.test.lib.containers.docker.Common; 49 import jdk.test.lib.containers.docker.DockerRunOptions; 50 import jdk.test.lib.containers.docker.DockerTestUtils; 51 import jdk.test.lib.process.OutputAnalyzer; 52 53 54 public class TestJcmdWithSideCar { 55 private static final String IMAGE_NAME = Common.imageName("jfr-jcmd"); 56 private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds 57 private static final String MAIN_CONTAINER_NAME = "test-container-main"; 58 59 public static void main(String[] args) throws Exception { 60 if (!DockerTestUtils.canTestDocker()) { 61 return; 62 } 63 64 DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker"); 65 66 try { 67 // Start the loop process in the "main" container, then run test cases 68 // using a sidecar container. 69 DockerThread t = startMainContainer(); 70 71 waitForMainContainerToStart(500, 10); 72 t.checkForErrors(); 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 } finally { 88 DockerTestUtils.removeDockerImage(IMAGE_NAME); 89 } 90 } 91 92 93 // Run "jcmd -l" in a sidecar container and find a process that runs EventGeneratorLoop 94 private static OutputAnalyzer testCase01() throws Exception { 95 return runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l") 96 .shouldHaveExitValue(0) 97 .shouldContain("sun.tools.jcmd.JCmd") 98 .shouldContain("EventGeneratorLoop"); 99 } 100 101 // run jhsdb jinfo <PID> (jhsdb uses PTRACE) 102 private static void testCase02(long pid) throws Exception { 103 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid) 104 .shouldHaveExitValue(0) 105 .shouldContain("Java System Properties") 106 .shouldContain("VM Flags"); 107 } 108 109 // test jcmd with some commands (help, start JFR recording) 110 // JCMD will use signal mechanism and Unix Socket 111 private static void testCase03(long pid) throws Exception { 112 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "help") 113 .shouldHaveExitValue(0) 114 .shouldContain("VM.version"); 115 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start") 116 .shouldHaveExitValue(0) 117 .shouldContain("Started recording"); 118 } 119 120 private static DockerThread startMainContainer() throws Exception { 121 // start "main" container (the observee) 122 DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop"); 123 opts.addDockerOpts("--cap-add=SYS_PTRACE") 124 .addDockerOpts("--name", MAIN_CONTAINER_NAME) 125 .addDockerOpts("-v", "/tmp") 126 .addJavaOpts("-XX:+UsePerfData") 127 .addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS); 128 DockerThread t = new DockerThread(opts); 129 t.start(); 130 131 return t; 132 } 133 134 private static void waitForMainContainerToStart(int delayMillis, int count) throws Exception { 135 boolean started = false; 136 for(int i=0; i < count; i++) { 137 try { 138 Thread.sleep(delayMillis); 139 } catch (InterruptedException e) {} 140 if (isMainContainerRunning()) { 141 started = true; 142 break; 143 } 144 } 145 if (!started) { 146 throw new RuntimeException("Main container did not start"); 147 } 148 } 149 150 private static boolean isMainContainerRunning() throws Exception { 151 OutputAnalyzer out = 152 DockerTestUtils.execute(Container.ENGINE_COMMAND, 153 "ps", "--no-trunc", 154 "--filter", "name=" + MAIN_CONTAINER_NAME); 155 return out.getStdout().contains(MAIN_CONTAINER_NAME); 156 } 157 158 // JCMD relies on the attach mechanism (com.sun.tools.attach), 159 // which in turn relies on JVMSTAT mechanism, which puts its mapped 160 // buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar 161 // we mount /tmp via --volumes-from from the main container. 162 private static OutputAnalyzer runSideCar(String MAIN_CONTAINER_NAME, String whatToRun, 163 String... args) throws Exception { 164 List<String> cmd = new ArrayList<>(); 165 String[] command = new String[] { 166 Container.ENGINE_COMMAND, "run", 167 "--tty=true", "--rm", 168 "--cap-add=SYS_PTRACE", "--sig-proxy=true", 169 "--pid=container:" + MAIN_CONTAINER_NAME, 170 "--volumes-from", MAIN_CONTAINER_NAME, 171 IMAGE_NAME, whatToRun 172 }; 173 174 cmd.addAll(Arrays.asList(command)); 175 cmd.addAll(Arrays.asList(args)); 176 return DockerTestUtils.execute(cmd); 177 } 178 179 private static long findProcess(OutputAnalyzer out, String name) throws Exception { 180 List<String> l = out.asLines() 181 .stream() 182 .filter(s -> s.contains(name)) 183 .collect(Collectors.toList()); 184 if (l.isEmpty()) { 185 throw new RuntimeException("Could not find matching process"); 186 } 187 String psInfo = l.get(0); 188 System.out.println("findProcess(): psInfo: " + psInfo); 189 String pid = psInfo.substring(0, psInfo.indexOf(' ')); 190 System.out.println("findProcess(): pid: " + pid); 191 return Long.parseLong(pid); 192 } 193 194 private static DockerRunOptions commonDockerOpts(String className) { 195 return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className) 196 .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/") 197 .addJavaOpts("-cp", "/test-classes/"); 198 } 199 200 201 static class DockerThread extends Thread { 202 DockerRunOptions runOpts; 203 Exception exception; 204 205 DockerThread(DockerRunOptions opts) { 206 runOpts = opts; 207 } 208 209 public void run() { 210 try { 211 DockerTestUtils.dockerRunJava(runOpts); 212 } catch (Exception e) { 213 exception = e; 214 } 215 } 216 217 public void assertIsAlive() throws Exception { 218 if (!isAlive()) { 219 throw new RuntimeException("DockerThread stopped unexpectedly"); | 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.io.File; 41 import java.nio.file.Path; 42 import java.nio.file.Paths; 43 import java.util.Arrays; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.stream.Collectors; 47 import jdk.test.lib.Container; 48 import jdk.test.lib.Utils; 49 import jdk.test.lib.containers.docker.Common; 50 import jdk.test.lib.containers.docker.DockerRunOptions; 51 import jdk.test.lib.containers.docker.DockerTestUtils; 52 import jdk.test.lib.process.OutputAnalyzer; 53 54 55 public class TestJcmdWithSideCar { 56 private static final String IMAGE_NAME = Common.imageName("jfr-jcmd"); 57 private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds 58 private static final String MAIN_CONTAINER_NAME = "test-container-main"; 59 private static final String MAIN_METHOD_STARTED_SIGNAL_FILE = "mainMethodStarted.signal"; 60 61 public static void main(String[] args) throws Exception { 62 if (!DockerTestUtils.canTestDocker()) { 63 return; 64 } 65 66 DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker"); 67 68 try { 69 // Start the loop process in the "main" container, then run test cases 70 // using a sidecar container. 71 DockerThread t = startMainContainer(); 72 73 waitForMainContainerToStart(500, 10); 74 t.checkForErrors(); 75 waitForContainerMainMethod(); 76 77 long mainProcPid = testCase01(); 78 79 // Excluding the test case below until JDK-8228850 is fixed 80 // JDK-8228850: jhsdb jinfo fails with ClassCastException: 81 // s.j.h.oops.TypeArray cannot be cast to s.j.h.oops.Instance 82 // t.assertIsAlive(); 83 // testCase02(mainProcPid); 84 85 // JCMD does not work in sidecar configuration, except for "jcmd -l". 86 // Including this test case to assist in reproduction of the problem. 87 // t.assertIsAlive(); 88 // testCase03(mainProcPid); 89 90 t.join(TIME_TO_RUN_MAIN_PROCESS * 1000); 91 t.checkForErrors(); 92 } finally { 93 DockerTestUtils.removeDockerImage(IMAGE_NAME); 94 } 95 } 96 97 98 // Run "jcmd -l" in a sidecar container, find a target process. 99 private static long testCase01() throws Exception { 100 OutputAnalyzer out = runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l") 101 .shouldHaveExitValue(0) 102 .shouldContain("sun.tools.jcmd.JCmd"); 103 long pid = findProcess(out, "EventGeneratorLoop"); 104 if (pid == -1) { 105 throw new RuntimeException("Could not find specified process"); 106 } 107 108 return pid; 109 } 110 111 // run jhsdb jinfo <PID> (jhsdb uses PTRACE) 112 private static void testCase02(long pid) throws Exception { 113 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid) 114 .shouldHaveExitValue(0) 115 .shouldContain("Java System Properties") 116 .shouldContain("VM Flags"); 117 } 118 119 // test jcmd with some commands (help, start JFR recording) 120 // JCMD will use signal mechanism and Unix Socket 121 private static void testCase03(long pid) throws Exception { 122 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "help") 123 .shouldHaveExitValue(0) 124 .shouldContain("VM.version"); 125 runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start") 126 .shouldHaveExitValue(0) 127 .shouldContain("Started recording"); 128 } 129 130 private static DockerThread startMainContainer() throws Exception { 131 // start "main" container (the observee) 132 DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop"); 133 opts.addDockerOpts("--cap-add=SYS_PTRACE") 134 .addDockerOpts("--name", MAIN_CONTAINER_NAME) 135 .addDockerOpts("--volume", "/tmp") 136 .addDockerOpts("--volume", Paths.get(".").toAbsolutePath() + ":/workdir/") 137 .addJavaOpts("-XX:+UsePerfData") 138 .addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS) 139 .addClassOptions("/workdir/" + MAIN_METHOD_STARTED_SIGNAL_FILE); 140 DockerThread t = new DockerThread(opts); 141 t.start(); 142 143 return t; 144 } 145 146 private static void waitForMainContainerToStart(int delayMillis, int count) throws Exception { 147 boolean started = false; 148 for(int i=0; i < count; i++) { 149 sleep(delayMillis); 150 if (isMainContainerRunning()) { 151 started = true; 152 break; 153 } 154 } 155 if (!started) { 156 throw new RuntimeException("Main container did not start"); 157 } 158 } 159 160 private static boolean isMainContainerRunning() throws Exception { 161 OutputAnalyzer out = 162 DockerTestUtils.execute(Container.ENGINE_COMMAND, 163 "ps", "--no-trunc", 164 "--filter", "name=" + MAIN_CONTAINER_NAME); 165 return out.getStdout().contains(MAIN_CONTAINER_NAME); 166 } 167 168 // JCMD relies on the attach mechanism (com.sun.tools.attach), 169 // which in turn relies on JVMSTAT mechanism, which puts its mapped 170 // buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar 171 // we mount /tmp via --volumes-from from the main container. 172 private static OutputAnalyzer runSideCar(String MAIN_CONTAINER_NAME, String whatToRun, 173 String... args) throws Exception { 174 List<String> cmd = new ArrayList<>(); 175 String[] command = new String[] { 176 Container.ENGINE_COMMAND, "run", 177 "--tty=true", "--rm", 178 "--cap-add=SYS_PTRACE", "--sig-proxy=true", 179 "--pid=container:" + MAIN_CONTAINER_NAME, 180 "--volumes-from", MAIN_CONTAINER_NAME, 181 IMAGE_NAME, whatToRun 182 }; 183 184 cmd.addAll(Arrays.asList(command)); 185 cmd.addAll(Arrays.asList(args)); 186 return DockerTestUtils.execute(cmd); 187 } 188 189 // Returns PID if a matching process, or -1 if not found. 190 private static long findProcess(OutputAnalyzer out, String name) throws Exception { 191 List<String> l = out.asLines() 192 .stream() 193 .filter(s -> s.contains(name)) 194 .collect(Collectors.toList()); 195 if (l.isEmpty()) { 196 return -1; 197 } 198 String psInfo = l.get(0); 199 System.out.println("findProcess(): psInfo: " + psInfo); 200 String pid = psInfo.substring(0, psInfo.indexOf(' ')); 201 System.out.println("findProcess(): pid: " + pid); 202 return Long.parseLong(pid); 203 } 204 205 private static DockerRunOptions commonDockerOpts(String className) { 206 return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className) 207 .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/") 208 .addJavaOpts("-cp", "/test-classes/"); 209 } 210 211 // Wait for the "main()" method in the container to start. 212 private static void waitForContainerMainMethod() { 213 waitForSignalFile(MAIN_METHOD_STARTED_SIGNAL_FILE, 10*1000, 500); 214 } 215 216 // Wait for a file in a specified location (aka signal file) to be created. 217 // fn - file name to wait for 218 // howLong - howLong (milliseconds) 219 // delay - delay between the attempts (milliseconds) 220 private static void waitForSignalFile(String fn, long howLong, long delay) { 221 long expiration = System.currentTimeMillis() + howLong; 222 File f = Paths.get(".", fn).toFile(); 223 224 do { 225 if (f.exists()) { 226 return; 227 } 228 sleep(delay); 229 } while (System.currentTimeMillis() < expiration); 230 231 String msg = "Timed out while waiting for a signal file " + fn; 232 throw new RuntimeException(msg); 233 } 234 235 private static void sleep(long delay) { 236 try { 237 Thread.sleep(delay); 238 } catch (InterruptedException e) { 239 System.out.println("InterruptedException" + e.getMessage()); 240 } 241 } 242 243 static class DockerThread extends Thread { 244 DockerRunOptions runOpts; 245 Exception exception; 246 247 DockerThread(DockerRunOptions opts) { 248 runOpts = opts; 249 } 250 251 public void run() { 252 try { 253 DockerTestUtils.dockerRunJava(runOpts); 254 } catch (Exception e) { 255 exception = e; 256 } 257 } 258 259 public void assertIsAlive() throws Exception { 260 if (!isAlive()) { 261 throw new RuntimeException("DockerThread stopped unexpectedly"); |