http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java new file mode 100644 index 0000000..9080e51 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java @@ -0,0 +1,115 @@ +/* + * 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; + +import java.util.List; +import java.util.Map; + +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.apache.brooklyn.util.text.StringShortener; +import org.apache.brooklyn.util.text.Strings; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Runs a test with many different distros and versions. + */ +public abstract class AbstractSoftlayerLiveTest { + + public static final String PROVIDER = "softlayer"; + public static final int MAX_TAG_LENGTH = 20; + public static final int MAX_VM_NAME_LENGTH = 30; + + 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("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"); + + ctx = new LocalManagementContext(brooklynProperties); + app = ApplicationBuilder.newManagedApp(TestApplication.class, ctx); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups = {"Live"}) + public void test_Default() throws Exception { + runTest(ImmutableMap.<String,Object>of()); + } + + @Test(groups = {"Live"}) + public void test_Ubuntu_12_0_4() throws Exception { + // Image: {id=UBUNTU_12_64, providerId=UBUNTU_12_64, os={family=ubuntu, version=12.04, description=Ubuntu / Ubuntu / 12.04.0-64 Minimal, is64Bit=true}, description=UBUNTU_12_64, status=AVAILABLE, loginUser=root} + runTest(ImmutableMap.<String,Object>of("imageId", "UBUNTU_12_64")); + } + + @Test(groups = {"Live"}) + public void test_Centos_6_0() throws Exception { + // 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} + runTest(ImmutableMap.<String,Object>of("imageId", "CENTOS_6_64")); + } + + protected void runTest(Map<String,?> flags) throws Exception { + StringShortener shortener = Strings.shortener().separator("-"); + shortener.canTruncate(getClass().getSimpleName(), MAX_TAG_LENGTH); + Map<String,?> allFlags = MutableMap.<String,Object>builder() + .put("tags", ImmutableList.of(shortener.getStringOfMaxLength(MAX_TAG_LENGTH))) + .put("vmNameMaxLength", MAX_VM_NAME_LENGTH) + .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/brooklynnode/BrooklynClusterIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterIntegrationTest.java new file mode 100644 index 0000000..1879e4b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterIntegrationTest.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.brooklynnode; + +import java.io.File; +import java.util.List; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.entity.brooklynnode.BrooklynCluster; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.net.Networking; +import org.apache.brooklyn.util.os.Os; +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 com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public class BrooklynClusterIntegrationTest extends BrooklynAppUnitTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(BrooklynNodeIntegrationTest.class); + + private File pseudoBrooklynPropertiesFile; + private File pseudoBrooklynCatalogFile; + private File persistenceDir; + private LocalhostMachineProvisioningLocation loc; + private List<LocalhostMachineProvisioningLocation> locs; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + pseudoBrooklynPropertiesFile = Os.newTempFile("brooklynnode-test", ".properties"); + pseudoBrooklynPropertiesFile.delete(); + + pseudoBrooklynCatalogFile = Os.newTempFile("brooklynnode-test", ".catalog"); + pseudoBrooklynCatalogFile.delete(); + + loc = app.newLocalhostProvisioningLocation(); + locs = ImmutableList.of(loc); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + try { + super.tearDown(); + } finally { + if (pseudoBrooklynPropertiesFile != null) pseudoBrooklynPropertiesFile.delete(); + if (pseudoBrooklynCatalogFile != null) pseudoBrooklynCatalogFile.delete(); + if (persistenceDir != null) Os.deleteRecursively(persistenceDir); + } + } + + @Test(groups="Integration") + public void testCanStartAndStop() throws Exception { + BrooklynCluster cluster = app.createAndManageChild(EntitySpec.create(BrooklynCluster.class) + .configure(BrooklynCluster.INITIAL_SIZE, 1) + .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, Networking.ANY_NIC) + .configure(BrooklynNode.ON_EXISTING_PROPERTIES_FILE, ExistingFileBehaviour.DO_NOT_USE)); + app.start(locs); + Entity brooklynNode = Iterables.find(cluster.getMembers(), Predicates.instanceOf(BrooklynNode.class)); + LOG.info("started "+app+" containing "+cluster+" for "+JavaClassNames.niceClassAndMethod()); + + EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynNode.SERVICE_UP, true); + EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, true); + + cluster.stop(); + EntityTestUtils.assertAttributeEquals(cluster, BrooklynNode.SERVICE_UP, false); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java new file mode 100644 index 0000000..948a42a --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java @@ -0,0 +1,632 @@ +/* + * 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.brooklynnode; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.apache.brooklyn.api.effector.Effector; +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.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.objs.proxy.EntityProxyImpl; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.entity.brooklynnode.BrooklynEntityMirror; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNodeImpl; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNodeSshDriver; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.StopNodeAndKillAppsEffector; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.entity.stock.BasicApplicationImpl; +import org.apache.brooklyn.sensor.feed.http.JsonFunctions; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.test.HttpTestUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.http.HttpTool; +import org.apache.brooklyn.util.core.http.HttpToolResponse; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Functionals; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.net.Networking; +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +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.Locations; +import org.apache.brooklyn.location.basic.PortRanges; +import org.apache.brooklyn.location.basic.SshMachineLocation; + +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.io.Files; + +/** + * This test needs to able to access the binary artifact in order to run. + * The default behaviour is to take this from maven, which works pretty well if you're downloading from hosted maven. + * <p> + * This class has been updated so that it does not effect or depend on the contents of ~/.brooklyn/brooklyn.properties . + * <p> + * If you wish to supply your own version (useful if testing changes locally!), you'll need to force download of this file. + * The simplest way is to install: + * <ul> + * <li>file://$HOME/.brooklyn/repository/BrooklynNode/${VERSION}/BrooklynNode-${VERSION}.tar.gz - for snapshot versions (filename is default format due to lack of filename in sonatype inferencing; + * note on case-sensitive systems it might have to be all in lower case!) + * <li>file://$HOME/.brooklyn/repository/BrooklynNode/${VERSION}/brooklyn-${VERSION}-dist.tar.gz - for release versions, filename should match that in maven central + * </ul> + * In both cases, remember that you may also need to wipe the local apps cache ($BROOKLYN_DATA_DIR/installs/BrooklynNode). + * The following commands may be useful: + * <p> + * <code> + * cp ~/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz ~/.brooklyn/repository/BrooklynNode/0.7.0-SNAPSHOT/BrooklynNode-0.7.0-SNAPSHOT.tar.gz + * rm -rf /tmp/brooklyn-`whoami`/installs/BrooklynNode* + * </code> + */ +public class BrooklynNodeIntegrationTest extends BrooklynAppUnitTestSupport { + + private static final Logger log = LoggerFactory.getLogger(BrooklynNodeIntegrationTest.class); + + private File pseudoBrooklynPropertiesFile; + private File pseudoBrooklynCatalogFile; + private File persistenceDir; + private LocalhostMachineProvisioningLocation loc; + private List<LocalhostMachineProvisioningLocation> locs; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + pseudoBrooklynPropertiesFile = Os.newTempFile("brooklynnode-test", ".properties"); + pseudoBrooklynPropertiesFile.delete(); + + pseudoBrooklynCatalogFile = Os.newTempFile("brooklynnode-test", ".catalog"); + pseudoBrooklynCatalogFile.delete(); + + loc = app.newLocalhostProvisioningLocation(); + locs = ImmutableList.of(loc); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + try { + super.tearDown(); + } finally { + if (pseudoBrooklynPropertiesFile != null) pseudoBrooklynPropertiesFile.delete(); + if (pseudoBrooklynCatalogFile != null) pseudoBrooklynCatalogFile.delete(); + if (persistenceDir != null) Os.deleteRecursively(persistenceDir); + } + } + + protected EntitySpec<BrooklynNode> newBrooklynNodeSpecForTest() { + // poor man's way to output which test is running + log.info("Creating entity spec for "+JavaClassNames.callerNiceClassAndMethod(1)); + + return EntitySpec.create(BrooklynNode.class) + .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, Networking.ANY_NIC) + .configure(BrooklynNode.ON_EXISTING_PROPERTIES_FILE, ExistingFileBehaviour.DO_NOT_USE); + + /* yaml equivalent, for testing: + +location: localhost +services: +- type: brooklyn.entity.brooklynnode.BrooklynNode + bindAddress: 127.0.0.1 + onExistingProperties: do_not_use + +# some other options + enabledHttpProtocols: [ https ] + managementPassword: s3cr3t + brooklynLocalPropertiesContents: | + brooklyn.webconsole.security.https.required=true + brooklyn.webconsole.security.users=admin + brooklyn.webconsole.security.user.admin.password=s3cr3t + brooklyn.location.localhost.enabled=false + + */ + } + + @Test(groups="Integration") + public void testCanStartAndStop() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, true); + + brooklynNode.stop(); + EntityTestUtils.assertAttributeEquals(brooklynNode, BrooklynNode.SERVICE_UP, false); + } + + @Test(groups="Integration") + public void testSetsGlobalBrooklynPropertiesFromContents() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH, pseudoBrooklynPropertiesFile.getAbsolutePath()) + .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_CONTENTS, "abc=def")); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def")); + } + + @Test(groups="Integration") + public void testSetsLocalBrooklynPropertiesFromContents() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_REMOTE_PATH, pseudoBrooklynPropertiesFile.getAbsolutePath()) + .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, "abc=def")); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def")); + } + + @Test(groups="Integration") + public void testSetsBrooklynPropertiesFromUri() throws Exception { + File brooklynPropertiesSourceFile = File.createTempFile("brooklynnode-test", ".properties"); + Files.write("abc=def", brooklynPropertiesSourceFile, Charsets.UTF_8); + + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH, pseudoBrooklynPropertiesFile.getAbsolutePath()) + .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_URI, brooklynPropertiesSourceFile.toURI().toString())); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def")); + } + + @Test(groups="Integration") + public void testSetsBrooklynCatalogFromContents() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_CATALOG_REMOTE_PATH, pseudoBrooklynCatalogFile.getAbsolutePath()) + .configure(BrooklynNode.BROOKLYN_CATALOG_CONTENTS, "<catalog/>")); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(pseudoBrooklynCatalogFile, Charsets.UTF_8), ImmutableList.of("<catalog/>")); + } + + @Test(groups="Integration") + public void testSetsBrooklynCatalogFromUri() throws Exception { + File brooklynCatalogSourceFile = File.createTempFile("brooklynnode-test", ".catalog"); + Files.write("abc=def", brooklynCatalogSourceFile, Charsets.UTF_8); + + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_CATALOG_REMOTE_PATH, pseudoBrooklynCatalogFile.getAbsolutePath()) + .configure(BrooklynNode.BROOKLYN_CATALOG_URI, brooklynCatalogSourceFile.toURI().toString())); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(pseudoBrooklynCatalogFile, Charsets.UTF_8), ImmutableList.of("abc=def")); + } + + @Test(groups="Integration") + public void testCopiesResources() throws Exception { + File sourceFile = File.createTempFile("brooklynnode-test", ".properties"); + Files.write("abc=def", sourceFile, Charsets.UTF_8); + File tempDir = Files.createTempDir(); + File expectedFile = new File(tempDir, "myfile.txt"); + + try { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath()) + .configure(BrooklynNode.COPY_TO_RUNDIR, ImmutableMap.of(sourceFile.getAbsolutePath(), "${RUN}/myfile.txt"))); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(expectedFile, Charsets.UTF_8), ImmutableList.of("abc=def")); + } finally { + expectedFile.delete(); + tempDir.delete(); + sourceFile.delete(); + } + } + + @Test(groups="Integration") + public void testCopiesClasspathEntriesInConfigKey() throws Exception { + String content = "abc=def"; + File classpathEntry1 = File.createTempFile("first", ".properties"); + File classpathEntry2 = File.createTempFile("second", ".properties"); + Files.write(content, classpathEntry1, Charsets.UTF_8); + Files.write(content, classpathEntry2, Charsets.UTF_8); + File tempDir = Files.createTempDir(); + File expectedFile1 = new File(new File(tempDir, "lib"), classpathEntry1.getName()); + File expectedFile2 = new File(new File(tempDir, "lib"), classpathEntry2.getName()); + + try { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath()) + .configure(BrooklynNode.CLASSPATH, ImmutableList.of(classpathEntry1.getAbsolutePath(), classpathEntry2.getAbsolutePath())) + ); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(expectedFile1, Charsets.UTF_8), ImmutableList.of(content)); + assertEquals(Files.readLines(expectedFile2, Charsets.UTF_8), ImmutableList.of(content)); + } finally { + expectedFile1.delete(); + expectedFile2.delete(); + tempDir.delete(); + classpathEntry1.delete(); + classpathEntry2.delete(); + } + } + + @Test(groups="Integration") + public void testCopiesClasspathEntriesInBrooklynProperties() throws Exception { + String content = "abc=def"; + File classpathEntry1 = File.createTempFile("first", ".properties"); + File classpathEntry2 = File.createTempFile("second", ".properties"); + Files.write(content, classpathEntry1, Charsets.UTF_8); + Files.write(content, classpathEntry2, Charsets.UTF_8); + File tempDir = Files.createTempDir(); + File expectedFile1 = new File(new File(tempDir, "lib"), classpathEntry1.getName()); + File expectedFile2 = new File(new File(tempDir, "lib"), classpathEntry2.getName()); + + try { + String propName = BrooklynNode.CLASSPATH.getName(); + String propValue = classpathEntry1.toURI().toString() + "," + classpathEntry2.toURI().toString(); + ((BrooklynProperties)app.getManagementContext().getConfig()).put(propName, propValue); + + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath()) + ); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + assertEquals(Files.readLines(expectedFile1, Charsets.UTF_8), ImmutableList.of(content)); + assertEquals(Files.readLines(expectedFile2, Charsets.UTF_8), ImmutableList.of(content)); + } finally { + expectedFile1.delete(); + expectedFile2.delete(); + tempDir.delete(); + classpathEntry1.delete(); + classpathEntry2.delete(); + } + } + + // TODO test that the classpath set above is actually used + + @Test(groups="Integration") + public void testSetsBrooklynWebConsolePort() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.HTTP_PORT, PortRanges.fromString("45000+"))); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + Integer httpPort = brooklynNode.getAttribute(BrooklynNode.HTTP_PORT); + URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + assertTrue(httpPort >= 45000 && httpPort < 54100, "httpPort="+httpPort); + assertEquals((Integer)webConsoleUri.getPort(), httpPort); + HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri.toString(), 200, 401); + } + + @Test(groups="Integration") + public void testStartsAppOnStartup() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.APP, BasicApplicationImpl.class.getName())); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + waitForApps(webConsoleUri, 1); + String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); + List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class); + assertEquals(appType, ImmutableList.of(BasicApplication.class.getName())); + } + + protected static void waitForApps(String webConsoleUri) { + HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri+"/v1/applications", 200, 403); + HttpTestUtils.assertHttpStatusCodeEventuallyEquals(webConsoleUri+"/v1/applications", 200); + } + + // TODO Should introduce startup stages and let the client select which stage it expects to be complete + protected void waitForApps(final URI webConsoleUri, final int num) { + waitForApps(webConsoleUri.toString()); + + // e.g. [{"id":"UnBqPcqg","spec":{"name":"Application (UnBqPcqg)","type":"brooklyn.entity.basic.BasicApplication","locations":["pOL4NtiW"]},"status":"RUNNING","links":{"self":"/v1/applications/UnBqPcqg","entities":"/v1/applications/UnBqPcqg/entities"}}] + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + //Wait all apps to become managed + String appsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); + List<String> appIds = parseJsonList(appsContent, ImmutableList.of("id"), String.class); + assertEquals(appIds.size(), num); + + // and then to start + List<String> statuses = parseJsonList(appsContent, ImmutableList.of("status"), String.class); + for (String status : statuses) { + assertEquals(status, Lifecycle.RUNNING.toString().toUpperCase()); + } + }}); + } + + @Test(groups="Integration") + public void testStartsAppViaEffector() throws Exception { + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + // note there is also a test for this in DeployApplication + final URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + waitForApps(webConsoleUri.toString()); + + final String id = brooklynNode.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance() + .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName()) + .getAllConfig()).get(); + + String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); + List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class); + assertEquals(appType, ImmutableList.of(BasicApplication.class.getName())); + + HttpTestUtils.assertContentEventuallyMatches( + webConsoleUri.toString()+"/v1/applications/"+id+"/entities/"+id+"/sensors/service.state", + "\"?(running|RUNNING)\"?"); + } + + @Test(groups="Integration") + public void testUsesLocation() throws Exception { + String brooklynPropertiesContents = + "brooklyn.location.named.mynamedloc=localhost:(name=myname)\n"+ + //force lat+long so test will work when offline + "brooklyn.location.named.mynamedloc.latitude=123\n"+ + "brooklyn.location.named.mynamedloc.longitude=45.6\n"; + + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, brooklynPropertiesContents) + .configure(BrooklynNode.APP, BasicApplicationImpl.class.getName()) + .configure(BrooklynNode.LOCATIONS, "named:mynamedloc")); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + waitForApps(webConsoleUri, 1); + + // Check that "mynamedloc" has been picked up from the brooklyn.properties + String locsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/locations"); + List<String> locNames = parseJsonList(locsContent, ImmutableList.of("name"), String.class); + assertTrue(locNames.contains("mynamedloc"), "locNames="+locNames); + + // Find the id of the concrete location instance of the app + String appsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); + List<String[]> appLocationIds = parseJsonList(appsContent, ImmutableList.of("spec", "locations"), String[].class); + String appLocationId = Iterables.getOnlyElement(appLocationIds)[0]; // app.getManagementContext().getLocationRegistry() + + // Check that the concrete location is of the required type + String locatedLocationsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/locations/usage/LocatedLocations"); + assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "name"), String.class), "myname"); + assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "longitude"), Double.class), 45.6, 0.00001); + } + + @Test(groups="Integration") + public void testAuthenticationAndHttps() throws Exception { + String adminPassword = "p4ssw0rd"; + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.ENABLED_HTTP_PROTOCOLS, ImmutableList.of("https")) + .configure(BrooklynNode.MANAGEMENT_PASSWORD, adminPassword) + .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, + Strings.lines( + "brooklyn.webconsole.security.https.required=true", + "brooklyn.webconsole.security.users=admin", + "brooklyn.webconsole.security.user.admin.password="+adminPassword, + "brooklyn.location.localhost.enabled=false") ) + ); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + Assert.assertTrue(webConsoleUri.toString().startsWith("https://"), "web console not https: "+webConsoleUri); + Integer httpsPort = brooklynNode.getAttribute(BrooklynNode.HTTPS_PORT); + Assert.assertTrue(httpsPort!=null && httpsPort >= 8443 && httpsPort <= 8500); + Assert.assertTrue(webConsoleUri.toString().contains(""+httpsPort), "web console not using right https port ("+httpsPort+"): "+webConsoleUri); + HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri.toString(), 401); + + HttpClient http = HttpTool.httpClientBuilder() + .trustAll() + .uri(webConsoleUri) + .laxRedirect(true) + .credentials(new UsernamePasswordCredentials("admin", adminPassword)) + .build(); + HttpToolResponse response = HttpTool.httpGet(http, webConsoleUri, MutableMap.<String,String>of()); + Assert.assertEquals(response.getResponseCode(), 200); + } + + @Test(groups="Integration") + public void testStopPlainThrowsException() throws Exception { + BrooklynNode brooklynNode = setUpBrooklynNodeWithApp(); + + // Not using annotation with `expectedExceptions = PropagatedRuntimeException.class` because want to + // ensure exception comes from stop. On jenkins, was seeing setUpBrooklynNodeWithApp fail in + // testStopAndKillAppsEffector; so can't tell if this method was really passing! + try { + brooklynNode.stop(); + fail("Expected "+brooklynNode+" stop to fail, because has app"); + } catch (Exception e) { + IllegalStateException ise = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class); + if (ise != null && ise.toString().contains("Can't stop instance with running applications")) { + // success + } else { + throw e; + } + } finally { + try { + brooklynNode.invoke(BrooklynNode.STOP_NODE_AND_KILL_APPS, ImmutableMap.of(StopNodeAndKillAppsEffector.TIMEOUT.getName(), Duration.THIRTY_SECONDS)).getUnchecked(); + } catch (Exception e) { + log.warn("Error in stopNodeAndKillApps for "+brooklynNode+" (continuing)", e); + } + } + } + + @Test(groups="Integration") + public void testStopAndKillAppsEffector() throws Exception { + createNodeAndExecStopEffector(BrooklynNode.STOP_NODE_AND_KILL_APPS); + } + + @Test(groups="Integration") + public void testStopButLeaveAppsEffector() throws Exception { + createNodeAndExecStopEffector(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS); + } + + @Test(groups="Integration") + public void testStopAndRestartProcess() throws Exception { + persistenceDir = Files.createTempDir(); + BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest() + .configure(BrooklynNode.EXTRA_LAUNCH_PARAMETERS, "--persist auto --persistenceDir "+persistenceDir.getAbsolutePath()) + .configure(BrooklynNode.APP, BasicApplicationImpl.class.getName())); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + File pidFile = new File(getDriver(brooklynNode).getPidFile()); + URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + + waitForApps(webConsoleUri, 1); + + // Stop just the process; will not have unmanaged entity unless machine was being terminated + brooklynNode.invoke(BrooklynNode.STOP, ImmutableMap.<String, Object>of( + BrooklynNode.StopSoftwareParameters.STOP_MACHINE_MODE.getName(), StopMode.NEVER, + BrooklynNode.StopSoftwareParameters.STOP_PROCESS_MODE.getName(), StopMode.ALWAYS)).getUnchecked(); + + assertTrue(Entities.isManaged(brooklynNode)); + assertFalse(isPidRunning(pidFile), "pid in "+pidFile+" still running"); + + // Clear the startup app so it's not started second time, in addition to the rebind state + // TODO remove this once the startup app is created only if no previous persistence state + brooklynNode.config().set(BrooklynNode.APP, (String)null); + ((EntityLocal)brooklynNode).setAttribute(BrooklynNode.APP, null); + + // Restart the process; expect persisted state to have been restored, so apps still known about + brooklynNode.invoke(BrooklynNode.RESTART, ImmutableMap.<String, Object>of( + BrooklynNode.RestartSoftwareParameters.RESTART_MACHINE.getName(), "false")).getUnchecked(); + + waitForApps(webConsoleUri.toString()); + String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); + List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class); + assertEquals(appType, ImmutableList.of(BasicApplication.class.getName())); + } + + private void createNodeAndExecStopEffector(Effector<?> eff) throws Exception { + BrooklynNode brooklynNode = setUpBrooklynNodeWithApp(); + File pidFile = new File(getDriver(brooklynNode).getPidFile()); + assertTrue(isPidRunning(pidFile)); + + brooklynNode.invoke(eff, Collections.<String, Object>emptyMap()).getUnchecked(); + + // Note can't use driver.isRunning to check shutdown; can't invoke scripts on an unmanaged entity + EntityTestUtils.assertAttributeEquals(brooklynNode, BrooklynNode.SERVICE_UP, false); + + // unmanaged if the machine is destroyed - ie false on localhost (this test by default), but true in the cloud +// assertFalse(Entities.isManaged(brooklynNode)); + + assertFalse(isPidRunning(pidFile), "pid in "+pidFile+" still running"); + } + + private boolean isPidRunning(File pidFile) throws Exception { + SshMachineLocation machine = loc.obtain(); + try { + int result = machine.execScript("check-pid", ImmutableList.of( + "test -f "+pidFile+" || exit 1", + "ps -p `cat "+pidFile+"`")); + return result == 0; + } finally { + loc.release(machine); + Locations.unmanage(machine); + } + } + + private BrooklynNodeSshDriver getDriver(BrooklynNode brooklynNode) { + try { + EntityProxyImpl entityProxy = (EntityProxyImpl)Proxy.getInvocationHandler(brooklynNode); + Method getDriver = BrooklynNodeImpl.class.getMethod("getDriver"); + return (BrooklynNodeSshDriver)entityProxy.invoke(brooklynNode, getDriver, new Object[]{}); + } catch (Throwable e) { + throw Exceptions.propagate(e); + } + } + + private BrooklynNode setUpBrooklynNodeWithApp() throws InterruptedException, + ExecutionException { + BrooklynNode brooklynNode = app.createAndManageChild(EntitySpec.create(BrooklynNode.class) + .configure(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE)); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, true); + + String baseUrl = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI).toString(); + waitForApps(baseUrl); + + final String id = brooklynNode.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance() + .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName()) + .getAllConfig()).get(); + + String entityUrl = Urls.mergePaths(baseUrl, "v1/applications/", id, "entities", id); + + Entity mirror = brooklynNode.addChild(EntitySpec.create(BrooklynEntityMirror.class) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_URL, entityUrl) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_ID, id)); + Entities.manage(mirror); + + assertEquals(brooklynNode.getChildren().size(), 1); + return brooklynNode; + } + + private <T> T parseJson(String json, List<String> elements, Class<T> clazz) { + Function<String, T> func = Functionals.chain( + JsonFunctions.asJson(), + JsonFunctions.walk(elements), + JsonFunctions.cast(clazz)); + return func.apply(json); + } + + private <T> List<T> parseJsonList(String json, List<String> elements, Class<T> clazz) { + Function<String, List<T>> func = Functionals.chain( + JsonFunctions.asJson(), + JsonFunctions.forEach(Functionals.chain( + JsonFunctions.walk(elements), + JsonFunctions.cast(clazz)))); + return func.apply(json); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeTest.java new file mode 100644 index 0000000..60d2c6b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeTest.java @@ -0,0 +1,141 @@ +/* + * 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.brooklynnode; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNodeImpl; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNodeSshDriver; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.trait.Startable; +import org.apache.brooklyn.sensor.feed.ConfigToAttributes; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; + +import org.apache.brooklyn.location.basic.SshMachineLocation; + +public class BrooklynNodeTest { + + // TODO Need test for copying/setting classpath + + private TestApplication app; + private SshMachineLocation loc; + + public static class SlowStopBrooklynNode extends BrooklynNodeImpl { + public SlowStopBrooklynNode() {} + + @Override + protected void postStop() { + super.postStop(); + + //Make sure UnmanageTask will wait for the STOP effector to complete. + Time.sleep(Duration.FIVE_SECONDS); + } + + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + app = TestApplication.Factory.newManagedInstanceForTests(); + loc = new SshMachineLocation(MutableMap.of("address", "localhost")); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test + public void testGeneratesCorrectSnapshotDownload() throws Exception { + String version = "0.0.1-SNAPSHOT"; + String expectedUrl = "https://repository.apache.org/service/local/artifact/maven/redirect?r=snapshots&g=org.apache.brooklyn&v="+version+"&a=brooklyn-dist&c=dist&e=tar.gz"; + runTestGeneratesCorrectDownloadUrl(version, expectedUrl); + } + + @Test + public void testGeneratesCorrectReleaseDownload() throws Exception { + String version = "0.0.1"; + String expectedUrl = "http://search.maven.org/remotecontent?filepath=org/apache/brooklyn/brooklyn-dist/"+version+"/brooklyn-dist-"+version+"-dist.tar.gz"; + runTestGeneratesCorrectDownloadUrl(version, expectedUrl); + } + + private void runTestGeneratesCorrectDownloadUrl(String version, String expectedUrl) throws Exception { + // TODO Using BrooklynNodeImpl directly, because want to instantiate a BroolynNodeSshDriver. + // Really want to make that easier to test, without going through "wrong" code path for creating entity. + BrooklynNodeImpl entity = new BrooklynNodeImpl(); + entity.setConfig(BrooklynNode.SUGGESTED_VERSION, version); + entity.setParent(app); + Entities.manage(entity); + ConfigToAttributes.apply(entity); + BrooklynNodeSshDriver driver = new BrooklynNodeSshDriver(entity, loc); + + DownloadResolver resolver = Entities.newDownloader(driver); + List<String> urls = resolver.getTargets(); + + System.out.println("urls="+urls); + assertTrue(urls.contains(expectedUrl), "urls="+urls); + } + + @Test(groups = "Integration") + public void testUnmanageOnStop() throws Exception { + final BrooklynNode node = app.addChild(EntitySpec.create(BrooklynNode.class).impl(SlowStopBrooklynNode.class)); + Entities.manage(node); + assertTrue(Entities.isManaged(node), "Entity " + node + " must be managed."); + node.invoke(Startable.STOP, ImmutableMap.<String,Object>of()).asTask().getUnchecked(); + //The UnmanageTask will unblock after the STOP effector completes, so we are competing with it here. + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertFalse(Entities.isManaged(node)); + } + }); + } + + + @Test + public void testCanStartSameNode() throws Exception { + // not very interesting as do not have REST when run in this project + // but test BrooklynNodeRestTest in downstream project does + BrooklynNode bn = app.createAndManageChild(EntitySpec.create(BrooklynNode.class, SameBrooklynNodeImpl.class)); + bn.start(MutableSet.<Location>of()); + + Assert.assertEquals(bn.getAttribute(Attributes.SERVICE_UP), (Boolean)true); + // no URI + Assert.assertNull(bn.getAttribute(BrooklynNode.WEB_CONSOLE_URI)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java new file mode 100644 index 0000000..c933b9d --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java @@ -0,0 +1,100 @@ +/* + * 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.brooklynnode; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.entity.brooklynnode.EntityHttpClient; +import org.apache.brooklyn.util.core.http.HttpToolResponse; +import org.apache.brooklyn.util.core.http.HttpTool.HttpClientBuilder; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; + +public class CallbackEntityHttpClient implements EntityHttpClient { + public static class Request { + private Entity entity; + private String method; + private String path; + private Map<String, String> params; + public Request(Entity entity, String method, String path, Map<String, String> params) { + this.entity = entity; + this.method = method; + this.path = path; + this.params = params; + } + public Entity getEntity() { + return entity; + } + public String getMethod() { + return method; + } + public String getPath() { + return path; + } + public Map<String, String> getParams() { + return params; + } + } + private Function<Request, String> callback; + private Entity entity; + + public CallbackEntityHttpClient(Entity entity, Function<Request, String> callback) { + this.entity = entity; + this.callback = callback; + } + + @Override + public HttpClientBuilder getHttpClientForBrooklynNode() { + throw new IllegalStateException("Method call not expected"); + } + + @Override + public HttpToolResponse get(String path) { + String result = callback.apply(new Request(entity, HttpGet.METHOD_NAME, path, Collections.<String, String>emptyMap())); + return new HttpToolResponse(HttpStatus.SC_OK, null, result.getBytes(), 0, 0, 0); + } + + @Override + public HttpToolResponse post(String path, Map<String, String> headers, byte[] body) { + throw new IllegalStateException("Method call not expected"); + } + + @Override + public HttpToolResponse post(String path, Map<String, String> headers, Map<String, String> formParams) { + String result = callback.apply(new Request(entity, HttpPost.METHOD_NAME, path, formParams)); + return new HttpToolResponse(HttpStatus.SC_OK, Collections.<String, List<String>>emptyMap(), result.getBytes(), 0, 0, 0); + } + + @Override + public HttpToolResponse delete(String path, Map<String, String> headers) { + throw new IllegalStateException("Method call not expected"); + } + + @Override + public EntityHttpClient responseSuccess(Predicate<Integer> successPredicate) { + throw new IllegalStateException("Method call not expected"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/MockBrooklynNode.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/MockBrooklynNode.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/MockBrooklynNode.java new file mode 100644 index 0000000..fcc939f --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/MockBrooklynNode.java @@ -0,0 +1,72 @@ +/* + * 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.brooklynnode; + +import java.util.Collection; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.EntityHttpClient; +import org.apache.brooklyn.entity.brooklynnode.CallbackEntityHttpClient.Request; +import org.apache.brooklyn.entity.brooklynnode.effector.SetHighAvailabilityModeEffectorBody; +import org.apache.brooklyn.entity.brooklynnode.effector.SetHighAvailabilityPriorityEffectorBody; +import org.apache.brooklyn.entity.core.AbstractEntity; +import org.apache.brooklyn.sensor.core.BasicAttributeSensor; + +import com.google.common.base.Function; +import com.google.common.reflect.TypeToken; + +public class MockBrooklynNode extends AbstractEntity implements BrooklynNode { + @SuppressWarnings("serial") + public static final ConfigKey<Function<Request, String>> HTTP_CLIENT_CALLBACK = ConfigKeys.newConfigKey(new TypeToken<Function<Request, String>>(){}, "httpClientCallback"); + public static final AttributeSensor<Integer> HA_PRIORITY = new BasicAttributeSensor<Integer>(Integer.class, "priority"); + + @Override + public void init() { + super.init(); + getMutableEntityType().addEffector(SetHighAvailabilityPriorityEffectorBody.SET_HIGH_AVAILABILITY_PRIORITY); + getMutableEntityType().addEffector(SetHighAvailabilityModeEffectorBody.SET_HIGH_AVAILABILITY_MODE); + setAttribute(HA_PRIORITY, 0); + } + + @Override + public EntityHttpClient http() { + return new CallbackEntityHttpClient(this, getConfig(HTTP_CLIENT_CALLBACK)); + } + + @Override + public void start(Collection<? extends Location> locations) { + } + + @Override + public void stop() { + } + + @Override + public void restart() { + } + + @Override + public void populateServiceNotUpDiagnostics() { + // no-op + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java new file mode 100644 index 0000000..84d2c33 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.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.brooklynnode; + +import java.net.URI; +import java.util.Collection; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.EntityHttpClient; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNodeImpl.DeployBlueprintEffectorBody; +import org.apache.brooklyn.entity.core.AbstractEntity; +import org.apache.brooklyn.sensor.feed.http.HttpFeed; +import org.apache.brooklyn.sensor.feed.http.HttpPollConfig; +import org.apache.brooklyn.sensor.feed.http.HttpValueFunctions; + +/** Implementation of BrooklynNode which just presents the node where this is running, for convenience; + * + * start/stop/restart have no effect; + * sensors are connected; + * deploy blueprint assumes that a REST endpoint is available */ +public class SameBrooklynNodeImpl extends AbstractEntity implements BrooklynNode { + + private HttpFeed httpFeed; + + @Override + public void start(Collection<? extends Location> locations) { + connectSensors(); + } + + @Override + public void stop() { + disconnectSensors(); + } + + @Override + public void restart() { + return; + } + + + @Override + public void init() { + super.init(); + getMutableEntityType().addEffector(DeployBlueprintEffectorBody.DEPLOY_BLUEPRINT); + } + + protected void connectSensors() { + URI webConsoleUri = getManagementContext().getManagementNodeUri().orNull(); + setAttribute(WEB_CONSOLE_URI, webConsoleUri); + + if (webConsoleUri != null) { + httpFeed = HttpFeed.builder() + .entity(this) + .period(200) + .baseUri(webConsoleUri) + .credentialsIfNotNull(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD)) + .poll(new HttpPollConfig<Boolean>(SERVICE_UP) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .setOnFailureOrException(false)) + .build(); + + } else { + setAttribute(SERVICE_UP, true); + } + } + + protected void disconnectSensors() { + if (httpFeed != null) httpFeed.stop(); + } + + @Override + public EntityHttpClient http() { + throw new UnsupportedOperationException(); + } + + @Override + public void populateServiceNotUpDiagnostics() { + // no-op + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java new file mode 100644 index 0000000..60cc6e9 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java @@ -0,0 +1,259 @@ +/* + * 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.brooklynnode; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.Group; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.effector.core.Effectors; +import org.apache.brooklyn.entity.brooklynnode.BrooklynCluster; +import org.apache.brooklyn.entity.brooklynnode.BrooklynClusterImpl; +import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; +import org.apache.brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector; +import org.apache.brooklyn.entity.brooklynnode.CallbackEntityHttpClient.Request; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.sensor.feed.AttributePollHandler; +import org.apache.brooklyn.sensor.feed.DelegatingPollHandler; +import org.apache.brooklyn.sensor.feed.Poller; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.time.Duration; +import org.apache.http.client.methods.HttpPost; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; + +public class SelectMasterEffectorTest extends BrooklynAppUnitTestSupport { + private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class); + + protected BrooklynCluster cluster; + protected HttpCallback http; + protected Poller<Void> poller; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + super.setUp(); + + // because the effector calls wait for a state change, use a separate thread to drive that + poller = new Poller<Void>((EntityLocal)app, false); + poller.scheduleAtFixedRate( + new Callable<Void>() { + @Override + public Void call() throws Exception { + masterFailoverIfNeeded(); + return null; + } + }, + new DelegatingPollHandler<Void>(Collections.<AttributePollHandler<? super Void>>emptyList()), + Duration.millis(20)); + poller.start(); + } + + @Override + protected void setUpApp() { + super.setUpApp(); + http = new HttpCallback(); + cluster = app.createAndManageChild(EntitySpec.create(BrooklynCluster.class) + .location(app.newLocalhostProvisioningLocation()) + .configure(BrooklynCluster.MEMBER_SPEC, EntitySpec.create(BrooklynNode.class) + .impl(MockBrooklynNode.class) + .configure(MockBrooklynNode.HTTP_CLIENT_CALLBACK, http))); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + poller.stop(); + super.tearDown(); + } + + @Test + public void testInvalidNewMasterIdFails() { + try { + selectMaster(cluster, "1234"); + fail("Non-existend entity ID provided."); + } catch (Exception e) { + assertTrue(e.toString().contains("1234 is not an ID of brooklyn node in this cluster")); + } + } + + @Test(groups="Integration") // because slow, due to sensor feeds + public void testSelectMasterAfterChange() { + List<Entity> nodes = makeTwoNodes(); + EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)nodes.get(0)); + + selectMaster(cluster, nodes.get(1).getId()); + checkMaster(cluster, nodes.get(1)); + } + + @Test + public void testFindMaster() { + List<Entity> nodes = makeTwoNodes(); + Assert.assertEquals(((BrooklynClusterImpl)Entities.deproxy(cluster)).findMasterChild(), nodes.get(0)); + } + + @Test(groups="Integration") // because slow, due to sensor feeds + public void testSelectMasterFailsAtChangeState() { + http.setFailAtStateChange(true); + + List<Entity> nodes = makeTwoNodes(); + + EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)nodes.get(0)); + + try { + selectMaster(cluster, nodes.get(1).getId()); + fail("selectMaster should have failed"); + } catch (Exception e) { + // expected + } + checkMaster(cluster, nodes.get(0)); + } + + private List<Entity> makeTwoNodes() { + List<Entity> nodes = MutableList.copyOf(cluster.resizeByDelta(2)); + setManagementState(nodes.get(0), ManagementNodeState.MASTER); + setManagementState(nodes.get(1), ManagementNodeState.HOT_STANDBY); + return nodes; + } + + private void checkMaster(Group cluster, Entity node) { + assertEquals(node.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.MASTER); + assertEquals(cluster.getAttribute(BrooklynCluster.MASTER_NODE), node); + for (Entity member : cluster.getMembers()) { + if (member != node) { + assertEquals(member.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.HOT_STANDBY); + } + assertEquals((int)member.getAttribute(MockBrooklynNode.HA_PRIORITY), 0); + } + } + + private static class HttpCallback implements Function<CallbackEntityHttpClient.Request, String> { + private enum State { + INITIAL, + PROMOTED + } + private State state = State.INITIAL; + private boolean failAtStateChange; + + @Override + public String apply(Request input) { + if ("/v1/server/ha/state".equals(input.getPath())) { + if (failAtStateChange) { + throw new RuntimeException("Testing failure at changing node state"); + } + + checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/state", "mode", "HOT_STANDBY"); + Entity entity = input.getEntity(); + EntityTestUtils.assertAttributeEquals(entity, BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER); + EntityTestUtils.assertAttributeEquals(entity, MockBrooklynNode.HA_PRIORITY, 0); + + setManagementState(entity, ManagementNodeState.HOT_STANDBY); + + return "MASTER"; + } else { + switch(state) { + case INITIAL: + checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "1"); + state = State.PROMOTED; + setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority"))); + return "0"; + case PROMOTED: + checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "0"); + state = State.INITIAL; + setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority"))); + return "1"; + default: throw new IllegalStateException("Illegal call at state " + state + ". Request = " + input.getMethod() + " " + input.getPath()); + } + } + } + + public void checkRequest(Request input, String methodName, String path, String key, String value) { + if (!input.getMethod().equals(methodName) || !input.getPath().equals(path)) { + throw new IllegalStateException("Request doesn't match expected state. Expected = " + input.getMethod() + " " + input.getPath() + ". " + + "Actual = " + methodName + " " + path); + } + + String inputValue = input.getParams().get(key); + if(!Objects.equal(value, inputValue)) { + throw new IllegalStateException("Request doesn't match expected parameter " + methodName + " " + path + ". Parameter " + key + + " expected = " + value + ", actual = " + inputValue); + } + } + + public void setFailAtStateChange(boolean failAtStateChange) { + this.failAtStateChange = failAtStateChange; + } + + } + + private void masterFailoverIfNeeded() { + if (!Entities.isManaged(cluster)) return; + if (cluster.getAttribute(BrooklynCluster.MASTER_NODE) == null) { + Collection<Entity> members = cluster.getMembers(); + if (members.size() > 0) { + for (Entity member : members) { + if (member.getAttribute(MockBrooklynNode.HA_PRIORITY) == 1) { + masterFailover(member); + return; + } + } + masterFailover(members.iterator().next()); + } + } + } + + private void masterFailover(Entity member) { + LOG.debug("Master failover to " + member); + setManagementState(member, ManagementNodeState.MASTER); + EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)member); + return; + } + + public static void setManagementState(Entity entity, ManagementNodeState state) { + ((EntityLocal)entity).setAttribute(BrooklynNode.MANAGEMENT_NODE_STATE, state); + } + + public static void setPriority(Entity entity, int priority) { + ((EntityLocal)entity).setAttribute(MockBrooklynNode.HA_PRIORITY, priority); + } + + private void selectMaster(DynamicCluster cluster, String id) { + app.getExecutionContext().submit(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, ImmutableMap.of(SelectMasterEffector.NEW_MASTER_ID.getName(), id))).asTask().getUnchecked(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefConfigsTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefConfigsTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefConfigsTest.java new file mode 100644 index 0000000..6cc8e27 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefConfigsTest.java @@ -0,0 +1,52 @@ +/* + * 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.chef; + +import java.util.Set; + +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.chef.ChefConfig; +import org.apache.brooklyn.entity.chef.ChefConfigs; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.factory.ApplicationBuilder; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +public class ChefConfigsTest { + + private TestApplication app = null; + + @AfterMethod(alwaysRun=true) + public void tearDown() { + if (app!=null) Entities.destroyAll(app.getManagementContext()); + app = null; + } + + @Test + public void testAddToRunList() { + app = ApplicationBuilder.newManagedApp(TestApplication.class); + ChefConfigs.addToLaunchRunList(app, "a", "b"); + Set<? extends String> runs = app.getConfig(ChefConfig.CHEF_LAUNCH_RUN_LIST); + Assert.assertEquals(runs.size(), 2, "runs="+runs); + Assert.assertTrue(runs.contains("a")); + Assert.assertTrue(runs.contains("b")); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefLiveTestSupport.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefLiveTestSupport.java b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefLiveTestSupport.java new file mode 100644 index 0000000..5ea8315 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefLiveTestSupport.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.chef; + +import java.io.File; +import java.io.IOException; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; +import org.apache.brooklyn.entity.chef.ChefConfig; +import org.apache.brooklyn.entity.core.EntityInternal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.io.FileUtil; +import org.apache.brooklyn.util.stream.InputStreamSupplier; + +import com.google.common.base.Throwables; +import com.google.common.io.Files; + +public class ChefLiveTestSupport extends BrooklynAppLiveTestSupport { + + private static final Logger log = LoggerFactory.getLogger(ChefLiveTestSupport.class); + + protected MachineProvisioningLocation<? extends SshMachineLocation> targetLocation; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + + targetLocation = createLocation(); + } + + protected MachineProvisioningLocation<? extends SshMachineLocation> createLocation() { + return createLocation(mgmt); + } + + /** convenience for setting up a pre-built / fixed IP machine + * (because you might not want to set up Chef on localhost) + * and ensuring tests against Chef use the same configured location + **/ + @SuppressWarnings("unchecked") + public static MachineProvisioningLocation<? extends SshMachineLocation> createLocation(ManagementContext mgmt) { + Location bestLocation = mgmt.getLocationRegistry().resolve("named:ChefTests", true, null).orNull(); + if (bestLocation==null) { + log.info("using AWS for chef tests because named:ChefTests does not exist"); + bestLocation = mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-east-1"); + } + if (bestLocation==null) { + throw new IllegalStateException("Need a location called named:ChefTests or AWS configured for these tests"); + } + return (MachineProvisioningLocation<? extends SshMachineLocation>)bestLocation; + } + + private static String defaultConfigFile = null; + public synchronized static String installBrooklynChefHostedConfig() { + if (defaultConfigFile!=null) return defaultConfigFile; + File tempDir = Files.createTempDir(); + ResourceUtils r = ResourceUtils.create(ChefServerTasksIntegrationTest.class); + try { + for (String f: new String[] { "knife.rb", "brooklyn-tests.pem", "brooklyn-validator.pem" }) { + String contents = r.getResourceAsString("classpath:///org/apache/brooklyn/entity/chef/hosted-chef-brooklyn-credentials/"+f); + FileUtil.copyTo(InputStreamSupplier.fromString(contents).getInput(), new File(tempDir, f)); + } + } catch (IOException e) { + throw Throwables.propagate(e); + } + File knifeConfig = new File(tempDir, "knife.rb"); + defaultConfigFile = knifeConfig.getPath(); + return defaultConfigFile; + } + + public static void installBrooklynChefHostedConfig(Entity entity) { + ((EntityInternal)entity).setConfig(ChefConfig.KNIFE_CONFIG_FILE, ChefLiveTestSupport.installBrooklynChefHostedConfig()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefServerTasksIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefServerTasksIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefServerTasksIntegrationTest.java new file mode 100644 index 0000000..79c7be0 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/chef/ChefServerTasksIntegrationTest.java @@ -0,0 +1,126 @@ +/* + * 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.chef; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.chef.ChefConfig; +import org.apache.brooklyn.entity.chef.ChefServerTasks; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.factory.ApplicationBuilder; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.stream.StreamGobbler; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Many tests expect knife on the path, but none require any configuration beyond that. + * They will use the Brooklyn registered account (which has been set up with mysql cookbooks and more). + * <p> + * Note this is a free account so cannot manage many nodes. + * You can use the credentials in src/test/resources/hosted-chef-brooklyn-credentials/ + * to log in and configure the settings for our tests using knife. You can also log in at: + * <p> + * https://manage.opscode.com/ + * <p> + * with credentials for those with need to know (which is a lot of people, but not everyone + * with access to this github repo!). + * <p> + * You can easily set up your own new account, for free; download the starter kit and + * point {@link ChefConfig#KNIFE_CONFIG_FILE} at the knife.rb. + * <p> + * Note that if you are porting an existing machine to be managed by a new chef account, you may need to do the following: + * <p> + * ON management machine: + * <li>knife client delete HOST # or bulk delete, but don't delete your validator! it is a PITA recreating and adding back all the permissions! + * <li>knife node delete HOST + * <p> + * ON machine being managed: + * <li>rm -rf /{etc,var}/chef + * <p> + * Note also that some tests require a location named:ChefLive to be set up in your brooklyn.properties. + * This can be a cloud (but will require frequent chef-node pruning) or a permanently set-up machine. + **/ +public class ChefServerTasksIntegrationTest { + + private static final Logger log = LoggerFactory.getLogger(ChefServerTasksIntegrationTest.class); + + protected TestApplication app; + protected ManagementContext mgmt; + + @BeforeMethod(alwaysRun=true) + public void setup() throws Exception { + app = ApplicationBuilder.newManagedApp(TestApplication.class); + mgmt = app.getManagementContext(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (mgmt != null) Entities.destroyAll(mgmt); + mgmt = null; + } + + /** @deprecated use {@link ChefLiveTestSupport} */ + @Deprecated + public synchronized static String installBrooklynChefHostedConfig() { + return ChefLiveTestSupport.installBrooklynChefHostedConfig(); + } + + @Test(groups="Integration") + @SuppressWarnings("resource") + public void testWhichKnife() throws IOException, InterruptedException { + // requires that knife is installed on the path of login shells + Process p = Runtime.getRuntime().exec(new String[] { "bash", "-l", "-c", "which knife" }); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new StreamGobbler(p.getInputStream(), out, log).start(); + new StreamGobbler(p.getErrorStream(), out, log).start(); + log.info("bash -l -c 'which knife' gives exit code: "+p.waitFor()); + Time.sleep(Duration.millis(1000)); + log.info("output:\n"+out); + Assert.assertEquals(p.exitValue(), 0); + } + + @Test(groups="Integration") + public void testKnifeWithoutConfig() { + // without config it shouldn't pass + // (assumes that knife global config is *not* installed on your machine) + ProcessTaskWrapper<Boolean> t = Entities.submit(app, ChefServerTasks.isKnifeInstalled()); + log.info("isKnifeInstalled without config returned: "+t.get()+" ("+t.getExitCode()+")\n"+t.getStdout()+"\nERR:\n"+t.getStderr()); + Assert.assertFalse(t.get()); + } + + @Test(groups="Integration") + public void testKnifeWithConfig() { + // requires that knife is installed on the path of login shells + // (creates the config in a temp space) + ChefLiveTestSupport.installBrooklynChefHostedConfig(app); + ProcessTaskWrapper<Boolean> t = Entities.submit(app, ChefServerTasks.isKnifeInstalled()); + log.info("isKnifeInstalled *with* config returned: "+t.get()+" ("+t.getExitCode()+")\n"+t.getStdout()+"\nERR:\n"+t.getStderr()); + Assert.assertTrue(t.get()); + } + +}
