1 /* 2 * Copyright (c) 2017, 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 jdk.test.lib.containers.docker; 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 import jdk.test.lib.Platform; 39 import jdk.test.lib.Utils; 40 import jdk.test.lib.process.OutputAnalyzer; 41 import jdk.test.lib.process.ProcessTools; 42 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 125 Path buildDir = Paths.get(".", buildDirName); 126 if (Files.exists(buildDir)) { 127 throw new RuntimeException("The docker build directory already exists: " + buildDir); 128 } 129 // check for the existance of a platform specific docker file as well 130 String platformSpecificDockerfile = dockerfile + "-" + Platform.getOsArch(); 131 if (Files.exists(Paths.get(Utils.TEST_SRC, platformSpecificDockerfile))) { 132 dockerfile = platformSpecificDockerfile; 133 } 134 135 Path jdkSrcDir = Paths.get(Utils.TEST_JDK); 136 Path jdkDstDir = buildDir.resolve("jdk"); 137 138 Files.createDirectories(jdkDstDir); 139 140 // Copy JDK-under-test tree to the docker build directory. 141 // This step is required for building a docker image. 142 Files.walkFileTree(jdkSrcDir, new CopyFileVisitor(jdkSrcDir, jdkDstDir)); 143 buildDockerImage(imageName, Paths.get(Utils.TEST_SRC, dockerfile), buildDir); 144 } 145 146 147 /** 148 * Build a docker image based on given docker file and docker build directory. 149 * 150 * @param imageName name of the image to be created, including version tag 151 * @param dockerfile path to the Dockerfile to be used for building the docker 152 * image. The specified dockerfile will be copied to the docker build 153 * directory as 'Dockerfile' 154 * @param buildDir build directory; it should already contain all the content 155 * needed to build the docker image. 156 * @throws Exception 157 */ 158 public static void 159 buildDockerImage(String imageName, Path dockerfile, Path buildDir) throws Exception { 160 161 // Copy docker file to the build dir 162 Files.copy(dockerfile, buildDir.resolve("Dockerfile")); 163 164 // Build the docker 165 execute("docker", "build", "--no-cache", "--tag", imageName, buildDir.toString()) 166 .shouldHaveExitValue(0) 167 .shouldContain("Successfully built"); 168 } 169 170 171 /** 172 * Run Java inside the docker image with specified parameters and options. 173 * 174 * @param DockerRunOptions optins for running docker 175 * 176 * @return output of the run command 177 * @throws Exception 178 */ 179 public static OutputAnalyzer dockerRunJava(DockerRunOptions opts) throws Exception { 180 ArrayList<String> cmd = new ArrayList<>(); 181 182 cmd.add("docker"); 183 cmd.add("run"); 184 if (opts.tty) 185 cmd.add("--tty=true"); 186 if (opts.removeContainerAfterUse) 187 cmd.add("--rm"); 188 189 cmd.addAll(opts.dockerOpts); 190 cmd.add(opts.imageNameAndTag); 191 cmd.add(opts.command); 192 193 cmd.addAll(opts.javaOpts); 194 if (opts.appendTestJavaOptions) { 195 Collections.addAll(cmd, Utils.getTestJavaOpts()); 196 } 197 198 cmd.add(opts.classToRun); 199 cmd.addAll(opts.classParams); 200 201 return execute(cmd); 202 } 203 204 205 /** 206 * Remove docker image 207 * 208 * @param DockerRunOptions optins for running docker 209 * @return output of the command 210 * @throws Exception 211 */ 212 public static OutputAnalyzer removeDockerImage(String imageNameAndTag) throws Exception { 213 return execute("docker", "rmi", "--force", imageNameAndTag); 214 } 215 216 217 218 /** 219 * Convenience method - express command as sequence of strings 220 * 221 * @param command to execute 222 * @return The output from the process 223 * @throws Exception 224 */ 225 public static OutputAnalyzer execute(List<String> command) throws Exception { 226 return execute(command.toArray(new String[command.size()])); 227 } 228 229 230 /** 231 * Execute a specified command in a process, report diagnostic info. 232 * 233 * @param command to be executed 234 * @return The output from the process 235 * @throws Exception 236 */ 237 public static OutputAnalyzer execute(String... command) throws Exception { 238 239 ProcessBuilder pb = new ProcessBuilder(command); 240 System.out.println("[COMMAND]\n" + Utils.getCommandLine(pb)); 241 242 long started = System.currentTimeMillis(); 243 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 244 245 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 246 System.out.println("[STDERR]\n" + output.getStderr()); 247 System.out.println("[STDOUT]\n" + output.getStdout()); 248 249 return output; 250 } 251 252 253 private static class CopyFileVisitor extends SimpleFileVisitor<Path> { 254 private final Path src; 255 private final Path dst; 256 257 public CopyFileVisitor(Path src, Path dst) { 258 this.src = src; 259 this.dst = dst; 260 } 261 262 263 @Override 264 public FileVisitResult preVisitDirectory(Path file, 265 BasicFileAttributes attrs) throws IOException { 266 Path dstDir = dst.resolve(src.relativize(file)); 267 if (!dstDir.toFile().exists()) { 268 Files.createDirectories(dstDir); 269 } 270 return FileVisitResult.CONTINUE; 271 } 272 273 274 @Override 275 public FileVisitResult visitFile(Path file, 276 BasicFileAttributes attrs) throws IOException { 277 if (!file.toFile().isFile()) { 278 return FileVisitResult.CONTINUE; 279 } 280 Path dstFile = dst.resolve(src.relativize(file)); 281 Files.copy(file, dstFile, StandardCopyOption.COPY_ATTRIBUTES); 282 return FileVisitResult.CONTINUE; 283 } 284 } 285 }