From ef2765e4d3cc7ce1262492e76f02e373af3fcf0a Mon Sep 17 00:00:00 2001 From: vaibhav Date: Thu, 26 Jul 2018 06:16:09 -0400 Subject: [PATCH] 8189762: [TESTBUG] Create tests for JDK-8146115 container awareness and resource configuration Summary: Created tests for the feature Reviewed-by: mseledtsov --- src/share/vm/prims/whitebox.cpp | 12 + .../runtime/containers/docker/AttemptOOM.java | 49 +++ .../containers/docker/CPUSetsReader.java | 141 +++++++++ .../containers/docker/CheckContainerized.java | 44 +++ .../containers/docker/DockerBasicTest.java | 80 +++++ .../containers/docker/Dockerfile-BasicTest | 8 + .../docker/Dockerfile-BasicTest-aarch64 | 8 + .../docker/Dockerfile-BasicTest-ppc64le | 10 + .../docker/Dockerfile-BasicTest-s390x | 7 + .../containers/docker/HelloDocker.java | 28 ++ .../containers/docker/PrintContainerInfo.java | 34 +++ .../runtime/containers/docker/TEST.properties | 1 + .../containers/docker/TestCPUAwareness.java | 204 +++++++++++++ .../containers/docker/TestCPUSets.java | 137 +++++++++ .../docker/TestMemoryAwareness.java | 109 +++++++ test/runtime/containers/docker/TestMisc.java | 113 +++++++ test/testlibrary/ClassFileInstaller.java | 216 +++++++++++++- .../com/oracle/java/testlibrary/Common.java | 89 ++++++ .../java/testlibrary/DockerRunOptions.java | 73 +++++ .../java/testlibrary/DockerTestUtils.java | 282 ++++++++++++++++++ .../com/oracle/java/testlibrary/Platform.java | 4 +- .../com/oracle/java/testlibrary/Utils.java | 11 +- .../whitebox/sun/hotspot/WhiteBox.java | 5 + 23 files changed, 1655 insertions(+), 10 deletions(-) create mode 100644 test/runtime/containers/docker/AttemptOOM.java create mode 100644 test/runtime/containers/docker/CPUSetsReader.java create mode 100644 test/runtime/containers/docker/CheckContainerized.java create mode 100644 test/runtime/containers/docker/DockerBasicTest.java create mode 100644 test/runtime/containers/docker/Dockerfile-BasicTest create mode 100644 test/runtime/containers/docker/Dockerfile-BasicTest-aarch64 create mode 100644 test/runtime/containers/docker/Dockerfile-BasicTest-ppc64le create mode 100644 test/runtime/containers/docker/Dockerfile-BasicTest-s390x create mode 100644 test/runtime/containers/docker/HelloDocker.java create mode 100644 test/runtime/containers/docker/PrintContainerInfo.java create mode 100644 test/runtime/containers/docker/TEST.properties create mode 100644 test/runtime/containers/docker/TestCPUAwareness.java create mode 100644 test/runtime/containers/docker/TestCPUSets.java create mode 100644 test/runtime/containers/docker/TestMemoryAwareness.java create mode 100644 test/runtime/containers/docker/TestMisc.java create mode 100644 test/testlibrary/com/oracle/java/testlibrary/Common.java create mode 100644 test/testlibrary/com/oracle/java/testlibrary/DockerRunOptions.java create mode 100644 test/testlibrary/com/oracle/java/testlibrary/DockerTestUtils.java diff --git a/src/share/vm/prims/whitebox.cpp b/src/share/vm/prims/whitebox.cpp index 3495b8dfb..284a4a971 100644 --- a/src/share/vm/prims/whitebox.cpp +++ b/src/share/vm/prims/whitebox.cpp @@ -160,6 +160,7 @@ WB_END #ifdef LINUX #include "utilities/elfFile.hpp" +#include "osContainer_linux.hpp" #endif WB_ENTRY(jlong, WB_GetCompressedOopsMaxHeapSize(JNIEnv* env, jobject o)) { @@ -1028,6 +1029,15 @@ WB_ENTRY(jboolean, WB_CheckLibSpecifiesNoexecstack(JNIEnv* env, jobject o, jstri return ret; WB_END +WB_ENTRY(jboolean, WB_IsContainerized(JNIEnv* env, jobject o)) + LINUX_ONLY(return OSContainer::is_containerized();) + return false; +WB_END + +WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o)) + os::print_os_info(tty); +WB_END + #define CC (char*) static JNINativeMethod methods[] = { @@ -1141,6 +1151,8 @@ static JNINativeMethod methods[] = { {CC"forceSafepoint", CC"()V", (void*)&WB_ForceSafepoint }, {CC"checkLibSpecifiesNoexecstack", CC"(Ljava/lang/String;)Z", (void*)&WB_CheckLibSpecifiesNoexecstack}, + {CC"isContainerized", CC"()Z", (void*)&WB_IsContainerized }, + {CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo }, }; #undef CC diff --git a/test/runtime/containers/docker/AttemptOOM.java b/test/runtime/containers/docker/AttemptOOM.java new file mode 100644 index 000000000..4d4682870 --- /dev/null +++ b/test/runtime/containers/docker/AttemptOOM.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public class AttemptOOM { + private static MyObj[] data; + + public static void main(String[] args) throws Exception { + System.out.println("Entering AttemptOOM main"); + + // each MyObj will allocate 1024 byte array + int sizeInMb = Integer.parseInt(args[0]); + data = new MyObj[sizeInMb*1024]; + + System.out.println("data.length = " + data.length); + + for (int i=0; i < data.length; i++) { + data[i] = new MyObj(1024); + } + + System.out.println("AttemptOOM allocation successful"); + } + + private static class MyObj { + private byte[] myData; + MyObj(int size) { + myData = new byte[size]; + } + } +} diff --git a/test/runtime/containers/docker/CPUSetsReader.java b/test/runtime/containers/docker/CPUSetsReader.java new file mode 100644 index 000000000..f6fa93ea4 --- /dev/null +++ b/test/runtime/containers/docker/CPUSetsReader.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Optional; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import com.oracle.java.testlibrary.Asserts; + + +// A simple CPU sets reader and parser +public class CPUSetsReader { + public static String PROC_SELF_STATUS_PATH="/proc/self/status"; + + // Test the parser + public static void test() { + assertParse("0-7", "0,1,2,3,4,5,6,7"); + assertParse("1,3,6", "1,3,6"); + assertParse("0,2-4,6,10-11", "0,2,3,4,6,10,11"); + assertParse("0", "0"); + } + + + private static void assertParse(String cpuSet, String expectedResult) { + Asserts.assertEquals(listToString(parseCpuSet(cpuSet)), expectedResult); + } + + + public static String readFromProcStatus(String setType) { + String path = PROC_SELF_STATUS_PATH; + Optional o = Optional.empty(); + + System.out.println("readFromProcStatus() entering for: " + setType); + + try (Stream stream = Files.lines(Paths.get(path))) { + o = stream + .filter(line -> line.contains(setType)) + .findFirst(); + } catch (IOException e) { + return null; + } + + if (!o.isPresent()) { + return null; // entry not found + } + + String[] parts = o.get().replaceAll("\\s","").split(":"); + + // Should be 2 parts, before and after ":" + Asserts.assertEquals(parts.length, 2); + + String result = parts[1]; + System.out.println("readFromProcStatus() returning: " + result); + return result; + } + + + public static List parseCpuSet(String value) { + ArrayList result = new ArrayList(); + + try { + String[] commaSeparated = value.split(","); + + for (String item : commaSeparated) { + if (item.contains("-")) { + addRange(result, item); + } else { + result.add(Integer.parseInt(item)); + } + } + } catch (Exception e) { + System.err.println("Exception in getMaxCpuSets(): " + e); + return null; + } + + return result; + } + + + private static void addRange(ArrayList list, String s) { + String[] range = s.split("-"); + if ( range.length != 2 ) { + throw new RuntimeException("Range should only contain two items, but contains " + + range.length + " items"); + } + + int min = Integer.parseInt(range[0]); + int max = Integer.parseInt(range[1]); + + if (min >= max) { + String msg = String.format("min is greater or equals to max, min = %d, max = %d", + min, max); + throw new RuntimeException(msg); + } + + for (int i = min; i <= max; i++) { + list.add(i); + } + } + + + // Convert list of integers to string with comma-separated values + public static String listToString(List list) { + return listToString(list, Integer.MAX_VALUE); + } + + // Convert list of integers to a string with comma-separated values; + // include up to maxCount. + public static String listToString(List list, int maxCount) { + return list.stream() + .limit(maxCount) + .map(Object::toString) + .collect(Collectors.joining(",")); + } +} diff --git a/test/runtime/containers/docker/CheckContainerized.java b/test/runtime/containers/docker/CheckContainerized.java new file mode 100644 index 000000000..c0ca4bf6c --- /dev/null +++ b/test/runtime/containers/docker/CheckContainerized.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import sun.hotspot.WhiteBox; + +public class CheckContainerized { + public static String OUTSIDE_OF_CONTAINER = + "CheckContainerized: Running outside of a container"; + public static String INSIDE_A_CONTAINER = + "CheckContainerized: Running inside a container"; + + public static void main(String[] args) { + System.out.println("CheckContainerized: Entering"); + WhiteBox wb = WhiteBox.getWhiteBox(); + + if (wb.isContainerized()) { + System.out.println(INSIDE_A_CONTAINER); + + } else { + System.out.println(OUTSIDE_OF_CONTAINER); + } + } +} + diff --git a/test/runtime/containers/docker/DockerBasicTest.java b/test/runtime/containers/docker/DockerBasicTest.java new file mode 100644 index 000000000..fe5d44179 --- /dev/null +++ b/test/runtime/containers/docker/DockerBasicTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Basic (sanity) test for JDK-under-test inside a docker image. + * @library /testlibrary + * @build HelloDocker + * @run driver DockerBasicTest + */ + +import com.oracle.java.testlibrary.Utils; +import com.oracle.java.testlibrary.Platform; +import com.oracle.java.testlibrary.DockerTestUtils; +import com.oracle.java.testlibrary.DockerRunOptions; + +public class DockerBasicTest { + private static final String imageNameAndTag = "jdk8-internal:test"; + // Diganostics: set to false to examine image after the test + private static final boolean removeImageAfterTest = true; + + public static void main(String[] args) throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + DockerTestUtils.buildJdkDockerImage(imageNameAndTag, "Dockerfile-BasicTest", "jdk-docker"); + + try { + testJavaVersion(); + testHelloDocker(); + } finally { + if (removeImageAfterTest) + DockerTestUtils.removeDockerImage(imageNameAndTag); + } + } + + + private static void testJavaVersion() throws Exception { + DockerRunOptions opts = + new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "-version"); + + DockerTestUtils.dockerRunJava(opts) + .shouldHaveExitValue(0) + .shouldContain(Platform.vmName); + } + + + private static void testHelloDocker() throws Exception { + DockerRunOptions opts = + new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "HelloDocker") + .addJavaOpts("-cp", "/test-classes/") + .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/"); + + DockerTestUtils.dockerRunJava(opts) + .shouldHaveExitValue(0) + .shouldContain("Hello Docker"); + } +} + diff --git a/test/runtime/containers/docker/Dockerfile-BasicTest b/test/runtime/containers/docker/Dockerfile-BasicTest new file mode 100644 index 000000000..166c96928 --- /dev/null +++ b/test/runtime/containers/docker/Dockerfile-BasicTest @@ -0,0 +1,8 @@ +FROM oraclelinux:7.2 +MAINTAINER mikhailo.seledtsov@oracle.com + +COPY /jdk /jdk + +ENV JAVA_HOME=/jdk + +CMD ["/bin/bash"] diff --git a/test/runtime/containers/docker/Dockerfile-BasicTest-aarch64 b/test/runtime/containers/docker/Dockerfile-BasicTest-aarch64 new file mode 100644 index 000000000..082a4d89e --- /dev/null +++ b/test/runtime/containers/docker/Dockerfile-BasicTest-aarch64 @@ -0,0 +1,8 @@ +# Use generic ubuntu Linux on AArch64 +FROM aarch64/ubuntu + +COPY /jdk /jdk + +ENV JAVA_HOME=/jdk + +CMD ["/bin/bash"] diff --git a/test/runtime/containers/docker/Dockerfile-BasicTest-ppc64le b/test/runtime/containers/docker/Dockerfile-BasicTest-ppc64le new file mode 100644 index 000000000..57dfb0f86 --- /dev/null +++ b/test/runtime/containers/docker/Dockerfile-BasicTest-ppc64le @@ -0,0 +1,10 @@ +# test on x86_64 uses Oracle Linux but we do not have this for ppc64le +# so use some other Linux where OpenJDK works +# FROM oraclelinux:7.2 +FROM ppc64le/ubuntu + +COPY /jdk /jdk + +ENV JAVA_HOME=/jdk + +CMD ["/bin/bash"] diff --git a/test/runtime/containers/docker/Dockerfile-BasicTest-s390x b/test/runtime/containers/docker/Dockerfile-BasicTest-s390x new file mode 100644 index 000000000..940b36ede --- /dev/null +++ b/test/runtime/containers/docker/Dockerfile-BasicTest-s390x @@ -0,0 +1,7 @@ +FROM s390x/ubuntu + +COPY /jdk /jdk + +ENV JAVA_HOME=/jdk + +CMD ["/bin/bash"] diff --git a/test/runtime/containers/docker/HelloDocker.java b/test/runtime/containers/docker/HelloDocker.java new file mode 100644 index 000000000..5fe7a5ae0 --- /dev/null +++ b/test/runtime/containers/docker/HelloDocker.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public class HelloDocker { + public static void main(String args[]) { + System.out.println("Hello Docker"); + } +} diff --git a/test/runtime/containers/docker/PrintContainerInfo.java b/test/runtime/containers/docker/PrintContainerInfo.java new file mode 100644 index 000000000..b06047729 --- /dev/null +++ b/test/runtime/containers/docker/PrintContainerInfo.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import sun.hotspot.WhiteBox; + +public class PrintContainerInfo { + + public static void main(String[] args) { + System.out.println("PrintContainerInfo: Entering"); + WhiteBox wb = WhiteBox.getWhiteBox(); + + wb.printOsInfo(); + } +} diff --git a/test/runtime/containers/docker/TEST.properties b/test/runtime/containers/docker/TEST.properties new file mode 100644 index 000000000..3d748e1ab --- /dev/null +++ b/test/runtime/containers/docker/TEST.properties @@ -0,0 +1 @@ +exclusiveAccess.dirs=. diff --git a/test/runtime/containers/docker/TestCPUAwareness.java b/test/runtime/containers/docker/TestCPUAwareness.java new file mode 100644 index 000000000..fe7d4499b --- /dev/null +++ b/test/runtime/containers/docker/TestCPUAwareness.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test JVM's CPU resource awareness when running inside docker container + * @library /testlibrary + * @run driver TestCPUAwareness + */ + +import java.util.List; +import com.oracle.java.testlibrary.Common; +import com.oracle.java.testlibrary.DockerTestUtils; +import com.oracle.java.testlibrary.DockerRunOptions; + +public class TestCPUAwareness { +private static final String imageName = Common.imageName("cpu"); + private static final int availableCPUs = Runtime.getRuntime().availableProcessors(); + + public static void main(String[] args) throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + + System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); + DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); + + try { + // cpuset, period, shares, expected Active Processor Count + testComboWithCpuSets(); + + // cpu shares - it should be safe to use CPU shares exceeding available CPUs + testCpuShares(256, 1); + testCpuShares(2048, 2); + testCpuShares(4096, 4); + + // leave one CPU for system and tools, otherwise this test may be unstable + int maxNrOfAvailableCpus = availableCPUs - 1; + for (int i=1; i < maxNrOfAvailableCpus; i = i * 2) { + testCpus(i, i); + } + + // If ActiveProcessorCount is set, the VM should use it, regardless of other + // container settings, host settings or available CPUs on the host. + testActiveProcessorCount(1, 1); + testActiveProcessorCount(2, 2); + + // cpu quota and period + testCpuQuotaAndPeriod(50*1000, 100*1000); + testCpuQuotaAndPeriod(100*1000, 100*1000); + testCpuQuotaAndPeriod(150*1000, 100*1000); + testCpuQuotaAndPeriod(400*1000, 100*1000); + + } finally { + DockerTestUtils.removeDockerImage(imageName); + } + } + + + private static void testComboWithCpuSets() throws Exception { + String cpuSetStr = CPUSetsReader.readFromProcStatus("Cpus_allowed_list"); + System.out.println("cpuSetStr = " + cpuSetStr); + + if (cpuSetStr == null) { + System.out.printf("The cpuset test cases are skipped"); + } else { + List cpuSet = CPUSetsReader.parseCpuSet(cpuSetStr); + + // Test subset of cpuset with one element + if (cpuSet.size() >= 1) { + String testCpuSet = CPUSetsReader.listToString(cpuSet, 1); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 1); + } + + // Test subset of cpuset with two elements + if (cpuSet.size() >= 2) { + String testCpuSet = CPUSetsReader.listToString(cpuSet, 2); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 2); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); + } + + // Test subset of cpuset with three elements + if (cpuSet.size() >= 3) { + String testCpuSet = CPUSetsReader.listToString(cpuSet, 3); + testAPCCombo(testCpuSet, 100*1000, 100*1000, 2*1024, true, 1); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); + testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); + } + } + } + + + private static void testActiveProcessorCount(int valueToSet, int expectedValue) throws Exception { + Common.logNewTestCase("Test ActiveProcessorCount: valueToSet = " + valueToSet); + + DockerRunOptions opts = Common.newOpts(imageName) + .addJavaOpts("-XX:ActiveProcessorCount=" + valueToSet, "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintActiveCpus"); + Common.run(opts) + .shouldMatch("active processor count set by user.*" + expectedValue); + } + + + private static void testCpus(int valueToSet, int expectedTraceValue) throws Exception { + Common.logNewTestCase("test cpus: " + valueToSet); + DockerRunOptions opts = Common.newOpts(imageName) + .addDockerOpts("--cpus", "" + valueToSet); + Common.run(opts) + .shouldMatch("active_processor_count.*" + expectedTraceValue); + } + + + // Expected active processor count can not exceed available CPU count + private static int adjustExpectedAPCForAvailableCPUs(int expectedAPC) { + if (expectedAPC > availableCPUs) { + expectedAPC = availableCPUs; + System.out.println("Adjusted expectedAPC = " + expectedAPC); + } + return expectedAPC; + } + + + private static void testCpuQuotaAndPeriod(int quota, int period) + throws Exception { + Common.logNewTestCase("test cpu quota and period: "); + System.out.println("quota = " + quota); + System.out.println("period = " + period); + + int expectedAPC = (int) Math.ceil((float) quota / (float) period); + System.out.println("expectedAPC = " + expectedAPC); + expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); + + DockerRunOptions opts = Common.newOpts(imageName) + .addDockerOpts("--cpu-period=" + period) + .addDockerOpts("--cpu-quota=" + quota); + + Common.run(opts) + .shouldMatch("CPU Period is.*" + period) + .shouldMatch("CPU Quota is.*" + quota) + .shouldMatch("active_processor_count.*" + expectedAPC); + } + + + // Test correctess of automatically selected active processor cound + private static void testAPCCombo(String cpuset, int quota, int period, int shares, + boolean usePreferContainerQuotaForCPUCount, + int expectedAPC) throws Exception { + Common.logNewTestCase("test APC Combo"); + System.out.println("cpuset = " + cpuset); + System.out.println("quota = " + quota); + System.out.println("period = " + period); + System.out.println("shares = " + period); + System.out.println("usePreferContainerQuotaForCPUCount = " + usePreferContainerQuotaForCPUCount); + System.out.println("expectedAPC = " + expectedAPC); + + expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); + + DockerRunOptions opts = Common.newOpts(imageName) + .addDockerOpts("--cpuset-cpus", "" + cpuset) + .addDockerOpts("--cpu-period=" + period) + .addDockerOpts("--cpu-quota=" + quota) + .addDockerOpts("--cpu-shares=" + shares); + + if (!usePreferContainerQuotaForCPUCount) opts.addJavaOpts("-XX:-PreferContainerQuotaForCPUCount"); + + Common.run(opts) + .shouldMatch("active_processor_count.*" + expectedAPC); + } + + + private static void testCpuShares(int shares, int expectedAPC) throws Exception { + Common.logNewTestCase("test cpu shares, shares = " + shares); + System.out.println("expectedAPC = " + expectedAPC); + + expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); + + DockerRunOptions opts = Common.newOpts(imageName) + .addDockerOpts("--cpu-shares=" + shares); + Common.run(opts) + .shouldMatch("CPU Shares is.*" + shares) + .shouldMatch("active_processor_count.*" + expectedAPC); + } +} diff --git a/test/runtime/containers/docker/TestCPUSets.java b/test/runtime/containers/docker/TestCPUSets.java new file mode 100644 index 000000000..baf0eba17 --- /dev/null +++ b/test/runtime/containers/docker/TestCPUSets.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test JVM's awareness of cpu sets (cpus and mems) + * @library /testlibrary /testlibrary/whitebox + * @build AttemptOOM CPUSetsReader sun.hotspot.WhiteBox PrintContainerInfo + * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission + * @run driver TestCPUSets + */ + +import java.util.List; +import com.oracle.java.testlibrary.Common; +import com.oracle.java.testlibrary.DockerRunOptions; +import com.oracle.java.testlibrary.DockerTestUtils; +import com.oracle.java.testlibrary.Asserts; +import com.oracle.java.testlibrary.Platform; +import com.oracle.java.testlibrary.Utils; +import com.oracle.java.testlibrary.OutputAnalyzer; + + +public class TestCPUSets { + private static final String imageName = Common.imageName("cpusets"); + + public static void main(String[] args) throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + + Common.prepareWhiteBox(); + DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); + + try { + // Sanity test the cpu sets reader and parser + CPUSetsReader.test(); + testTheSet("Cpus_allowed_list"); + testTheSet("Mems_allowed_list"); + } finally { + DockerTestUtils.removeDockerImage(imageName); + } + } + + + private static void testTheSet(String setType) throws Exception { + String cpuSetStr = CPUSetsReader.readFromProcStatus(setType); + + if (cpuSetStr == null) { + System.out.printf("The %s test is skipped %n", setType); + } else { + List cpuSet = CPUSetsReader.parseCpuSet(cpuSetStr); + + // Test subset of one, full subset, and half of the subset + testCpuSet(CPUSetsReader.listToString(cpuSet, 1)); + if (cpuSet.size() > 1) { + testCpuSet(CPUSetsReader.listToString(cpuSet)); + } + if (cpuSet.size() > 2) { + testCpuSet(CPUSetsReader.listToString(cpuSet, cpuSet.size()/2 )); + } + } + } + + + private static DockerRunOptions commonOpts() { + DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", + "PrintContainerInfo"); + opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/"); + opts.addJavaOpts("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintContainerInfo", "-cp", "/test-classes/"); + Common.addWhiteBoxOpts(opts); + return opts; + } + + + private static void checkResult(List lines, String lineMarker, String value) { + boolean lineMarkerFound = false; + + for (String line : lines) { + if (line.contains(lineMarker)) { + lineMarkerFound = true; + String[] parts = line.split(":"); + System.out.println("DEBUG: line = " + line); + System.out.println("DEBUG: parts.length = " + parts.length); + + Asserts.assertEquals(parts.length, 2); + String set = parts[1].replaceAll("\\s",""); + String actual = CPUSetsReader.listToString(CPUSetsReader.parseCpuSet(set)); + Asserts.assertEquals(actual, value); + break; + } + } + Asserts.assertTrue(lineMarkerFound); + } + + + private static void testCpuSet(String value) throws Exception { + Common.logNewTestCase("cpusets.cpus, value = " + value); + + DockerRunOptions opts = commonOpts(); + opts.addDockerOpts("--cpuset-cpus=" + value); + + List lines = Common.run(opts).asLines(); + checkResult(lines, "cpuset.cpus is:", value); + } + + private static void testMemSet(String value) throws Exception { + Common.logNewTestCase("cpusets.mems, value = " + value); + + DockerRunOptions opts = commonOpts(); + opts.addDockerOpts("--cpuset-mems=" + value); + + List lines = Common.run(opts).asLines(); + checkResult(lines, "cpuset.mems is:", value); + } + +} diff --git a/test/runtime/containers/docker/TestMemoryAwareness.java b/test/runtime/containers/docker/TestMemoryAwareness.java new file mode 100644 index 000000000..415f2c91b --- /dev/null +++ b/test/runtime/containers/docker/TestMemoryAwareness.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test JVM's memory resource awareness when running inside docker container + * @library /testlibrary /testlibrary/whitebox + * @build AttemptOOM sun.hotspot.WhiteBox PrintContainerInfo + * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission + * @run driver TestMemoryAwareness + */ + +import com.oracle.java.testlibrary.Common; +import com.oracle.java.testlibrary.DockerRunOptions; +import com.oracle.java.testlibrary.DockerTestUtils; + + +public class TestMemoryAwareness { + private static final String imageName = Common.imageName("memory"); + + public static void main(String[] args) throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + + Common.prepareWhiteBox(); + DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); + + try { + testMemoryLimit("100m", "104857600"); + testMemoryLimit("500m", "524288000"); + testMemoryLimit("1g", "1073741824"); + testMemoryLimit("4g", "4294967296"); + + testMemorySoftLimit("500m", "524288000"); + testMemorySoftLimit("1g", "1073741824"); + + // Add extra 10 Mb to allocator limit, to be sure to cause OOM + testOOM("256m", 256 + 10); + + } finally { + DockerTestUtils.removeDockerImage(imageName); + } + } + + + private static void testMemoryLimit(String valueToSet, String expectedTraceValue) + throws Exception { + + Common.logNewTestCase("memory limit: " + valueToSet); + + DockerRunOptions opts = Common.newOpts(imageName) + .addDockerOpts("--memory", valueToSet); + + Common.run(opts) + .shouldMatch("Memory Limit is:.*" + expectedTraceValue); + } + + + private static void testMemorySoftLimit(String valueToSet, String expectedTraceValue) + throws Exception { + Common.logNewTestCase("memory soft limit: " + valueToSet); + + DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo"); + Common.addWhiteBoxOpts(opts); + opts.addDockerOpts("--memory-reservation=" + valueToSet); + + Common.run(opts) + .shouldMatch("Memory Soft Limit.*" + expectedTraceValue); + } + + + // provoke OOM inside the container, see how VM reacts + private static void testOOM(String dockerMemLimit, int sizeToAllocInMb) throws Exception { + Common.logNewTestCase("OOM"); + + DockerRunOptions opts = Common.newOpts(imageName, "AttemptOOM") + .addDockerOpts("--memory", dockerMemLimit, "--memory-swap", dockerMemLimit); + opts.classParams.add("" + sizeToAllocInMb); + + DockerTestUtils.dockerRunJava(opts) + .shouldHaveExitValue(1) + .shouldContain("Entering AttemptOOM main") + .shouldNotContain("AttemptOOM allocation successful") + .shouldContain("java.lang.OutOfMemoryError"); + } + +} diff --git a/test/runtime/containers/docker/TestMisc.java b/test/runtime/containers/docker/TestMisc.java new file mode 100644 index 000000000..efd52278c --- /dev/null +++ b/test/runtime/containers/docker/TestMisc.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test miscellanous functionality related to JVM running in docker container + * @library /testlibrary /testlibrary/whitebox + * @build CheckContainerized sun.hotspot.WhiteBox PrintContainerInfo + * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission + * @run driver TestMisc + */ + +import com.oracle.java.testlibrary.Common; +import com.oracle.java.testlibrary.DockerTestUtils; +import com.oracle.java.testlibrary.DockerRunOptions; +import com.oracle.java.testlibrary.OutputAnalyzer; +import com.oracle.java.testlibrary.ProcessTools; + + +public class TestMisc { + private static final String imageName = Common.imageName("misc"); + + public static void main(String[] args) throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + + Common.prepareWhiteBox(); + DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); + + try { + testMinusContainerSupport(); + testIsContainerized(); + testPrintContainerInfo(); + } finally { + DockerTestUtils.removeDockerImage(imageName); + } + } + + + private static void testMinusContainerSupport() throws Exception { + Common.logNewTestCase("Test related flags: '-UseContainerSupport'"); + DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "-version"); + opts.addJavaOpts("-XX:+UnlockDiagnosticVMOptions", "-XX:-UseContainerSupport", "-XX:+PrintContainerInfo"); + + Common.run(opts) + .shouldContain("Container Support not enabled"); + } + + + private static void testIsContainerized() throws Exception { + Common.logNewTestCase("Test is_containerized() inside a docker container"); + + DockerRunOptions opts = Common.newOpts(imageName, "CheckContainerized"); + Common.addWhiteBoxOpts(opts); + + Common.run(opts) + .shouldContain(CheckContainerized.INSIDE_A_CONTAINER); + } + + + private static void testPrintContainerInfo() throws Exception { + Common.logNewTestCase("Test print_container_info()"); + + DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo"); + Common.addWhiteBoxOpts(opts); + + checkContainerInfo(Common.run(opts)); + } + + + private static void checkContainerInfo(OutputAnalyzer out) throws Exception { + String[] expectedToContain = new String[] { + "cpuset.cpus", + "cpuset.mems", + "CPU Shares", + "CPU Quota", + "CPU Period", + "OSContainer::active_processor_count", + "Memory Limit", + "Memory Soft Limit", + "Memory Usage", + "Maximum Memory Usage", + "memory_max_usage_in_bytes" + }; + + for (String s : expectedToContain) { + out.shouldContain(s); + } + } + +} diff --git a/test/testlibrary/ClassFileInstaller.java b/test/testlibrary/ClassFileInstaller.java index 303e96e5a..d319e45ce 100644 --- a/test/testlibrary/ClassFileInstaller.java +++ b/test/testlibrary/ClassFileInstaller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,28 +21,229 @@ * questions. */ +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; import java.io.InputStream; +import java.io.ByteArrayInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** - * Dump a class file for a class on the class path in the current directory + * Dump a class file for a class on the class path in the current directory, or + * in the specified JAR file. This class is usually used when you build a class + * from a test library, but want to use this class in a sub-process. + * + * For example, to build the following library class: + * test/lib/sun/hotspot/WhiteBox.java + * + * You would use the following tags: + * + * @library /test/lib + * @build sun.hotspot.WhiteBox + * + * JTREG would build the class file under + * ${JTWork}/classes/test/lib/sun/hotspot/WhiteBox.class + * + * With you run your main test class using "@run main MyMainClass", JTREG would setup the + * -classpath to include "${JTWork}/classes/test/lib/", so MyMainClass would be able to + * load the WhiteBox class. + * + * However, if you run a sub process, and do not wish to use the exact same -classpath, + * You can use ClassFileInstaller to ensure that WhiteBox is available in the current + * directory of your test: + * + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * + * Or, you can use the -jar option to store the class in the specified JAR file. If a relative + * path name is given, the JAR file would be relative to the current directory of + * + * @run main ClassFileInstaller -jar myjar.jar sun.hotspot.WhiteBox */ public class ClassFileInstaller { + /** + * You can enable debug tracing of ClassFileInstaller by running JTREG with + * jtreg -DClassFileInstaller.debug=true ... + */ + public static boolean DEBUG = Boolean.getBoolean("ClassFileInstaller.debug"); + /** * @param args The names of the classes to dump * @throws Exception */ public static void main(String... args) throws Exception { - for (String arg : args) { - ClassLoader cl = ClassFileInstaller.class.getClassLoader(); + if (args.length > 1 && args[0].equals("-jar")) { + if (args.length < 2) { + throw new RuntimeException("Usage: ClassFileInstaller \n" + + "where possible options include:\n" + + " -jar Write to the JAR file "); + } + writeJar(args[1], null, args, 2, args.length); + } else { + if (DEBUG) { + System.out.println("ClassFileInstaller: Writing to " + System.getProperty("user.dir")); + } + for (String arg : args) { + writeClassToDisk(arg); + } + } + } + + public static class Manifest { + private InputStream in; + + private Manifest(InputStream in) { + this.in = in; + } + + static Manifest fromSourceFile(String fileName) throws Exception { + String pathName = System.getProperty("test.src") + File.separator + fileName; + return new Manifest(new FileInputStream(pathName)); + } + + // Example: + // String manifest = "Premain-Class: RedefineClassHelper\n" + + // "Can-Redefine-Classes: true\n"; + // ClassFileInstaller.writeJar("redefineagent.jar", + // ClassFileInstaller.Manifest.fromString(manifest), + // "RedefineClassHelper"); + static Manifest fromString(String manifest) throws Exception { + return new Manifest(new ByteArrayInputStream(manifest.getBytes())); + } + + public InputStream getInputStream() { + return in; + } + } + + private static void writeJar(String jarFile, Manifest manifest, String classes[], int from, int to) throws Exception { + if (DEBUG) { + System.out.println("ClassFileInstaller: Writing to " + getJarPath(jarFile)); + } + + (new File(jarFile)).delete(); + FileOutputStream fos = new FileOutputStream(jarFile); + ZipOutputStream zos = new ZipOutputStream(fos); + + // The manifest must be the first or second entry. See comments in JarInputStream + // constructor and JDK-5046178. + if (manifest != null) { + writeToDisk(zos, "META-INF/MANIFEST.MF", manifest.getInputStream()); + } + + for (int i=from; i 0) { + pathName = prependPath + "/" + pathName; + } + writeToDisk(zos, pathName, is); + } + + public static void writeClassToDisk(String className, byte[] bytecode) throws Exception { + writeClassToDisk(null, className, bytecode); + } + private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode) throws Exception { + writeClassToDisk(zos, className, bytecode, ""); + } + + public static void writeClassToDisk(String className, byte[] bytecode, String prependPath) throws Exception { + writeClassToDisk(null, className, bytecode, prependPath); + } + private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode, String prependPath) throws Exception { + // Convert dotted class name to a path to a class file + String pathName = className.replace('.', '/').concat(".class"); + if (prependPath.length() > 0) { + pathName = prependPath + "/" + pathName; + } + writeToDisk(zos, pathName, new ByteArrayInputStream(bytecode)); + } + + private static void writeToDisk(ZipOutputStream zos, String pathName, InputStream is) throws Exception { + if (DEBUG) { + System.out.println("ClassFileInstaller: Writing " + pathName); + } + if (zos != null) { + ZipEntry ze = new ZipEntry(pathName); + zos.putNextEntry(ze); + byte[] buf = new byte[1024]; + int len; + while ((len = is.read(buf))>0){ + zos.write(buf, 0, len); + } + } else { // Create the class file's package directory Path p = Paths.get(pathName); if (pathName.contains("/")) { @@ -51,5 +252,6 @@ public class ClassFileInstaller { // Create the class file Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); } + is.close(); } } diff --git a/test/testlibrary/com/oracle/java/testlibrary/Common.java b/test/testlibrary/com/oracle/java/testlibrary/Common.java new file mode 100644 index 000000000..3d5a52943 --- /dev/null +++ b/test/testlibrary/com/oracle/java/testlibrary/Common.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.java.testlibrary; + +/* + * Methods and definitions common to docker tests container in this directory + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import com.oracle.java.testlibrary.DockerTestUtils; +import com.oracle.java.testlibrary.DockerRunOptions; +import com.oracle.java.testlibrary.Utils; +import com.oracle.java.testlibrary.OutputAnalyzer; + + +public class Common { + public static final String imageNameAndTag = "jdk-internal:test"; + + public static String imageName(String suffix) { + return imageNameAndTag + "-" + suffix; + } + + public static void prepareWhiteBox() throws Exception { + Path whiteboxPath = Paths.get(Utils.TEST_CLASSES, "whitebox.jar"); + if( !Files.exists(whiteboxPath) ) { + Files.copy(Paths.get(new File("whitebox.jar").getAbsolutePath()), + Paths.get(Utils.TEST_CLASSES, "whitebox.jar")); + } + } + + // create simple commonly used options + public static DockerRunOptions newOpts(String imageNameAndTag) { + return new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "-version") + .addJavaOpts("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintContainerInfo"); + } + + + // create commonly used options with class to be launched inside container + public static DockerRunOptions newOpts(String imageNameAndTag, String testClass) { + DockerRunOptions opts = + new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", testClass); + opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/"); + opts.addJavaOpts("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintContainerInfo", "-cp", "/test-classes/"); + return opts; + } + + public static DockerRunOptions addWhiteBoxOpts(DockerRunOptions opts) { + opts.addJavaOpts("-Xbootclasspath/a:/test-classes/whitebox.jar", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI"); + return opts; + } + + // most common type of run and checks + public static OutputAnalyzer run(DockerRunOptions opts) throws Exception { + return DockerTestUtils.dockerRunJava(opts) + .shouldHaveExitValue(0).shouldContain("Initializing Container Support"); + } + + + // log beginning of a test case + public static void logNewTestCase(String msg) { + System.out.println("========== NEW TEST CASE: " + msg); + } + +} diff --git a/test/testlibrary/com/oracle/java/testlibrary/DockerRunOptions.java b/test/testlibrary/com/oracle/java/testlibrary/DockerRunOptions.java new file mode 100644 index 000000000..05e3b6bf6 --- /dev/null +++ b/test/testlibrary/com/oracle/java/testlibrary/DockerRunOptions.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.java.testlibrary; + +import java.util.ArrayList; +import java.util.Collections; + + +// This class represents options for running java inside docker containers +// in test environment. +public class DockerRunOptions { + public String imageNameAndTag; + public ArrayList dockerOpts = new ArrayList(); + public String command; // normally a full path to java + public ArrayList javaOpts = new ArrayList(); + public String classToRun; // class or "-version" + public ArrayList classParams = new ArrayList(); + + public boolean tty = true; + public boolean removeContainerAfterUse = true; + public boolean appendTestJavaOptions = true; + public boolean retainChildStdout = false; + + /** + * Convenience constructor for most common use cases in testing. + * @param imageNameAndTag a string representing name and tag for the + * docker image to run, as "name:tag" + * @param javaCmd a java command to run (e.g. /jdk/bin/java) + * @param classToRun a class to run, or "-version" + * @param javaOpts java options to use + * + * @return Default docker run options + */ + public DockerRunOptions(String imageNameAndTag, String javaCmd, + String classToRun, String... javaOpts) { + this.imageNameAndTag = imageNameAndTag; + this.command = javaCmd; + this.classToRun = classToRun; + this.addJavaOpts(javaOpts); + } + + public DockerRunOptions addDockerOpts(String... opts) { + Collections.addAll(dockerOpts, opts); + return this; + } + + public DockerRunOptions addJavaOpts(String... opts) { + Collections.addAll(javaOpts, opts); + return this; + } + +} diff --git a/test/testlibrary/com/oracle/java/testlibrary/DockerTestUtils.java b/test/testlibrary/com/oracle/java/testlibrary/DockerTestUtils.java new file mode 100644 index 000000000..ea125b109 --- /dev/null +++ b/test/testlibrary/com/oracle/java/testlibrary/DockerTestUtils.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.java.testlibrary; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.oracle.java.testlibrary.Utils; +import com.oracle.java.testlibrary.Platform; +import com.oracle.java.testlibrary.OutputAnalyzer; +import com.oracle.java.testlibrary.ProcessTools; + +public class DockerTestUtils { + private static final String FS = File.separator; + private static boolean isDockerEngineAvailable = false; + private static boolean wasDockerEngineChecked = false; + + // Diagnostics: set to true to enable more diagnostic info + private static final boolean DEBUG = false; + + /** + * Optimized check of whether the docker engine is available in a given + * environment. Checks only once, then remembers the result in a singleton. + * + * @return true if docker engine is available + * @throws Exception + */ + public static boolean isDockerEngineAvailable() throws Exception { + if (wasDockerEngineChecked) + return isDockerEngineAvailable; + + isDockerEngineAvailable = isDockerEngineAvailableCheck(); + wasDockerEngineChecked = true; + return isDockerEngineAvailable; + } + + + /** + * Convenience method, will check if docker engine is available and usable; + * will print the appropriate message when not available. + * + * @return true if docker engine is available + * @throws Exception + */ + public static boolean canTestDocker() throws Exception { + if (isDockerEngineAvailable()) { + return true; + } else { + System.out.println("Docker engine is not available on this system"); + System.out.println("This test is SKIPPED"); + return false; + } + } + + + /** + * Simple check - is docker engine available, accessible and usable. + * Run basic docker command: 'docker ps' - list docker instances. + * If docker engine is available and accesible then true is returned + * and we can proceed with testing docker. + * + * @return true if docker engine is available and usable + * @throws Exception + */ + private static boolean isDockerEngineAvailableCheck() throws Exception { + try { + execute("docker", "ps") + .shouldHaveExitValue(0) + .shouldContain("CONTAINER") + .shouldContain("IMAGE"); + } catch (Exception e) { + return false; + } + return true; + } + + + /** + * Build a docker image that contains JDK under test. + * The jdk will be placed under the "/jdk/" folder inside the docker file system. + * + * @param imageName name of the image to be created, including version tag + * @param dockerfile name of the dockerfile residing in the test source; + * we check for a platform specific dockerfile as well + * and use this one in case it exists + * @param buildDirName name of the docker build/staging directory, which will + * be created in the jtreg's scratch folder + * @throws Exception + */ + public static void + buildJdkDockerImage(String imageName, String dockerfile, String buildDirName) + throws Exception { + Path buildDir = Paths.get(".", buildDirName); + if (Files.exists(buildDir)) { + throw new RuntimeException("The docker build directory already exists: " + buildDir); + } + // check for the existance of a platform specific docker file as well + String platformSpecificDockerfile = dockerfile + "-" + Platform.getOsArch(); + if (Files.exists(Paths.get(Utils.TEST_SRC, platformSpecificDockerfile))) { + dockerfile = platformSpecificDockerfile; + } + + Path jdkSrcDir = Paths.get(Utils.TEST_JDK); + Path jdkDstDir = buildDir.resolve("jdk"); + + Files.createDirectories(jdkDstDir); + + // Copy JDK-under-test tree to the docker build directory. + // This step is required for building a docker image. + Files.walkFileTree(jdkSrcDir, new CopyFileVisitor(jdkSrcDir, jdkDstDir)); + buildDockerImage(imageName, Paths.get(Utils.TEST_SRC, dockerfile), buildDir); + } + + + /** + * Build a docker image based on given docker file and docker build directory. + * + * @param imageName name of the image to be created, including version tag + * @param dockerfile path to the Dockerfile to be used for building the docker + * image. The specified dockerfile will be copied to the docker build + * directory as 'Dockerfile' + * @param buildDir build directory; it should already contain all the content + * needed to build the docker image. + * @throws Exception + */ + public static void + buildDockerImage(String imageName, Path dockerfile, Path buildDir) throws Exception { + // Copy docker file to the build dir + Files.copy(dockerfile, buildDir.resolve("Dockerfile")); + + // Build the docker + execute("docker", "build", "--no-cache", "--tag", imageName, buildDir.toString()) + .shouldHaveExitValue(0) + .shouldContain("Successfully built"); + } + + + /** + * Run Java inside the docker image with specified parameters and options. + * + * @param DockerRunOptions optins for running docker + * + * @return output of the run command + * @throws Exception + */ + public static OutputAnalyzer dockerRunJava(DockerRunOptions opts) throws Exception { + ArrayList cmd = new ArrayList<>(); + + cmd.add("docker"); + cmd.add("run"); + if (opts.tty) + cmd.add("--tty=true"); + if (opts.removeContainerAfterUse) + cmd.add("--rm"); + + cmd.addAll(opts.dockerOpts); + cmd.add(opts.imageNameAndTag); + cmd.add(opts.command); + + cmd.addAll(opts.javaOpts); + if (opts.appendTestJavaOptions) { + Collections.addAll(cmd, Utils.getTestJavaOpts()); + } + + cmd.add(opts.classToRun); + cmd.addAll(opts.classParams); + return execute(cmd); + } + + + /** + * Remove docker image + * + * @param DockerRunOptions optins for running docker + * @return output of the command + * @throws Exception + */ + public static OutputAnalyzer removeDockerImage(String imageNameAndTag) throws Exception { + return execute("docker", "rmi", "--force", imageNameAndTag); + } + + + + /** + * Convenience method - express command as sequence of strings + * + * @param command to execute + * @return The output from the process + * @throws Exception + */ + public static OutputAnalyzer execute(List command) throws Exception { + return execute(command.toArray(new String[command.size()])); + } + + + /** + * Execute a specified command in a process, report diagnostic info. + * + * @param command to be executed + * @return The output from the process + * @throws Exception + */ + public static OutputAnalyzer execute(String... command) throws Exception { + + ProcessBuilder pb = new ProcessBuilder(command); + System.out.println("[COMMAND]\n" + Utils.getCommandLine(pb)); + + long started = System.currentTimeMillis(); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + + System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); + System.out.println("[STDERR]\n" + output.getStderr()); + System.out.println("[STDOUT]\n" + output.getStdout()); + + return output; + } + + + private static class CopyFileVisitor extends SimpleFileVisitor { + private final Path src; + private final Path dst; + + public CopyFileVisitor(Path src, Path dst) { + this.src = src; + this.dst = dst; + } + + + @Override + public FileVisitResult preVisitDirectory(Path file, + BasicFileAttributes attrs) throws IOException { + Path dstDir = dst.resolve(src.relativize(file)); + if (!dstDir.toFile().exists()) { + Files.createDirectories(dstDir); + } + return FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) throws IOException { + if (!file.toFile().isFile()) { + return FileVisitResult.CONTINUE; + } + Path dstFile = dst.resolve(src.relativize(file)); + Files.copy(file, dstFile, StandardCopyOption.COPY_ATTRIBUTES); + return FileVisitResult.CONTINUE; + } + } +} diff --git a/test/testlibrary/com/oracle/java/testlibrary/Platform.java b/test/testlibrary/com/oracle/java/testlibrary/Platform.java index 93841bf59..6a1407934 100644 --- a/test/testlibrary/com/oracle/java/testlibrary/Platform.java +++ b/test/testlibrary/com/oracle/java/testlibrary/Platform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,7 @@ public class Platform { private static final String dataModel = System.getProperty("sun.arch.data.model"); private static final String vmVersion = System.getProperty("java.vm.version"); private static final String osArch = System.getProperty("os.arch"); - private static final String vmName = System.getProperty("java.vm.name"); + public static final String vmName = System.getProperty("java.vm.name"); private static final String userName = System.getProperty("user.name"); public static boolean isClient() { diff --git a/test/testlibrary/com/oracle/java/testlibrary/Utils.java b/test/testlibrary/com/oracle/java/testlibrary/Utils.java index b332c1e3a..902e3b138 100644 --- a/test/testlibrary/com/oracle/java/testlibrary/Utils.java +++ b/test/testlibrary/com/oracle/java/testlibrary/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,6 +61,15 @@ public final class Utils { */ public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim(); + public static final String TEST_JDK = System.getProperty("test.jdk"); + + public static final String COMPILE_JDK= System.getProperty("compile.jdk", TEST_JDK); + + public static final String TEST_SRC = System.getProperty("test.src", "").trim(); + + public static final String TEST_CLASSES = System.getProperty("test.classes", "."); + + private static Unsafe unsafe = null; /** diff --git a/test/testlibrary/whitebox/sun/hotspot/WhiteBox.java b/test/testlibrary/whitebox/sun/hotspot/WhiteBox.java index 69d38a512..d2f49da47 100644 --- a/test/testlibrary/whitebox/sun/hotspot/WhiteBox.java +++ b/test/testlibrary/whitebox/sun/hotspot/WhiteBox.java @@ -238,4 +238,9 @@ public class WhiteBox { // Returns true on linux if library has the noexecstack flag set. public native boolean checkLibSpecifiesNoexecstack(String libfilename); + + // Container testing + public native boolean isContainerized(); + public native void printOsInfo(); + } -- GitLab