1 /* 2 * Copyright (c) 2018, 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 package com.oracle.java.testlibrary; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.nio.file.Files; 29 import java.nio.file.FileVisitResult; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.nio.file.SimpleFileVisitor; 33 import java.nio.file.StandardCopyOption; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 39 import com.oracle.java.testlibrary.Utils; 40 import com.oracle.java.testlibrary.Platform; 41 import com.oracle.java.testlibrary.OutputAnalyzer; 42 import com.oracle.java.testlibrary.ProcessTools; 43 44 public class DockerTestUtils { 45 private static final String FS = File.separator; 46 private static boolean isDockerEngineAvailable = false; 47 private static boolean wasDockerEngineChecked = false; 48 49 // Diagnostics: set to true to enable more diagnostic info 50 private static final boolean DEBUG = false; 51 52 /** 53 * Optimized check of whether the docker engine is available in a given 54 * environment. Checks only once, then remembers the result in a singleton. 55 * 56 * @return true if docker engine is available 57 * @throws Exception 58 */ 59 public static boolean isDockerEngineAvailable() throws Exception { 60 if (wasDockerEngineChecked) 61 return isDockerEngineAvailable; 62 63 isDockerEngineAvailable = isDockerEngineAvailableCheck(); 64 wasDockerEngineChecked = true; 65 return isDockerEngineAvailable; 66 } 67 68 69 /** 70 * Convenience method, will check if docker engine is available and usable; 71 * will print the appropriate message when not available. 72 * 73 * @return true if docker engine is available 74 * @throws Exception 75 */ 76 public static boolean canTestDocker() throws Exception { 77 if (isDockerEngineAvailable()) { 78 return true; 79 } else { 80 System.out.println("Docker engine is not available on this system"); 81 System.out.println("This test is SKIPPED"); 82 return false; 83 } 84 } 85 86 87 /** 88 * Simple check - is docker engine available, accessible and usable. 89 * Run basic docker command: 'docker ps' - list docker instances. 90 * If docker engine is available and accesible then true is returned 91 * and we can proceed with testing docker. 92 * 93 * @return true if docker engine is available and usable 94 * @throws Exception 95 */ 96 private static boolean isDockerEngineAvailableCheck() throws Exception { 97 try { 98 execute("docker", "ps") 99 .shouldHaveExitValue(0) 100 .shouldContain("CONTAINER") 101 .shouldContain("IMAGE"); 102 } catch (Exception e) { 103 return false; 104 } 105 return true; 106 } 107 108 109 /** 110 * Build a docker image that contains JDK under test. 111 * The jdk will be placed under the "/jdk/" folder inside the docker file system. 112 * 113 * @param imageName name of the image to be created, including version tag 114 * @param dockerfile name of the dockerfile residing in the test source; 115 * we check for a platform specific dockerfile as well 116 * and use this one in case it exists 117 * @param buildDirName name of the docker build/staging directory, which will 118 * be created in the jtreg's scratch folder 119 * @throws Exception 120 */ 121 public static void 122 buildJdkDockerImage(String imageName, String dockerfile, String buildDirName) 123 throws Exception { 124 Path buildDir = Paths.get(".", buildDirName); 125 if (Files.exists(buildDir)) { 126 throw new RuntimeException("The docker build directory already exists: " + buildDir); 127 } 128 // check for the existance of a platform specific docker file as well 129 String platformSpecificDockerfile = dockerfile + "-" + Platform.getOsArch(); 130 if (Files.exists(Paths.get(Utils.TEST_SRC, platformSpecificDockerfile))) { 131 dockerfile = platformSpecificDockerfile; 132 } 133 134 Path jdkSrcDir = Paths.get(Utils.TEST_JDK); 135 Path jdkDstDir = buildDir.resolve("jdk"); 136 137 Files.createDirectories(jdkDstDir); 138 139 // Copy JDK-under-test tree to the docker build directory. 140 // This step is required for building a docker image. 141 Files.walkFileTree(jdkSrcDir, new CopyFileVisitor(jdkSrcDir, jdkDstDir)); 142 buildDockerImage(imageName, Paths.get(Utils.TEST_SRC, dockerfile), buildDir); 143 } 144 145 146 /** 147 * Build a docker image based on given docker file and docker build directory. 148 * 149 * @param imageName name of the image to be created, including version tag 150 * @param dockerfile path to the Dockerfile to be used for building the docker 151 * image. The specified dockerfile will be copied to the docker build 152 * directory as 'Dockerfile' 153 * @param buildDir build directory; it should already contain all the content 154 * needed to build the docker image. 155 * @throws Exception 156 */ 157 public static void 158 buildDockerImage(String imageName, Path dockerfile, Path buildDir) throws Exception { 159 // Copy docker file to the build dir 160 Files.copy(dockerfile, buildDir.resolve("Dockerfile")); 161 162 // Build the docker 163 execute("docker", "build", "--no-cache", "--tag", imageName, buildDir.toString()) 164 .shouldHaveExitValue(0) 165 .shouldContain("Successfully built"); 166 } 167 168 169 /** 170 * Build the docker command to run java inside a container 171 * 172 * @param DockerRunOptions optins for running docker 173 * 174 * @return command 175 * @throws Exception 176 */ 177 public static List<String> buildJavaCommand(DockerRunOptions opts) throws Exception { 178 List<String> cmd = new ArrayList<>(); 179 180 cmd.add("docker"); 181 cmd.add("run"); 182 if (opts.tty) 183 cmd.add("--tty=true"); 184 if (opts.removeContainerAfterUse) 185 cmd.add("--rm"); 186 187 cmd.addAll(opts.dockerOpts); 188 cmd.add(opts.imageNameAndTag); 189 cmd.add(opts.command); 190 191 cmd.addAll(opts.javaOpts); 192 if (opts.appendTestJavaOptions) { 193 Collections.addAll(cmd, Utils.getTestJavaOpts()); 194 } 195 196 cmd.add(opts.classToRun); 197 cmd.addAll(opts.classParams); 198 return cmd; 199 } 200 201 /** 202 * Run Java inside the docker image with specified parameters and options. 203 * 204 * @param DockerRunOptions optins for running docker 205 * 206 * @return output of the run command 207 * @throws Exception 208 */ 209 public static OutputAnalyzer dockerRunJava(DockerRunOptions opts) throws Exception { 210 return execute(buildJavaCommand(opts)); 211 } 212 213 /** 214 * Remove docker image 215 * 216 * @param DockerRunOptions optins for running docker 217 * @return output of the command 218 * @throws Exception 219 */ 220 public static OutputAnalyzer removeDockerImage(String imageNameAndTag) throws Exception { 221 return execute("docker", "rmi", "--force", imageNameAndTag); 222 } 223 224 225 226 /** 227 * Convenience method - express command as sequence of strings 228 * 229 * @param command to execute 230 * @return The output from the process 231 * @throws Exception 232 */ 233 public static OutputAnalyzer execute(List<String> command) throws Exception { 234 return execute(command.toArray(new String[command.size()])); 235 } 236 237 238 /** 239 * Execute a specified command in a process, report diagnostic info. 240 * 241 * @param command to be executed 242 * @return The output from the process 243 * @throws Exception 244 */ 245 public static OutputAnalyzer execute(String... command) throws Exception { 246 247 ProcessBuilder pb = new ProcessBuilder(command); 248 System.out.println("[COMMAND]\n" + Utils.getCommandLine(pb)); 249 250 long started = System.currentTimeMillis(); 251 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 252 253 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 254 System.out.println("[STDERR]\n" + output.getStderr()); 255 System.out.println("[STDOUT]\n" + output.getStdout()); 256 257 return output; 258 } 259 260 261 private static class CopyFileVisitor extends SimpleFileVisitor<Path> { 262 private final Path src; 263 private final Path dst; 264 265 public CopyFileVisitor(Path src, Path dst) { 266 this.src = src; 267 this.dst = dst; 268 } 269 270 271 @Override 272 public FileVisitResult preVisitDirectory(Path file, 273 BasicFileAttributes attrs) throws IOException { 274 Path dstDir = dst.resolve(src.relativize(file)); 275 if (!dstDir.toFile().exists()) { 276 Files.createDirectories(dstDir); 277 } 278 return FileVisitResult.CONTINUE; 279 } 280 281 282 @Override 283 public FileVisitResult visitFile(Path file, 284 BasicFileAttributes attrs) throws IOException { 285 if (!file.toFile().isFile()) { 286 return FileVisitResult.CONTINUE; 287 } 288 Path dstFile = dst.resolve(src.relativize(file)); 289 Files.copy(file, dstFile, StandardCopyOption.COPY_ATTRIBUTES); 290 return FileVisitResult.CONTINUE; 291 } 292 } 293 }