http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java new file mode 100644 index 0000000..e4d30c3 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java @@ -0,0 +1,352 @@ +/* + * 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.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.net.MalformedURLException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.java.JavaAppUtils; +import org.apache.brooklyn.entity.java.UsesJava; +import org.apache.brooklyn.entity.java.UsesJmx; +import org.apache.brooklyn.entity.java.VanillaJavaApp; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.sensor.feed.jmx.JmxHelper; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.crypto.FluentKeySigner; +import org.apache.brooklyn.util.core.crypto.SecureKeys; +import org.apache.brooklyn.util.crypto.SslTrustUtils; +import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.basic.PortRanges; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +public class VanillaJavaAppTest { + + private static final Logger LOG = LoggerFactory.getLogger(VanillaJavaAppTest.class); + + private static final long TIMEOUT_MS = 10*1000; + + // Static attributes such as number of processors and start time are only polled every 60 seconds + // so if they are not immediately available, it will be 60 seconds before they are polled again + private static final Object LONG_TIMEOUT_MS = 61*1000; + + private static String BROOKLYN_THIS_CLASSPATH = null; + private static Class<?> MAIN_CLASS = ExampleVanillaMain.class; + private static Class<?> MAIN_CPU_HUNGRY_CLASS = ExampleVanillaMainCpuHungry.class; + + private TestApplication app; + private LocalhostMachineProvisioningLocation loc; + + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception { + if (BROOKLYN_THIS_CLASSPATH==null) { + BROOKLYN_THIS_CLASSPATH = ResourceUtils.create(MAIN_CLASS).getClassLoaderDir(); + } + app = TestApplication.Factory.newManagedInstanceForTests(); + loc = app.newLocalhostProvisioningLocation(MutableMap.of("address", "localhost")); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test + public void testReadsConfigFromFlags() throws Exception { + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", "my.Main").configure("classpath", ImmutableList.of("c1", "c2")) + .configure("args", ImmutableList.of("a1", "a2"))); + + assertEquals(javaProcess.getMainClass(), "my.Main"); + assertEquals(javaProcess.getClasspath(), ImmutableList.of("c1","c2")); + assertEquals(javaProcess.getConfig(VanillaJavaApp.ARGS), ImmutableList.of("a1", "a2")); + } + + @Test(groups={"WIP", "Integration"}) + public void testJavaSystemProperties() throws Exception { + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", "my.Main").configure("classpath", ImmutableList.of("c1", "c2")) + .configure("args", ImmutableList.of("a1", "a2"))); + ((EntityLocal)javaProcess).setConfig(UsesJava.JAVA_SYSPROPS, ImmutableMap.of("fooKey", "fooValue", "barKey", "barValue")); + // TODO: how to test: launch standalone app that outputs system properties to stdout? Probe via JMX? + } + + @Test(groups={"Integration"}) + public void testStartsAndStops() throws Exception { + String main = MAIN_CLASS.getCanonicalName(); + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH)) + .configure("args", ImmutableList.of())); + app.start(ImmutableList.of(loc)); + assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING); + + javaProcess.stop(); + assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.STOPPED); + } + + @Test(groups={"Integration"}) + public void testHasJvmMXBeanSensorVals() throws Exception { + String main = MAIN_CLASS.getCanonicalName(); + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH)) + .configure("args", ImmutableList.of())); + app.start(ImmutableList.of(loc)); + + // Memory MXBean + Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() { + public void run() { + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.NON_HEAP_MEMORY_USAGE)); + long init = javaProcess.getAttribute(VanillaJavaApp.INIT_HEAP_MEMORY); + long used = javaProcess.getAttribute(VanillaJavaApp.USED_HEAP_MEMORY); + long committed = javaProcess.getAttribute(VanillaJavaApp.COMMITTED_HEAP_MEMORY); + long max = javaProcess.getAttribute(VanillaJavaApp.MAX_HEAP_MEMORY); + + assertNotNull(used); + assertNotNull(init); + assertNotNull(committed); + assertNotNull(max); + assertTrue(init <= max, String.format("init %d > max %d heap memory", init, max)); + assertTrue(used <= committed, String.format("used %d > committed %d heap memory", used, committed)); + assertTrue(committed <= max, String.format("committed %d > max %d heap memory", committed, max)); + }}); + + // Threads MX Bean + Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() { + public void run() { + long current = javaProcess.getAttribute(VanillaJavaApp.CURRENT_THREAD_COUNT); + long peak = javaProcess.getAttribute(VanillaJavaApp.PEAK_THREAD_COUNT); + + assertNotNull(current); + assertNotNull(peak); + assertTrue(current <= peak, String.format("current %d > peak %d thread count", current, peak)); + }}); + + // Runtime MX Bean + Asserts.succeedsEventually(MutableMap.of("timeout", LONG_TIMEOUT_MS), new Runnable() { + public void run() { + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.START_TIME)); + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.UP_TIME)); + }}); + + // Operating System MX Bean + Asserts.succeedsEventually(MutableMap.of("timeout", LONG_TIMEOUT_MS), new Runnable() { + public void run() { + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.PROCESS_CPU_TIME)); + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.SYSTEM_LOAD_AVERAGE)); + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.AVAILABLE_PROCESSORS)); + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.TOTAL_PHYSICAL_MEMORY_SIZE)); + assertNotNull(javaProcess.getAttribute(VanillaJavaApp.FREE_PHYSICAL_MEMORY_SIZE)); + }}); + // TODO work on providing useful metrics from garbage collector MX Bean + // assertNotNull(javaProcess.getAttribute(VanillaJavaApp.GARBAGE_COLLECTION_TIME)) TODO: work on providing this + } + + @Test(groups={"Integration"}) + public void testJvmMXBeanProcessCpuTimeGivesNonZeroPercentage() throws Exception { + String main = MAIN_CPU_HUNGRY_CLASS.getCanonicalName(); + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH)) + .configure("args", ImmutableList.of())); + app.start(ImmutableList.of(loc)); + + JavaAppUtils.connectJavaAppServerPolicies((EntityLocal)javaProcess); + + final List<Double> fractions = new CopyOnWriteArrayList<Double>(); + app.getManagementContext().getSubscriptionManager().subscribe(javaProcess, VanillaJavaApp.PROCESS_CPU_TIME_FRACTION_LAST, new SensorEventListener<Double>() { + public void onEvent(SensorEvent<Double> event) { + fractions.add(event.getValue()); + }}); + + // Expect non-trivial load to be generated by the process. + // Expect load to be in the right order of magnitude (to ensure we haven't got a decimal point in the wrong place etc); + // But with multi-core could get big number; and on jenkins@releng3 we once saw [11.9, 0.6, 0.5]! + Asserts.succeedsEventually(new Runnable() { + public void run() { + Iterable<Double> nonTrivialFractions = Iterables.filter(fractions, new Predicate<Double>() { + public boolean apply(Double input) { + return input > 0.01; + }}); + assertTrue(Iterables.size(nonTrivialFractions) > 3, "fractions="+fractions); + }}); + + Iterable<Double> tooBigFractions = Iterables.filter(fractions, new Predicate<Double>() { + public boolean apply(Double input) { + return input > 50; + }}); + assertTrue(Iterables.isEmpty(tooBigFractions), "fractions="+fractions); + + Iterable<Double> ballparkRightFractions = Iterables.filter(fractions, new Predicate<Double>() { + public boolean apply(Double input) { + return input > 0.01 && input < 4; + }}); + assertTrue(Iterables.size(ballparkRightFractions) >= (fractions.size() / 2), "fractions="+fractions); + + LOG.info("VanillaJavaApp->ExampleVanillaMainCpuHuntry: ProcessCpuTime fractions="+fractions); + } + + @Test(groups={"Integration"}) + public void testStartsWithJmxPortSpecifiedInConfig() throws Exception { + int port = 53405; + String main = MAIN_CLASS.getCanonicalName(); + VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH)) + .configure("args", ImmutableList.of())); + ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_PORT, PortRanges.fromInteger(port)); + app.start(ImmutableList.of(loc)); + + assertEquals(javaProcess.getAttribute(UsesJmx.JMX_PORT), (Integer)port); + } + + // FIXME Way test was written requires JmxSensorAdapter; need to rewrite... + @Test(groups={"Integration", "WIP"}) + public void testStartsWithSecureJmxPortSpecifiedInConfig() throws Exception { + int port = 53406; + String main = MAIN_CLASS.getCanonicalName(); + final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class) + .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH)) + .configure("args", ImmutableList.of())); + ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_PORT, PortRanges.fromInteger(port)); + ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_SSL_ENABLED, true); + + app.start(ImmutableList.of(loc)); + // will fail above if JMX can't connect, but also do some add'l checks + + assertEquals(javaProcess.getAttribute(UsesJmx.JMX_PORT), (Integer)port); + + // good key+cert succeeds + new AsserterForJmxConnection(javaProcess) + .customizeSocketFactory(null, null) + .connect(); + + // bad cert fails + Asserts.assertFails(new Callable<Void>() { + public Void call() throws Exception { + new AsserterForJmxConnection(javaProcess) + .customizeSocketFactory(null, new FluentKeySigner("cheater").newCertificateFor("jmx-access-key", SecureKeys.newKeyPair())) + .connect(); + return null; + }}); + + // bad key fails + Asserts.assertFails(new Callable<Void>() { + public Void call() throws Exception { + new AsserterForJmxConnection(javaProcess) + .customizeSocketFactory(SecureKeys.newKeyPair().getPrivate(), null) + .connect(); + return null; + }}); + + // bad profile fails + Asserts.assertFails(new Callable<Void>() { + public Void call() throws Exception { + AsserterForJmxConnection asserter = new AsserterForJmxConnection(javaProcess); + asserter.putEnv("jmx.remote.profiles", JmxmpAgent.TLS_JMX_REMOTE_PROFILES); + asserter.customizeSocketFactory(SecureKeys.newKeyPair().getPrivate(), null) + .connect(); + return null; + }}); + } + + private static class AsserterForJmxConnection { + final VanillaJavaApp entity; + final JMXServiceURL url; + final Map<String,Object> env; + + @SuppressWarnings("unchecked") + public AsserterForJmxConnection(VanillaJavaApp e) throws MalformedURLException { + this.entity = e; + + JmxHelper jmxHelper = new JmxHelper((EntityLocal)entity); + this.url = new JMXServiceURL(jmxHelper.getUrl()); + this.env = Maps.newLinkedHashMap(jmxHelper.getConnectionEnvVars()); + } + + public JMXServiceURL getJmxUrl() throws MalformedURLException { + return url; + } + + public void putEnv(String key, Object val) { + env.put(key, val); + } + + public AsserterForJmxConnection customizeSocketFactory(PrivateKey customKey, Certificate customCert) throws Exception { + PrivateKey key = (customKey == null) ? entity.getConfig(UsesJmx.JMX_SSL_ACCESS_KEY) : customKey; + Certificate cert = (customCert == null) ? entity.getConfig(UsesJmx.JMX_SSL_ACCESS_CERT) : customCert; + + KeyStore ks = SecureKeys.newKeyStore(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + if (key!=null) { + ks.setKeyEntry("brooklyn-jmx-access", key, "".toCharArray(), new Certificate[] {cert}); + } + kmf.init(ks, "".toCharArray()); + + TrustManager tms = + // TODO use root cert for trusting server + //trustStore!=null ? SecureKeys.getTrustManager(trustStore) : + SslTrustUtils.TRUST_ALL; + + SSLContext ctx = SSLContext.getInstance("TLSv1"); + ctx.init(kmf.getKeyManagers(), new TrustManager[] {tms}, null); + SSLSocketFactory ssf = ctx.getSocketFactory(); + env.put(JmxmpAgent.TLS_SOCKET_FACTORY_PROPERTY, ssf); + + return this; + } + + public JMXConnector connect() throws Exception { + return JMXConnectorFactory.connect(getJmxUrl(), env); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java new file mode 100644 index 0000000..9c4e571 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java @@ -0,0 +1,57 @@ +/* + * 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.machine; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.entity.AbstractEc2LiveTest; +import org.apache.brooklyn.entity.machine.MachineEntity; +import org.apache.brooklyn.test.Asserts; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class MachineEntityEc2LiveTest extends AbstractEc2LiveTest { + + @Override + protected void doTest(Location loc) throws Exception { + final MachineEntity server = app.createAndManageChild(EntitySpec.create(MachineEntity.class)); + + app.start(ImmutableList.of(loc)); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertNotNull(server.getAttribute(MachineEntity.UPTIME)); + assertNotNull(server.getAttribute(MachineEntity.LOAD_AVERAGE)); + assertNotNull(server.getAttribute(MachineEntity.CPU_USAGE)); + assertNotNull(server.getAttribute(MachineEntity.FREE_MEMORY)); + assertNotNull(server.getAttribute(MachineEntity.TOTAL_MEMORY)); + assertNotNull(server.getAttribute(MachineEntity.USED_MEMORY)); + }}); + + String result = server.execCommand("MY_ENV=myval && echo start $MY_ENV"); + assertTrue(result.contains("start myval"), "result="+result); + } + + @Test(enabled=false) + public void testDummy() {} // Convince testng IDE integration that this really does have test methods +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java new file mode 100644 index 0000000..cda1309 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java @@ -0,0 +1,44 @@ +/* + * 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.machine; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.apache.brooklyn.test.EntityTestUtils; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class MachineEntityRebindTest extends RebindTestFixtureWithApp { + + @Test(groups = "Integration") + public void testRebindToMachineEntity() throws Exception { + EmptySoftwareProcess machine = origApp.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class)); + origApp.start(ImmutableList.of(origManagementContext.getLocationRegistry().resolve("localhost"))); + EntityTestUtils.assertAttributeEqualsEventually(machine, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + rebind(false); + Entity machine2 = newManagementContext.getEntityManager().getEntity(machine.getId()); + EntityTestUtils.assertAttributeEqualsEventually(machine2, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java new file mode 100644 index 0000000..c31e458 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java @@ -0,0 +1,145 @@ +/* + * 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.machine.pool; + +import static org.testng.Assert.fail; + +import java.util.List; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.factory.ApplicationBuilder; +import org.apache.brooklyn.entity.machine.pool.ServerPool; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public abstract class AbstractServerPoolTest { + + // Note not extending BrooklynAppUnitTestSupport because sub-classes of this are for live and for unit tests. + // Instead, we have to repeat that logic for setting SKIP_ON_BOX_BASE_DIR_RESOLUTION + + private static final int DEFAULT_POOL_SIZE = 3; + + protected Location location; + protected ManagementContext mgmt; + protected TestApplication poolApp; + protected ServerPool pool; + private List<TestApplication> createdApps = Lists.newLinkedList(); + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + createdApps.clear(); + mgmt = createManagementContext(); + location = createLocation(); + EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class) + .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution()); + poolApp = ApplicationBuilder.newManagedApp(appSpec, mgmt); + + pool = poolApp.createAndManageChild(EntitySpec.create(ServerPool.class) + .configure(ServerPool.INITIAL_SIZE, getInitialPoolSize()) + .configure(ServerPool.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class))); + poolApp.start(ImmutableList.of(location)); + EntityTestUtils.assertAttributeEqualsEventually(pool, Attributes.SERVICE_UP, true); + assertAvailableCountEventuallyEquals(getInitialPoolSize()); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + // Kills the apps before terminating the pool + for (TestApplication app : createdApps) { + Entities.destroy(app); + } + if (mgmt != null) { + Entities.destroyAll(mgmt); + mgmt = null; + } + } + + protected int getInitialPoolSize() { + return DEFAULT_POOL_SIZE; + } + + protected ManagementContext createManagementContext() { + return new LocalManagementContextForTests(); + } + + protected boolean shouldSkipOnBoxBaseDirResolution() { + return true; + } + + /** @return Creates a LocalhostMachineProvisioningLocation */ + protected Location createLocation() { + return mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + } + + protected void assertNoMachinesAvailableForApp(TestApplication app) { + try { + app.start(ImmutableList.of(pool.getDynamicLocation())); + fail("Expected exception when starting app with too many entities for pool"); + } catch (Exception e) { + Throwable t = Exceptions.getFirstThrowableOfType(e, NoMachinesAvailableException.class); + if (t == null) { + throw new RuntimeException(e); + } + } + } + + protected void assertAvailableCountEventuallyEquals(int count) { + assertAvailableCountEventuallyEquals(pool, count); + } + + protected void assertAvailableCountEventuallyEquals(ServerPool pool, int count) { + EntityTestUtils.assertAttributeEqualsEventually(pool, ServerPool.AVAILABLE_COUNT, count); + } + + protected void assertClaimedCountEventuallyEquals(int count) { + assertClaimedCountEventuallyEquals(pool, count); + } + + protected void assertClaimedCountEventuallyEquals(ServerPool pool, Integer count) { + EntityTestUtils.assertAttributeEqualsEventually(pool, ServerPool.CLAIMED_COUNT, count); + } + + protected TestApplication createAppWithChildren(int numChildren) { + if (numChildren < 0) fail("Invalid number of children for app: " + numChildren); + EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class) + .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution()); + TestApplication app = ApplicationBuilder.newManagedApp(appSpec, mgmt); + while (numChildren-- > 0) { + app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class)); + } + createdApps.add(app); + return app; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java new file mode 100644 index 0000000..c1e1f18 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java @@ -0,0 +1,97 @@ +/* + * 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.machine.pool; + +import static org.testng.Assert.assertTrue; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.util.collections.MutableMap; +import org.testng.annotations.Test; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; + +public class ServerPoolLiveTest extends AbstractServerPoolTest { + + public static final String PROVIDER = "softlayer"; + + protected BrooklynProperties brooklynProperties; + + @Override + protected Location createLocation() { + // Image: {id=CENTOS_6_64, providerId=CENTOS_6_64, os={family=centos, version=6.5, description=CentOS / CentOS / 6.5-64 LAMP for Bare Metal, is64Bit=true}, description=CENTOS_6_64, status=AVAILABLE, loginUser=root} + Map<String, ?> allFlags = MutableMap.<String, Object>builder() + .put("provider", PROVIDER) + .put("tags", ImmutableList.of(getClass().getName())) + .put("vmNameMaxLength", 30) + .put("imageId", "CENTOS_6_64") + .build(); + return mgmt.getLocationRegistry().resolve(PROVIDER, allFlags); + } + + @Override + protected ManagementContext createManagementContext() { + String[] propsToRemove = new String[]{"imageId", "imageDescriptionRegex", "imageNameRegex", "inboundPorts", "hardwareId", "minRam"}; + + // Don't let any defaults from brooklyn.properties (except credentials) interfere with test + brooklynProperties = BrooklynProperties.Factory.newDefault(); + for (String propToRemove : propsToRemove) { + for (String propVariant : ImmutableList.of(propToRemove, CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, propToRemove))) { + brooklynProperties.remove("brooklyn.locations.jclouds." + PROVIDER + "." + propVariant); + brooklynProperties.remove("brooklyn.locations." + propVariant); + brooklynProperties.remove("brooklyn.jclouds." + PROVIDER + "." + propVariant); + brooklynProperties.remove("brooklyn.jclouds." + propVariant); + } + } + + // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty") + brooklynProperties.remove("brooklyn.ssh.config.scriptHeader"); + return new LocalManagementContextForTests(brooklynProperties); + } + + protected boolean shouldSkipOnBoxBaseDirResolution() { + return false; + } + + @Override + protected int getInitialPoolSize() { + return 1; + } + + @Test(groups = "Live") + public void testAppCanBeDeployedToPool() { + TestApplication app = createAppWithChildren(1); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertTrue(app.getAttribute(Attributes.SERVICE_UP)); + for (Entity child : app.getChildren()) { + assertTrue(child.getAttribute(Attributes.SERVICE_UP)); + } + TestApplication app2 = createAppWithChildren(1); + assertNoMachinesAvailableForApp(app2); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java new file mode 100644 index 0000000..fb3974b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java @@ -0,0 +1,90 @@ +/* + * 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.machine.pool; + +import static org.testng.Assert.assertEquals; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.factory.ApplicationBuilder; +import org.apache.brooklyn.entity.machine.pool.ServerPool; +import org.apache.brooklyn.entity.machine.pool.ServerPoolLocation; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.apache.brooklyn.util.collections.MutableMap; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.dynamic.DynamicLocation; + +public class ServerPoolLocationResolverTest { + + private LocalManagementContext managementContext; + private Entity locationOwner; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + managementContext = new LocalManagementContextForTests(BrooklynProperties.Factory.newEmpty()); + TestApplication t = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext); + locationOwner = t.createAndManageChild(EntitySpec.create(ServerPool.class) + .configure(ServerPool.INITIAL_SIZE, 0) + .configure(ServerPool.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class))); + Location poolLocation = managementContext.getLocationManager() + .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + t.start(ImmutableList.of(poolLocation)); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (managementContext != null) Entities.destroyAll(managementContext); + } + + @Test + public void testResolve() { + ServerPoolLocation location = resolve("pool:" + locationOwner.getId()); + assertEquals(location.getOwner().getId(), locationOwner.getId()); + } + + @Test + public void testSetsDisplayName() { + ServerPoolLocation location = resolve("pool:" + locationOwner.getId() + ":(displayName=xyz)"); + assertEquals(location.getDisplayName(), "xyz"); + } + + private ServerPoolLocation resolve(String val) { + Map<String, Object> flags = MutableMap.<String, Object>of(DynamicLocation.OWNER.getName(), locationOwner); + Location l = managementContext.getLocationRegistry().resolve(val, flags); + Assert.assertNotNull(l); + return (ServerPoolLocation) l; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java new file mode 100644 index 0000000..2de22bb --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java @@ -0,0 +1,109 @@ +/* + * 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.machine.pool; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.File; +import java.util.Collection; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.rebind.RebindOptions; +import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.machine.pool.ServerPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.io.Files; + +public class ServerPoolRebindTest extends AbstractServerPoolTest { + + private static final Logger LOG = LoggerFactory.getLogger(ServerPoolRebindTest.class); + private ClassLoader classLoader = getClass().getClassLoader(); + private File mementoDir; + + @Override + protected ManagementContext createManagementContext() { + mementoDir = Files.createTempDir(); + return RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader); + } + + @Override + @AfterMethod(alwaysRun = true) + public void tearDown() throws Exception { + super.tearDown(); + if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir); + } + + private Collection<Application> rebind(TestApplication app) throws Exception { + LOG.info("Rebind start"); + RebindTestUtils.waitForPersisted(app); + ((LocalManagementContext) app.getManagementContext()).terminate(); + Collection<Application> r = RebindTestUtils.rebindAll(RebindOptions.create().mementoDir(mementoDir).classLoader(classLoader)); + LOG.info("Rebind complete"); + return r; + } + + @Test(enabled = false) + public void testRebindingToPool() throws Exception { + TestApplication app = createAppWithChildren(1); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertTrue(app.getAttribute(Attributes.SERVICE_UP)); + assertAvailableCountEventuallyEquals(pool, getInitialPoolSize() - 1); + assertClaimedCountEventuallyEquals(pool, 1); + + Collection<Application> reboundApps = rebind(poolApp); + ServerPool reboundPool = null; + for (Application reboundApp : reboundApps) { + Optional<Entity> np = Iterables.tryFind(reboundApp.getChildren(), Predicates.instanceOf(ServerPool.class)); + if (np.isPresent()) { + mgmt = reboundApp.getManagementContext(); + reboundPool = (ServerPool) np.get(); + break; + } + } + + assertNotNull(reboundPool, "No app in rebound context has " + ServerPool.class.getName() + + " child. Apps: " + reboundApps); + assertNotNull(reboundPool.getDynamicLocation()); + assertTrue(reboundPool.getAttribute(Attributes.SERVICE_UP)); + assertAvailableCountEventuallyEquals(reboundPool, getInitialPoolSize() - 1); + assertClaimedCountEventuallyEquals(reboundPool, 1); + + TestApplication app2 = createAppWithChildren(1); + app2.start(ImmutableList.of(reboundPool.getDynamicLocation())); + assertTrue(app2.getAttribute(Attributes.SERVICE_UP)); + assertAvailableCountEventuallyEquals(reboundPool, getInitialPoolSize() - 2); + assertClaimedCountEventuallyEquals(reboundPool, 2); + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java new file mode 100644 index 0000000..9d9b3a2 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java @@ -0,0 +1,175 @@ +/* + * 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.machine.pool; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.Collection; +import java.util.Iterator; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.machine.pool.ServerPoolImpl; +import org.apache.brooklyn.test.EntityTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public class ServerPoolTest extends AbstractServerPoolTest { + + private static final Logger LOG = LoggerFactory.getLogger(ServerPoolTest.class); + + @Test + public void testAppCanBeDeployedToServerPool() { + TestApplication app = createAppWithChildren(1); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertTrue(app.getAttribute(Attributes.SERVICE_UP)); + for (Entity child : app.getChildren()) { + assertTrue(child.getAttribute(Attributes.SERVICE_UP)); + } + } + + @Test + public void testFailureWhenNotEnoughServersAvailable() { + TestApplication app = createAppWithChildren(getInitialPoolSize() + 1); + assertNoMachinesAvailableForApp(app); + EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); + } + + @Test + public void testDeployReleaseDeploy() { + TestApplication app = createAppWithChildren(getInitialPoolSize()); + TestApplication app2 = createAppWithChildren(1); + + app.start(ImmutableList.of(pool.getDynamicLocation())); + EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); + assertAvailableCountEventuallyEquals(0); + assertNoMachinesAvailableForApp(app2); + + app.stop(); + assertFalse(app.getAttribute(Attributes.SERVICE_UP)); + assertAvailableCountEventuallyEquals(getInitialPoolSize()); + + app2.start(ImmutableList.of(pool.getDynamicLocation())); + EntityTestUtils.assertAttributeEqualsEventually(app2, Attributes.SERVICE_UP, true); + + assertAvailableCountEventuallyEquals(getInitialPoolSize() - 1); + assertClaimedCountEventuallyEquals(1); + } + + @Test + public void testResizingPoolUp() { + TestApplication app = createAppWithChildren(getInitialPoolSize()); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertTrue(app.getAttribute(Attributes.SERVICE_UP)); + + TestApplication app2 = createAppWithChildren(1); + assertNoMachinesAvailableForApp(app2); + + pool.resizeByDelta(1); + + assertAvailableCountEventuallyEquals(1); + + assertEquals((int) pool.getCurrentSize(), getInitialPoolSize() + 1); + app2.start(ImmutableList.of(pool.getDynamicLocation())); + assertTrue(app2.getAttribute(Attributes.SERVICE_UP)); + } + + @Test + public void testResizePoolDownSucceedsWhenEnoughMachinesAreFree() { + TestApplication app = createAppWithChildren(1); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(getInitialPoolSize() - 1); + + pool.resize(1); + + assertAvailableCountEventuallyEquals(0); + } + + @Test + public void testResizeDownDoesNotReleaseClaimedMachines() { + TestApplication app = createAppWithChildren(getInitialPoolSize() - 1); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(1); + assertClaimedCountEventuallyEquals(getInitialPoolSize() - 1); + + LOG.info("Test attempting to resize to 0 members. Should only drop the one available machine."); + pool.resize(0); + + assertAvailableCountEventuallyEquals(0); + assertEquals(Iterables.size(pool.getMembers()), getInitialPoolSize() - 1); + assertAvailableCountEventuallyEquals(0); + assertClaimedCountEventuallyEquals(getInitialPoolSize() - 1); + } + + @Test + public void testCanAddExistingMachinesToPool() { + TestApplication app = createAppWithChildren(getInitialPoolSize()); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(0); + + LocalhostMachine loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachine.class)); + Entity added = pool.addExistingMachine(loc); + assertFalse(added.getConfig(ServerPoolImpl.REMOVABLE)); + assertAvailableCountEventuallyEquals(1); + + TestApplication app2 = createAppWithChildren(1); + app2.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(0); + } + + @Test + public void testExistingMachinesAreNotRemovedFromThePoolOnShrinkButAreOnStop() { + LocalhostMachine loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachine.class)); + pool.addExistingMachine(loc); + assertAvailableCountEventuallyEquals(getInitialPoolSize() + 1); + pool.resize(0); + assertAvailableCountEventuallyEquals(1); + pool.stop(); + assertAvailableCountEventuallyEquals(0); + } + + @Test + public void testAddExistingMachineFromSpec() { + TestApplication app = createAppWithChildren(getInitialPoolSize()); + app.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(0); + + Collection<Entity> added = pool.addExistingMachinesFromSpec("byon:(hosts=\"localhost,localhost\")"); + assertEquals(added.size(), 2, "Added: " + Joiner.on(", ").join(added)); + Iterator<Entity> it = added.iterator(); + assertFalse(it.next().getConfig(ServerPoolImpl.REMOVABLE)); + assertFalse(it.next().getConfig(ServerPoolImpl.REMOVABLE)); + assertAvailableCountEventuallyEquals(2); + + TestApplication app2 = createAppWithChildren(2); + app2.start(ImmutableList.of(pool.getDynamicLocation())); + assertAvailableCountEventuallyEquals(0); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java new file mode 100644 index 0000000..7a9fd6d --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java @@ -0,0 +1,99 @@ +/* + * 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.software.base; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.factory.ApplicationBuilder; +import org.apache.brooklyn.util.collections.MutableMap; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +/** + * Runs a test with many different distros and versions. + */ +public abstract class AbstractDockerLiveTest { + + public static final String PROVIDER = "docker"; + + protected BrooklynProperties brooklynProperties; + protected ManagementContext ctx; + + protected TestApplication app; + protected Location jcloudsLocation; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + List<String> propsToRemove = ImmutableList.of("imageDescriptionRegex", "imageNameRegex", "inboundPorts", + "hardwareId", "minRam"); + + // Don't let any defaults from brooklyn.properties (except credentials) interfere with test + brooklynProperties = BrooklynProperties.Factory.newDefault(); + for (String propToRemove : propsToRemove) { + for (String propVariant : ImmutableList.of(propToRemove, CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, propToRemove))) { + brooklynProperties.remove("brooklyn.locations.jclouds."+PROVIDER+"."+propVariant); + brooklynProperties.remove("brooklyn.locations."+propVariant); + brooklynProperties.remove("brooklyn.jclouds."+PROVIDER+"."+propVariant); + brooklynProperties.remove("brooklyn.jclouds."+propVariant); + } + } + + // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty") + brooklynProperties.remove("brooklyn.ssh.config.scriptHeader"); + + ctx = new LocalManagementContext(brooklynProperties); + app = ApplicationBuilder.newManagedApp(TestApplication.class, ctx); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAllCatching(app.getManagementContext()); + } + + @Test(groups={"Live", "WIP"}) + public void test_Ubuntu_13_10() throws Exception { + runTest(ImmutableMap.of("imageId", "7fe2ec2ff748c411cf0d6833120741778c00e1b07a83c4104296b6258b5331c4", + "loginUser", "root", + "loginUser.password", "password")); + } + + protected void runTest(Map<String,?> flags) throws Exception { + String tag = getClass().getSimpleName().toLowerCase(); + Map<String,?> allFlags = MutableMap.<String,Object>builder() + .put("tags", ImmutableList.of(tag)) + .putAll(flags) + .build(); + jcloudsLocation = ctx.getLocationRegistry().resolve(PROVIDER, allFlags); + doTest(jcloudsLocation); + } + + protected abstract void doTest(Location loc) throws Exception; +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java new file mode 100644 index 0000000..67f5fc4 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java @@ -0,0 +1,96 @@ +/* + * 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.software.base; + +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.RestartSoftwareParameters; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Tests restart of the software *process* (as opposed to the VM). + */ +public abstract class AbstractSoftwareProcessRestartIntegrationTest extends BrooklynAppLiveTestSupport { + + // TODO Remove duplication from TomcatServerRestartIntegrationTest and MySqlRestartIntegrationTest + + @SuppressWarnings("unused") + private static final Logger LOG = LoggerFactory.getLogger(AbstractSoftwareProcessRestartIntegrationTest.class); + + protected abstract EntitySpec<? extends SoftwareProcess> newEntitySpec(); + + @Test(groups="Integration") + public void testStopProcessAndRestart() throws Exception { + runStopProcessAndRestart( + SoftwareProcess.RESTART, + ImmutableMap.of(RestartSoftwareParameters.RESTART_MACHINE.getName(), RestartSoftwareParameters.RestartMachineMode.FALSE)); + } + + @Test(groups="Integration") + public void testStopProcessAndStart() throws Exception { + runStopProcessAndRestart( + SoftwareProcess.START, + ImmutableMap.of("locations", ImmutableList.of())); + } + + protected void runStopProcessAndRestart(Effector<?> restartEffector, Map<String, ?> args) throws Exception { + LocalhostMachineProvisioningLocation loc = app.newLocalhostProvisioningLocation(); + SoftwareProcess entity = app.createAndManageChild(newEntitySpec()); + + // Start the app + app.start(ImmutableList.of(loc)); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, true); + EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_UP, true); + + // Stop the process + Entities.invokeEffector(app, entity, SoftwareProcess.STOP, ImmutableMap.of( + StopSoftwareParameters.STOP_MACHINE_MODE.getName(), StopSoftwareParameters.StopMode.NEVER)) + .get(); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, false); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false); + EntityTestUtils.assertAttributeEventually(entity, ServiceStateLogic.SERVICE_NOT_UP_INDICATORS, CollectionFunctionals.<String>mapSizeEquals(1)); + + // Restart the process + Entities.invokeEffector(app, entity, restartEffector, args).get(); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, true); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true); + EntityTestUtils.assertAttributeEqualsEventually(entity, ServiceStateLogic.SERVICE_NOT_UP_INDICATORS, ImmutableMap.<String, Object>of()); + + EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_UP, true); + EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java new file mode 100644 index 0000000..4459db4 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java @@ -0,0 +1,33 @@ +/* + * 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.software.base; + +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; + +@ImplementedBy(DoNothingSoftwareProcessImpl.class) +public interface DoNothingSoftwareProcess extends SoftwareProcess { + + public static final ConfigKey<Boolean> SKIP_ON_BOX_BASE_DIR_RESOLUTION = ConfigKeys.newConfigKeyWithDefault( + BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, + true); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java new file mode 100644 index 0000000..2e0ea00 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java @@ -0,0 +1,69 @@ +/* + * 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.software.base; + +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; +import org.apache.brooklyn.location.basic.SshMachineLocation; + +/** + * Implements methods in {@link org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver} + * such that no actions are performed. + * <p> + * {@link #isRunning()} returns true. + */ +public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDriver { + + public DoNothingSoftwareProcessDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public boolean isRunning() { + return true; + } + + @Override + public void copyPreInstallResources() { + } + + @Override + public void copyInstallResources() { + } + + @Override + public void copyRuntimeResources() { + } + + @Override + public void install() { + } + + @Override + public void customize() { + } + + @Override + public void launch() { + } + + @Override + public void stop() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java new file mode 100644 index 0000000..a04c660 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java @@ -0,0 +1,38 @@ +/* + * 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.software.base; + +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl; + +public class DoNothingSoftwareProcessImpl extends SoftwareProcessImpl implements DoNothingSoftwareProcess { + + @Override + public Class getDriverInterface() { + return DoNothingSoftwareProcessDriver.class; + } + + @Override + protected void connectSensors() { + super.connectSensors(); + if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) { + setAttribute(SERVICE_UP, true); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java new file mode 100644 index 0000000..3d87dbb --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java @@ -0,0 +1,140 @@ +/* + * 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.software.base; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode; +import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks; +import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.entity.stock.BasicEntityImpl; +import org.apache.brooklyn.entity.trait.Startable; +import org.apache.brooklyn.sensor.core.DependentConfiguration; +import org.apache.brooklyn.sensor.core.Sensors; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.core.task.TaskInternal; +import org.apache.brooklyn.util.time.Duration; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import org.apache.brooklyn.location.jclouds.BailOutJcloudsLocation; + +public class MachineLifecycleEffectorTasksTest { + public static boolean canStop(StopMode stopMode, boolean isEntityStopped) { + BasicEntityImpl entity = new BasicEntityImpl(); + Lifecycle state = isEntityStopped ? Lifecycle.STOPPED : Lifecycle.RUNNING; + entity.setAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL, state); + return MachineLifecycleEffectorTasks.canStop(stopMode, entity); + } + + @DataProvider(name = "canStopStates") + public Object[][] canStopStates() { + return new Object[][] { + { StopMode.ALWAYS, true, true }, + { StopMode.ALWAYS, false, true }, + { StopMode.IF_NOT_STOPPED, true, false }, + { StopMode.IF_NOT_STOPPED, false, true }, + { StopMode.NEVER, true, false }, + { StopMode.NEVER, false, false }, + }; + } + + @Test(dataProvider = "canStopStates") + public void testBasicSonftwareProcessCanStop(StopMode mode, boolean isEntityStopped, boolean expected) { + boolean canStop = canStop(mode, isEntityStopped); + assertEquals(canStop, expected); + } + + @Test + public void testProvisionLatchObeyed() throws Exception { + + AttributeSensor<Boolean> ready = Sensors.newBooleanSensor("readiness"); + + TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); + BasicEntity triggerEntity = app.createAndManageChild(EntitySpec.create(BasicEntity.class)); + + EmptySoftwareProcess entity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class) + .configure(BrooklynConfigKeys.PROVISION_LATCH, DependentConfiguration.attributeWhenReady(triggerEntity, ready))); + + final Task<Void> task = Entities.invokeEffector(app, app, Startable.START, ImmutableMap.of( + "locations", ImmutableList.of(BailOutJcloudsLocation.newBailOutJcloudsLocation(app.getManagementContext())))); + + assertEffectorBlockingDetailsEventually(entity, "Waiting for config " + BrooklynConfigKeys.PROVISION_LATCH.getName()); + + Asserts.succeedsContinually(new Runnable() { + @Override + public void run() { + assertFalse(task.isDone()); + } + }); + try { + ((EntityLocal) triggerEntity).setAttribute(ready, true); + task.get(Duration.THIRTY_SECONDS); + } catch (Throwable t) { + // BailOut location throws but we don't care. + } finally { + Entities.destroyAll(app.getManagementContext()); + } + } + + private void assertEffectorBlockingDetailsEventually(final Entity entity, final String blockingDetailsSnippet) { + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + Task<?> entityTask = Iterables.getOnlyElement(entity.getApplication().getManagementContext().getExecutionManager().getTasksWithAllTags( + ImmutableList.of(BrooklynTaskTags.EFFECTOR_TAG, BrooklynTaskTags.tagForContextEntity(entity)))); + String blockingDetails = getBlockingDetails(entityTask); + assertTrue(blockingDetails.contains(blockingDetailsSnippet)); + }}); + } + + private String getBlockingDetails(Task<?> task) { + List<TaskInternal<?>> taskChain = Lists.newArrayList(); + TaskInternal<?> taskI = (TaskInternal<?>) task; + while (taskI != null) { + taskChain.add(taskI); + if (taskI.getBlockingDetails() != null) { + return taskI.getBlockingDetails(); + } + taskI = (TaskInternal<?>) taskI.getBlockingTask(); + } + throw new IllegalStateException("No blocking details for "+task+" (walked task chain "+taskChain+")"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java new file mode 100644 index 0000000..937c87d --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java @@ -0,0 +1,84 @@ +/* + * 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.software.base; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.software.base.SameServerEntity; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class SameServerEntityTest { + + private LocalhostMachineProvisioningLocation loc; + private ManagementContext mgmt; + private TestApplication app; + private SameServerEntity entity; + + @BeforeMethod(alwaysRun=true) + public void setUp() { + loc = new LocalhostMachineProvisioningLocation(); + app = TestApplication.Factory.newManagedInstanceForTests(); + mgmt = app.getManagementContext(); + entity = app.createAndManageChild(EntitySpec.create(SameServerEntity.class)); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() { + if (app != null) Entities.destroyAll(mgmt); + } + + @Test + public void testUsesSameMachineLocationForEachChild() throws Exception { + Entity child1 = entity.addChild(EntitySpec.create(TestEntity.class)); + Entity child2 = entity.addChild(EntitySpec.create(TestEntity.class)); + Entities.manage(child1); + Entities.manage(child2); + + app.start(ImmutableList.of(loc)); + + Location child1Loc = Iterables.getOnlyElement(child1.getLocations()); + Location child2Loc = Iterables.getOnlyElement(child2.getLocations()); + + assertSame(child1Loc, child2Loc); + assertTrue(child1Loc instanceof LocalhostMachine, "loc="+child1Loc); + + assertEquals(ImmutableSet.of(child1Loc), ImmutableSet.copyOf(loc.getInUse())); + + app.stop(); + + assertEquals(ImmutableSet.of(), ImmutableSet.copyOf(loc.getInUse())); + } +}
