/*
* 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.launcher;
import org.apache.brooklyn.api.entity.Application;
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.catalog.internal.CatalogInitialization;
import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.core.test.entity.TestApplicationImpl;
import org.apache.brooklyn.core.test.entity.TestEntity;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import org.apache.brooklyn.test.HttpTestUtils;
import org.apache.brooklyn.util.exceptions.FatalRuntimeException;
import org.apache.brooklyn.util.io.FileUtil;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.StringFunctions;
import org.apache.brooklyn.util.text.Strings;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
public class BrooklynLauncherTest {
private BrooklynLauncher launcher;
@AfterMethod(alwaysRun=true)
public void tearDown() throws Exception {
if (launcher != null) launcher.terminate();
launcher = null;
}
// Integration because takes a few seconds to start web-console
@Test(groups="Integration")
public void testStartsWebServerOnExpectectedPort() throws Exception {
launcher = newLauncherForTests(true)
.webconsolePort("10000+")
.start();
String webServerUrlStr = launcher.getServerDetails().getWebServerUrl();
URI webServerUri = new URI(webServerUrlStr);
assertEquals(launcher.getApplications(), ImmutableList.of());
assertTrue(webServerUri.getPort() >= 10000 && webServerUri.getPort() < 10100, "port="+webServerUri.getPort()+"; uri="+webServerUri);
HttpTestUtils.assertUrlReachable(webServerUrlStr);
}
// Integration because takes a few seconds to start web-console
@Test(groups="Integration")
public void testWebServerTempDirRespectsDataDirConfig() throws Exception {
String dataDirName = ".brooklyn-foo"+Strings.makeRandomId(4);
String dataDir = "~/"+dataDirName;
launcher = newLauncherForTests(true)
.brooklynProperties(BrooklynServerConfig.MGMT_BASE_DIR, dataDir)
.start();
ManagementContext managementContext = launcher.getServerDetails().getManagementContext();
String expectedTempDir = Os.mergePaths(Os.home(), dataDirName, "planes", managementContext.getManagementPlaneId(), managementContext.getManagementNodeId(), "jetty");
File webappTempDir = launcher.getServerDetails().getWebServer().getWebappTempDir();
assertEquals(webappTempDir.getAbsolutePath(), expectedTempDir);
}
@Test
public void testCanDisableWebServerStartup() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.start();
assertNull(launcher.getServerDetails().getWebServer());
assertNull(launcher.getServerDetails().getWebServerUrl());
Assert.assertTrue( ((ManagementContextInternal)launcher.getServerDetails().getManagementContext()).errors().isEmpty() );
}
@Test
public void testStartsAppInstance() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.application(new TestApplicationImpl())
.start();
assertOnlyApp(launcher, TestApplication.class);
}
@Test
public void testStartsAppFromSpec() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.application(EntitySpec.create(TestApplication.class))
.start();
assertOnlyApp(launcher, TestApplication.class);
}
@Test
public void testStartsAppFromBuilder() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.application(new ApplicationBuilder(EntitySpec.create(TestApplication.class)) {
@Override protected void doBuild() {
}})
.start();
assertOnlyApp(launcher, TestApplication.class);
}
@Test
public void testStartsAppFromYAML() throws Exception {
String yaml = "name: example-app\n" +
"services:\n" +
"- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n" +
" name: test-app";
launcher = newLauncherForTests(true)
.webconsole(false)
.application(yaml)
.start();
assertEquals(launcher.getApplications().size(), 1, "apps="+launcher.getApplications());
Application app = Iterables.getOnlyElement(launcher.getApplications());
assertEquals(app.getChildren().size(), 1, "children=" + app.getChildren());
assertTrue(Iterables.getOnlyElement(app.getChildren()) instanceof TestEntity);
}
@Test // may take 2s initializing location if running this test case alone, but noise if running suite
public void testStartsAppInSuppliedLocations() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.location("localhost")
.application(new ApplicationBuilder(EntitySpec.create(TestApplication.class)) {
@Override protected void doBuild() {
}})
.start();
Application app = Iterables.find(launcher.getApplications(), Predicates.instanceOf(TestApplication.class));
assertOnlyLocation(app, LocalhostMachineProvisioningLocation.class);
}
@Test
public void testUsesSuppliedManagementContext() throws Exception {
LocalManagementContext myManagementContext = LocalManagementContextForTests.newInstance();
launcher = newLauncherForTests(false)
.webconsole(false)
.managementContext(myManagementContext)
.start();
assertSame(launcher.getServerDetails().getManagementContext(), myManagementContext);
}
@Test
public void testUsesSuppliedBrooklynProperties() throws Exception {
BrooklynProperties props = LocalManagementContextForTests.builder(true).buildProperties();
props.put("mykey", "myval");
launcher = newLauncherForTests(false)
.webconsole(false)
.brooklynProperties(props)
.start();
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("mykey"), "myval");
}
@Test
public void testUsesSupplementaryBrooklynProperties() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.brooklynProperties("mykey", "myval")
.start();
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("mykey"), "myval");
}
@Test
public void testReloadBrooklynPropertiesRestoresProgrammaticProperties() throws Exception {
launcher = newLauncherForTests(true)
.webconsole(false)
.brooklynProperties("mykey", "myval")
.start();
LocalManagementContext managementContext = (LocalManagementContext)launcher.getServerDetails().getManagementContext();
assertEquals(managementContext.getConfig().getFirst("mykey"), "myval");
managementContext.getBrooklynProperties().put("mykey", "newval");
assertEquals(managementContext.getConfig().getFirst("mykey"), "newval");
managementContext.reloadBrooklynProperties();
assertEquals(managementContext.getConfig().getFirst("mykey"), "myval");
}
@Test
public void testReloadBrooklynPropertiesFromFile() throws Exception {
File globalPropertiesFile = File.createTempFile("local-brooklyn-properties-test", ".properties");
FileUtil.setFilePermissionsTo600(globalPropertiesFile);
try {
String property = "mykey=myval";
Files.append(getMinimalLauncherPropertiesString()+property, globalPropertiesFile, Charsets.UTF_8);
launcher = newLauncherForTests(false)
.webconsole(false)
.globalBrooklynPropertiesFile(globalPropertiesFile.getAbsolutePath())
.start();
LocalManagementContext managementContext = (LocalManagementContext)launcher.getServerDetails().getManagementContext();
assertEquals(managementContext.getConfig().getFirst("mykey"), "myval");
property = "mykey=newval";
Files.write(getMinimalLauncherPropertiesString()+property, globalPropertiesFile, Charsets.UTF_8);
managementContext.reloadBrooklynProperties();
assertEquals(managementContext.getConfig().getFirst("mykey"), "newval");
} finally {
globalPropertiesFile.delete();
}
}
@Test(groups="Integration")
public void testChecksGlobalBrooklynPropertiesPermissionsX00() throws Exception {
File propsFile = File.createTempFile("testChecksGlobalBrooklynPropertiesPermissionsX00", ".properties");
propsFile.setReadable(true, false);
try {
launcher = newLauncherForTests(false)
.webconsole(false)
.globalBrooklynPropertiesFile(propsFile.getAbsolutePath())
.start();
Assert.fail("Should have thrown");
} catch (FatalRuntimeException e) {
if (!e.toString().contains("Invalid permissions for file")) throw e;
} finally {
propsFile.delete();
}
}
@Test(groups="Integration")
public void testChecksLocalBrooklynPropertiesPermissionsX00() throws Exception {
File propsFile = File.createTempFile("testChecksLocalBrooklynPropertiesPermissionsX00", ".properties");
propsFile.setReadable(true, false);
try {
launcher = newLauncherForTests(false)
.webconsole(false)
.localBrooklynPropertiesFile(propsFile.getAbsolutePath())
.start();
Assert.fail("Should have thrown");
} catch (FatalRuntimeException e) {
if (!e.toString().contains("Invalid permissions for file")) throw e;
} finally {
propsFile.delete();
}
}
@Test(groups="Integration")
public void testStartsWithSymlinkedBrooklynPropertiesPermissionsX00() throws Exception {
File dir = Files.createTempDir();
Path globalPropsFile = java.nio.file.Files.createFile(Paths.get(dir.toString(), "globalProps.properties"));
Path globalSymlink = java.nio.file.Files.createSymbolicLink(Paths.get(dir.toString(), "globalLink"), globalPropsFile);
Path localPropsFile = java.nio.file.Files.createFile(Paths.get(dir.toString(), "localPropsFile.properties"));
Path localSymlink = java.nio.file.Files.createSymbolicLink(Paths.get(dir.toString(), "localLink"), localPropsFile);
Files.write(getMinimalLauncherPropertiesString() + "key_in_global=1", globalPropsFile.toFile(), Charset.defaultCharset());
Files.write("key_in_local=2", localPropsFile.toFile(), Charset.defaultCharset());
FileUtil.setFilePermissionsTo600(globalPropsFile.toFile());
FileUtil.setFilePermissionsTo600(localPropsFile.toFile());
try {
launcher = newLauncherForTests(false)
.webconsole(false)
.localBrooklynPropertiesFile(localSymlink.toAbsolutePath().toString())
.globalBrooklynPropertiesFile(globalSymlink.toAbsolutePath().toString())
.start();
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("key_in_global"), "1");
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("key_in_local"), "2");
} finally {
Os.deleteRecursively(dir);
}
}
@Test(groups="Integration")
public void testStartsWithBrooklynPropertiesPermissionsX00() throws Exception {
File globalPropsFile = File.createTempFile("testChecksLocalBrooklynPropertiesPermissionsX00_global", ".properties");
Files.write(getMinimalLauncherPropertiesString()+"key_in_global=1", globalPropsFile, Charset.defaultCharset());
File localPropsFile = File.createTempFile("testChecksLocalBrooklynPropertiesPermissionsX00_local", ".properties");
Files.write("key_in_local=2", localPropsFile, Charset.defaultCharset());
FileUtil.setFilePermissionsTo600(globalPropsFile);
FileUtil.setFilePermissionsTo600(localPropsFile);
try {
launcher = newLauncherForTests(false)
.webconsole(false)
.localBrooklynPropertiesFile(localPropsFile.getAbsolutePath())
.globalBrooklynPropertiesFile(globalPropsFile.getAbsolutePath())
.start();
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("key_in_global"), "1");
assertEquals(launcher.getServerDetails().getManagementContext().getConfig().getFirst("key_in_local"), "2");
} finally {
globalPropsFile.delete();
localPropsFile.delete();
}
}
@Test // takes a bit of time because starts webapp, but also tests rest api so useful
public void testErrorsCaughtByApiAndRestApiWorks() throws Exception {
launcher = newLauncherForTests(true)
.catalogInitialization(new CatalogInitialization(null, false, null, false).addPopulationCallback(new Function<CatalogInitialization, Void>() {
@Override
public Void apply(CatalogInitialization input) {
throw new RuntimeException("deliberate-exception-for-testing");
}
}))
.start();
// such an error should be thrown, then caught in this calling thread
ManagementContext mgmt = launcher.getServerDetails().getManagementContext();
Assert.assertFalse( ((ManagementContextInternal)mgmt).errors().isEmpty() );
Assert.assertTrue( ((ManagementContextInternal)mgmt).errors().get(0).toString().contains("deliberate"), ""+((ManagementContextInternal)mgmt).errors() );
HttpTestUtils.assertContentMatches(
Urls.mergePaths(launcher.getServerDetails().getWebServerUrl(), "v1/server/up"),
"true");
HttpTestUtils.assertContentMatches(
Urls.mergePaths(launcher.getServerDetails().getWebServerUrl(), "v1/server/healthy"),
"false");
// TODO test errors api?
}
private BrooklynLauncher newLauncherForTests(boolean minimal) {
Preconditions.checkArgument(launcher == null, "can only be used if no launcher yet");
BrooklynLauncher launcher = BrooklynLauncher.newInstance();
if (minimal)
launcher.brooklynProperties(LocalManagementContextForTests.builder(true).buildProperties());
return launcher;
}
private String getMinimalLauncherPropertiesString() throws IOException {
BrooklynProperties p1 = LocalManagementContextForTests.builder(true).buildProperties();
Properties p = new Properties();
p.putAll(Maps.transformValues(p1.asMapWithStringKeys(), StringFunctions.toStringFunction()));
Writer w = new StringWriter();
p.store(w, "test");
w.close();
return w.toString()+"\n";
}
private void assertOnlyApp(BrooklynLauncher launcher, Class<? extends Application> expectedType) {
assertEquals(launcher.getApplications().size(), 1, "apps="+launcher.getApplications());
assertNotNull(Iterables.find(launcher.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+launcher.getApplications());
}
private void assertOnlyLocation(Application app, Class<? extends Location> expectedType) {
assertEquals(app.getLocations().size(), 1, "locs="+app.getLocations());
assertNotNull(Iterables.find(app.getLocations(), Predicates.instanceOf(LocalhostMachineProvisioningLocation.class), null), "locs="+app.getLocations());
}
}