/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.entity.java; import static org.testng.Assert.fail; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool; import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmd; import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; @SuppressWarnings({ "rawtypes", "unchecked" }) public class JavaOptsTest extends BrooklynAppUnitTestSupport { // TODO Test setting classpath; but this works by customize() copying all the artifacts into /lib/* // so that we can simply set this on the classpath... private static final Logger log = LoggerFactory.getLogger(JavaOptsTest.class); private SshMachineLocation loc; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { RecordingSshTool.clear(); super.setUp(); loc = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) .configure("address", "localhost") .configure(SshMachineLocation.SSH_TOOL_CLASS, RecordingSshTool.class.getName())); } @AfterMethod(alwaysRun=true) @Override public void tearDown() throws Exception { super.tearDown(); RecordingSshTool.clear(); } @Test public void testSimpleLaunchesJavaProcess() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) .configure("main", "my.Main").configure("useJmx", false)); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); Map<String,String> expectedEnvs = ImmutableMap.<String,String>of(); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } @Test public void testPassesJavaArgs() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) .configure("main", "my.Main").configure("useJmx", false).configure("args", ImmutableList.of("a1", "a2"))); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); Map<String,String> expectedEnvs = ImmutableMap.<String,String>of(); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main \"a1\" \"a2\" >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } @Test public void testPassesJavaOpts() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) .configure("main", "my.Main").configure("useJmx", false).configure("javaOpts", ImmutableList.of("-abc"))); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); String defaultJavaOpts = "-Xms128m -Xmx512m -XX:MaxPermSize=512m"; String expectedJavaOpts = defaultJavaOpts+" -abc"; Map<String,String> expectedEnvs = ImmutableMap.<String,String>of("JAVA_OPTS", expectedJavaOpts); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } @Test public void testPassesJavaSysProps() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) .configure("main", "my.Main").configure("useJmx", false).configure("javaSysProps", ImmutableMap.of("mykey", "myval"))); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); String defaultJavaOpts = "-Xms128m -Xmx512m -XX:MaxPermSize=512m"; String expectedJavaOpts = defaultJavaOpts+" -Dmykey=myval"; Map<String,String> expectedEnvs = ImmutableMap.<String,String>of("JAVA_OPTS", expectedJavaOpts); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } @Test public void testPassesJavaOptsOverridingDefaults() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) .configure("main", "my.Main").configure("useJmx", false).configure("javaOpts", ImmutableList.of("-Xmx567m", "-XX:MaxPermSize=567m"))); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); Object expectedJavaOpts = MutableSet.of("-Xms128m", "-Xmx567m", "-XX:MaxPermSize=567m"); Map<String,Object> expectedEnvs = ImmutableMap.<String,Object>of("JAVA_OPTS", expectedJavaOpts); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } public static class TestingJavaOptsVanillaJavaAppImpl extends VanillaJavaAppImpl { @Override public VanillaJavaAppSshDriver newDriver(MachineLocation loc) { return new VanillaJavaAppSshDriver(this, (SshMachineLocation)loc) { @Override protected List<String> getCustomJavaConfigOptions() { return MutableList.<String>builder() .addAll(super.getCustomJavaConfigOptions()) .add("-server") .build(); }; }; } } @Test public void testPassesJavaOptsObeyingMutualExclusions() { VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class, TestingJavaOptsVanillaJavaAppImpl.class) .configure("main", "my.Main").configure("useJmx", false).configure("javaOpts", ImmutableList.of("-client"))); app.start(ImmutableList.of(loc)); String runDir = javaProcess.getRunDir(); String defaultJavaOpts = "-Xms128m -Xmx512m -XX:MaxPermSize=512m"; String expectedJavaOpts = defaultJavaOpts+" -client"; Map<String,String> expectedEnvs = ImmutableMap.<String,String>of("JAVA_OPTS", expectedJavaOpts); List<String> expectedCmds = ImmutableList.of(String.format("java $JAVA_OPTS -cp \"%1$s/lib\" my.Main >> %1$s/console 2>&1 </dev/null &", runDir)); assertHasExpectedCmds(expectedCmds, expectedEnvs); } /** * Asserts that one of the scripts executed had these commands, and these environment variables. * There could be other commands in between though. */ private void assertHasExpectedCmds(List<String> expectedCmds, Map<String,?> expectedEnvs) { if (RecordingSshTool.execScriptCmds.isEmpty()) fail("No commands recorded"); for (ExecCmd cmd : RecordingSshTool.execScriptCmds) { // TODO Check expectedCmds in-order // check if expectedEnv is a set, then the string value contains all elements in the set Map<Object, ValueDifference<Object>> difference = Maps.<Object,Object>difference(cmd.env, expectedEnvs).entriesDiffering(); boolean same = difference.isEmpty(); if (!same) { Set<Object> differingKeys = new LinkedHashSet<Object>(difference.keySet()); Iterator<Object> ki = differingKeys.iterator(); while (ki.hasNext()) { Object key = ki.next(); Object expectationHere = expectedEnvs.get(key); Object valueHere = cmd.env.get(key); if (valueHere==null) break; if (expectationHere instanceof Set) { Set mutableExpectationHere = new LinkedHashSet(((Set)expectationHere)); Iterator si = ((Set)mutableExpectationHere).iterator(); while (si.hasNext()) { Object oneExpectationHere = si.next(); if (valueHere.toString().contains(Strings.toString(oneExpectationHere))) si.remove(); else break; } if (mutableExpectationHere.isEmpty()) differingKeys.remove(key); else // not the same break; } else { // not the same break; } } if (differingKeys.isEmpty()) same = true; } if (cmd.commands.containsAll(expectedCmds) && same) { return; } } for (ExecCmd cmd : RecordingSshTool.execScriptCmds) { log.info("Command:"); log.info("\tEnv:"); for (Map.Entry<?,?> entry : cmd.env.entrySet()) { log.info("\t\t"+entry.getKey()+" = "+entry.getValue()); } log.info("\tCmds:"); for (String c : cmd.commands) { log.info("\t\t"+c); } } fail("Cmd not present: expected="+expectedCmds+"/"+expectedEnvs+"; actual="+RecordingSshTool.execScriptCmds); } public static class TestingNoSensorsVanillaJavaAppImpl extends VanillaJavaAppImpl { protected void connectSensors() { /* nothing here */ sensors().set(SERVICE_UP, true); } } private void assertJmxWithPropsHasPhrases(Map props, List<String> expectedPhrases, List<String> forbiddenPhrases) { if (!props.containsKey("main")) props.put("main", "my.Main"); @SuppressWarnings({ "unused" }) VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class, TestingNoSensorsVanillaJavaAppImpl.class) .configure(props)); app.start(ImmutableList.of(loc)); List<String> phrases = new ArrayList<String>(expectedPhrases); Set<String> forbiddenPhrasesFound = new LinkedHashSet<String>(); for (ExecCmd cmd : RecordingSshTool.execScriptCmds) { String biggun = ""+cmd.env+" "+cmd.commands; Iterator<String> pi = phrases.iterator(); while (pi.hasNext()) { String phrase = pi.next(); if (biggun.contains(phrase)) pi.remove(); } if (forbiddenPhrases!=null) for (String p: forbiddenPhrases) if (biggun.contains(p)) forbiddenPhrasesFound.add(p); } if (!phrases.isEmpty()) { log.warn("Missing phrases in commands: "+phrases+"\nCOMMANDS: "+RecordingSshTool.execScriptCmds); fail("Missing phrases in commands: "+phrases); } if (!forbiddenPhrasesFound.isEmpty()) { log.warn("Forbidden phrases found in commands: "+forbiddenPhrasesFound+"\nCOMMANDS: "+RecordingSshTool.execScriptCmds); fail("Forbidden phrases found in commands: "+forbiddenPhrasesFound); } } private static final List<String> EXPECTED_BASIC_JMX_OPTS = Arrays.asList( "-Dcom.sun.management.jmxremote", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false" ); private static final List<String> FORBIDDEN_BASIC_JMX_OPTS = Arrays.asList( "-Dcom.sun.management.jmxremote.ssl=true", // often breaks things, as this is an advertised hostname usually; // it typically listens on all interfaces anyway "-Djava.rmi.server.hostname=0.0.0.0" ); @Test public void testBasicJmxFromFlag() { assertJmxWithPropsHasPhrases( MutableMap.builder(). put("useJmx", true). build(), EXPECTED_BASIC_JMX_OPTS, FORBIDDEN_BASIC_JMX_OPTS); } @Test public void testBasicJmxFromConfig() { assertJmxWithPropsHasPhrases( MutableMap.builder(). put(UsesJmx.USE_JMX, true). build(), EXPECTED_BASIC_JMX_OPTS, FORBIDDEN_BASIC_JMX_OPTS); } @Test public void testBasicJmxConfigFromDefault() { assertJmxWithPropsHasPhrases( MutableMap.builder(). build(), EXPECTED_BASIC_JMX_OPTS, FORBIDDEN_BASIC_JMX_OPTS); } @Test public void testSecureJmxConfigFromDefault() { final List<String> EXPECTED_SECURE_JMX_OPTS = Arrays.asList( "-Dcom.sun.management.jmxremote", "-Dbrooklyn.jmxmp.port=31009", "-Dcom.sun.management.jmxremote.ssl=true", "-D"+JmxmpAgent.AUTHENTICATE_CLIENTS_PROPERTY+"=true", "keyStore", "/jmx-keystore", "trustStore", "/jmx-truststore", "-javaagent", "brooklyn-jmxmp-agent" ); final List<String> FORBIDDEN_SECURE_JMX_OPTS = Arrays.asList( "-Dcom.sun.management.jmxremote.authenticate=true", "-Dcom.sun.management.jmxremote.ssl=false", // hostname isn't forbidden -- but it is generally not used now "-Djava.rmi.server.hostname=" ); assertJmxWithPropsHasPhrases( MutableMap.builder() .put(UsesJmx.JMX_SSL_ENABLED, true) .put(UsesJmx.JMX_PORT, 31009) .build(), EXPECTED_SECURE_JMX_OPTS, FORBIDDEN_SECURE_JMX_OPTS); } }