You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/07 03:09:49 UTC
[5/8] incubator-brooklyn git commit: brooklyn-launcher: add
org.apache package prefix
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
new file mode 100644
index 0000000..ac17aa1
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
@@ -0,0 +1,1062 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.StringReader;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import brooklyn.catalog.internal.CatalogInitialization;
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BrooklynShutdownHooks;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.LocalBrooklynNode;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.PersistenceExceptionHandler;
+import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.RebindManager;
+import brooklyn.entity.rebind.RebindManagerImpl;
+import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistMode;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.entity.rebind.transformer.CompoundTransformer;
+import brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.launcher.config.StopWhichAppsOnShutdown;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.PortRange;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.HighAvailabilityManager;
+import brooklyn.management.ha.HighAvailabilityManagerImpl;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.management.ha.ManagementPlaneSyncRecord;
+import brooklyn.management.ha.ManagementPlaneSyncRecordPersister;
+import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.mementos.BrooklynMementoRawData;
+import brooklyn.rest.BrooklynWebConfig;
+import brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+import brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider;
+import brooklyn.rest.util.ShutdownHandler;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import brooklyn.util.exceptions.FatalRuntimeException;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.io.FileUtil;
+import brooklyn.util.net.Networking;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
+
+/**
+ * Example usage is:
+ * * <pre>
+ * {@code
+ * BrooklynLauncher launcher = BrooklynLauncher.newInstance()
+ * .application(new WebClusterDatabaseExample().appDisplayName("Web-cluster example"))
+ * .location("localhost")
+ * .start();
+ *
+ * Entities.dumpInfo(launcher.getApplications());
+ * </pre>
+ */
+public class BrooklynLauncher {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BrooklynLauncher.class);
+
+ /** Creates a configurable (fluent API) launcher for use starting the web console and Brooklyn applications. */
+ public static BrooklynLauncher newInstance() {
+ return new BrooklynLauncher();
+ }
+
+ private final Map<String,Object> brooklynAdditionalProperties = Maps.newLinkedHashMap();
+ private BrooklynProperties brooklynProperties;
+ private ManagementContext managementContext;
+
+ private final List<String> locationSpecs = new ArrayList<String>();
+ private final List<Location> locations = new ArrayList<Location>();
+
+ private final List<Application> appsToManage = new ArrayList<Application>();
+ private final List<ApplicationBuilder> appBuildersToManage = new ArrayList<ApplicationBuilder>();
+ private final List<String> yamlAppsToManage = new ArrayList<String>();
+ private final List<Application> apps = new ArrayList<Application>();
+
+ private boolean startWebApps = true;
+ private boolean startBrooklynNode = false;
+ private PortRange port = null;
+ private Boolean useHttps = null;
+ private InetAddress bindAddress = null;
+ private InetAddress publicAddress = null;
+ private Map<String,String> webApps = new LinkedHashMap<String,String>();
+ private Map<String, ?> webconsoleFlags = Maps.newLinkedHashMap();
+ private Boolean skipSecurityFilter = null;
+
+ private boolean ignoreWebErrors = false;
+ private boolean ignorePersistenceErrors = true;
+ private boolean ignoreCatalogErrors = true;
+ private boolean ignoreAppErrors = true;
+
+ private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+ private ShutdownHandler shutdownHandler;
+
+ private Function<ManagementContext,Void> customizeManagement = null;
+ private CatalogInitialization catalogInitialization = null;
+
+ private PersistMode persistMode = PersistMode.DISABLED;
+ private HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
+ private String persistenceDir;
+ private String persistenceLocation;
+ private Duration persistPeriod = Duration.ONE_SECOND;
+ // these default values come from config in HighAvailablilityManagerImpl
+ private Duration haHeartbeatTimeoutOverride = null;
+ private Duration haHeartbeatPeriodOverride = null;
+
+ private volatile BrooklynWebServer webServer;
+ private CampPlatform campPlatform;
+
+ private boolean started;
+ private String globalBrooklynPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties");
+ private String localBrooklynPropertiesFile;
+
+ public List<Application> getApplications() {
+ if (!started) throw new IllegalStateException("Cannot retrieve application until started");
+ return ImmutableList.copyOf(apps);
+ }
+
+ public BrooklynServerDetails getServerDetails() {
+ if (!started) throw new IllegalStateException("Cannot retrieve server details until started");
+ return new BrooklynServerDetails(webServer, managementContext);
+ }
+
+ /**
+ * Specifies that the launcher should manage the given Brooklyn application.
+ * The application must not yet be managed.
+ * The application will not be started as part of this call (callers can
+ * subsequently call {@link #start()} or {@link #getApplications()}.
+ *
+ * @see #application(ApplicationBuilder)
+ */
+ public BrooklynLauncher application(Application app) {
+ if (Entities.isManaged(app)) throw new IllegalArgumentException("Application must not already be managed");
+ appsToManage.add(checkNotNull(app, "app"));
+ return this;
+ }
+
+ /**
+ * Specifies that the launcher should build and manage the given Brooklyn application.
+ * The application must not yet be managed.
+ * The application will not be started as part of this call (callers can
+ * subsequently call {@link #start()} or {@link #getApplications()}.
+ *
+ * @see #application(Application)
+ */
+ public BrooklynLauncher application(ApplicationBuilder appBuilder) {
+ appBuildersToManage.add(checkNotNull(appBuilder, "appBuilder"));
+ return this;
+ }
+
+ /**
+ * Specifies that the launcher should build and manage the Brooklyn application
+ * described by the given spec.
+ * The application will not be started as part of this call (callers can
+ * subsequently call {@link #start()} or {@link #getApplications()}.
+ *
+ * @see #application(Application)
+ */
+ public BrooklynLauncher application(EntitySpec<? extends StartableApplication> appSpec) {
+ appBuildersToManage.add(new ApplicationBuilder(checkNotNull(appSpec, "appSpec")) {
+ @Override protected void doBuild() {
+ }});
+ return this;
+ }
+
+ /**
+ * Specifies that the launcher should build and manage the Brooklyn application
+ * described by the given YAML blueprint.
+ * The application will not be started as part of this call (callers can
+ * subsequently call {@link #start()} or {@link #getApplications()}.
+ *
+ * @see #application(Application)
+ */
+ public BrooklynLauncher application(String yaml) {
+ this.yamlAppsToManage.add(yaml);
+ return this;
+ }
+
+ /**
+ * Adds a location to be passed in on {@link #start()}, when that calls
+ * {@code application.start(locations)}.
+ */
+ public BrooklynLauncher location(Location location) {
+ locations.add(checkNotNull(location, "location"));
+ return this;
+ }
+
+ /**
+ * Give the spec of an application, to be created.
+ *
+ * @see #location(Location)
+ */
+ public BrooklynLauncher location(String spec) {
+ locationSpecs.add(checkNotNull(spec, "spec"));
+ return this;
+ }
+
+ public BrooklynLauncher locations(List<String> specs) {
+ locationSpecs.addAll(checkNotNull(specs, "specs"));
+ return this;
+ }
+
+ public BrooklynLauncher persistenceLocation(@Nullable String persistenceLocationSpec) {
+ persistenceLocation = persistenceLocationSpec;
+ return this;
+ }
+
+ public BrooklynLauncher globalBrooklynPropertiesFile(String file) {
+ globalBrooklynPropertiesFile = file;
+ return this;
+ }
+
+ public BrooklynLauncher localBrooklynPropertiesFile(String file) {
+ localBrooklynPropertiesFile = file;
+ return this;
+ }
+
+ /**
+ * Specifies the management context this launcher should use.
+ * If not specified a new one is created automatically.
+ */
+ public BrooklynLauncher managementContext(ManagementContext context) {
+ if (brooklynProperties != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+ this.managementContext = context;
+ return this;
+ }
+
+ /**
+ * Specifies the brooklyn properties to be used.
+ * Must not be set if managementContext is explicitly set.
+ */
+ public BrooklynLauncher brooklynProperties(BrooklynProperties brooklynProperties){
+ if (managementContext != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+ if (this.brooklynProperties!=null && brooklynProperties!=null && this.brooklynProperties!=brooklynProperties)
+ LOG.warn("Brooklyn properties being reset in "+this+"; set null first if you wish to clear it", new Throwable("Source of brooklyn properties reset"));
+ this.brooklynProperties = brooklynProperties;
+ return this;
+ }
+
+ /**
+ * Specifies a property to be added to the brooklyn properties
+ */
+ public BrooklynLauncher brooklynProperties(String field, Object value) {
+ brooklynAdditionalProperties.put(checkNotNull(field, "field"), value);
+ return this;
+ }
+ public <T> BrooklynLauncher brooklynProperties(ConfigKey<T> key, T value) {
+ return brooklynProperties(key.getName(), value);
+ }
+
+ /**
+ * Specifies whether the launcher will start the Brooklyn web console
+ * (and any additional webapps specified); default true.
+ */
+ public BrooklynLauncher webconsole(boolean startWebApps) {
+ this.startWebApps = startWebApps;
+ return this;
+ }
+
+ public BrooklynLauncher installSecurityFilter(Boolean val) {
+ this.skipSecurityFilter = val == null ? null : !val;
+ return this;
+ }
+
+ /**
+ * As {@link #webconsolePort(PortRange)} taking a single port
+ */
+ public BrooklynLauncher webconsolePort(int port) {
+ return webconsolePort(PortRanges.fromInteger(port));
+ }
+
+ /**
+ * As {@link #webconsolePort(PortRange)} taking a string range
+ */
+ public BrooklynLauncher webconsolePort(String port) {
+ if (port==null) return webconsolePort((PortRange)null);
+ return webconsolePort(PortRanges.fromString(port));
+ }
+
+ /**
+ * Specifies the port where the web console (and any additional webapps specified) will listen;
+ * default (null) means "8081+" being the first available >= 8081 (or "8443+" for https).
+ */
+ public BrooklynLauncher webconsolePort(PortRange port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Specifies whether the webconsole should use https.
+ */
+ public BrooklynLauncher webconsoleHttps(Boolean useHttps) {
+ this.useHttps = useHttps;
+ return this;
+ }
+
+ /**
+ * Specifies the NIC where the web console (and any additional webapps specified) will be bound;
+ * default 0.0.0.0, unless no security is specified (e.g. users) in which case it is localhost.
+ */
+ public BrooklynLauncher bindAddress(InetAddress bindAddress) {
+ this.bindAddress = bindAddress;
+ return this;
+ }
+
+ /**
+ * Specifies the address that the management context's REST API will be available on. Defaults
+ * to {@link #bindAddress} if it is not 0.0.0.0.
+ * @see #bindAddress(java.net.InetAddress)
+ */
+ public BrooklynLauncher publicAddress(InetAddress publicAddress) {
+ this.publicAddress = publicAddress;
+ return this;
+ }
+
+ /**
+ * Specifies additional flags to be passed to {@link BrooklynWebServer}.
+ */
+ public BrooklynLauncher webServerFlags(Map<String,?> webServerFlags) {
+ this.webconsoleFlags = webServerFlags;
+ return this;
+ }
+
+ /**
+ * Specifies an additional webapp to host on the webconsole port.
+ * @param contextPath The context path (e.g. "/hello", or equivalently just "hello") where the webapp will be hosted.
+ * "/" will override the brooklyn console webapp.
+ * @param warUrl The URL from which the WAR should be loaded, supporting classpath:// protocol in addition to file:// and http(s)://.
+ */
+ public BrooklynLauncher webapp(String contextPath, String warUrl) {
+ webApps.put(contextPath, warUrl);
+ return this;
+ }
+
+ public BrooklynLauncher ignorePersistenceErrors(boolean ignorePersistenceErrors) {
+ this.ignorePersistenceErrors = ignorePersistenceErrors;
+ return this;
+ }
+
+ public BrooklynLauncher ignoreCatalogErrors(boolean ignoreCatalogErrors) {
+ this.ignoreCatalogErrors = ignoreCatalogErrors;
+ return this;
+ }
+
+ public BrooklynLauncher ignoreWebErrors(boolean ignoreWebErrors) {
+ this.ignoreWebErrors = ignoreWebErrors;
+ return this;
+ }
+
+ public BrooklynLauncher ignoreAppErrors(boolean ignoreAppErrors) {
+ this.ignoreAppErrors = ignoreAppErrors;
+ return this;
+ }
+
+ public BrooklynLauncher stopWhichAppsOnShutdown(StopWhichAppsOnShutdown stopWhich) {
+ this.stopWhichAppsOnShutdown = stopWhich;
+ return this;
+ }
+
+ public BrooklynLauncher customizeManagement(Function<ManagementContext,Void> customizeManagement) {
+ this.customizeManagement = customizeManagement;
+ return this;
+ }
+
+ @Beta
+ public BrooklynLauncher catalogInitialization(CatalogInitialization catInit) {
+ if (this.catalogInitialization!=null)
+ throw new IllegalStateException("Initial catalog customization already set.");
+ this.catalogInitialization = catInit;
+ return this;
+ }
+
+ public BrooklynLauncher shutdownOnExit(boolean val) {
+ LOG.warn("Call to deprecated `shutdownOnExit`", new Throwable("source of deprecated call"));
+ stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+ return this;
+ }
+
+ public BrooklynLauncher persistMode(PersistMode persistMode) {
+ this.persistMode = persistMode;
+ return this;
+ }
+
+ public BrooklynLauncher highAvailabilityMode(HighAvailabilityMode highAvailabilityMode) {
+ this.highAvailabilityMode = highAvailabilityMode;
+ return this;
+ }
+
+ public BrooklynLauncher persistenceDir(@Nullable String persistenceDir) {
+ this.persistenceDir = persistenceDir;
+ return this;
+ }
+
+ public BrooklynLauncher persistenceDir(@Nullable File persistenceDir) {
+ if (persistenceDir==null) return persistenceDir((String)null);
+ return persistenceDir(persistenceDir.getAbsolutePath());
+ }
+
+ public BrooklynLauncher persistPeriod(Duration persistPeriod) {
+ this.persistPeriod = persistPeriod;
+ return this;
+ }
+
+ public BrooklynLauncher haHeartbeatTimeout(Duration val) {
+ this.haHeartbeatTimeoutOverride = val;
+ return this;
+ }
+
+ public BrooklynLauncher startBrooklynNode(boolean val) {
+ this.startBrooklynNode = val;
+ return this;
+ }
+
+ /**
+ * Controls both the frequency of heartbeats, and the frequency of checking the health of other nodes.
+ */
+ public BrooklynLauncher haHeartbeatPeriod(Duration val) {
+ this.haHeartbeatPeriodOverride = val;
+ return this;
+ }
+
+ /**
+ * @param destinationDir Directory for state to be copied to
+ */
+ public void copyPersistedState(String destinationDir) {
+ copyPersistedState(destinationDir, null, null);
+ }
+
+ /**
+ * A listener to call when the user requests a shutdown (i.e. through the REST API)
+ */
+ public BrooklynLauncher shutdownHandler(ShutdownHandler shutdownHandler) {
+ this.shutdownHandler = shutdownHandler;
+ return this;
+ }
+
+ /**
+ * @param destinationDir Directory for state to be copied to
+ * @param destinationLocation Optional location if target for copied state is a blob store.
+ */
+ public void copyPersistedState(String destinationDir, @Nullable String destinationLocation) {
+ copyPersistedState(destinationDir, destinationLocation, null);
+ }
+
+ /**
+ * @param destinationDir Directory for state to be copied to
+ * @param destinationLocationSpec Optional location if target for copied state is a blob store.
+ * @param transformer Optional transformations to apply to retrieved state before it is copied.
+ */
+ public void copyPersistedState(String destinationDir, @Nullable String destinationLocationSpec, @Nullable CompoundTransformer transformer) {
+ initManagementContext();
+ try {
+ highAvailabilityMode = HighAvailabilityMode.HOT_STANDBY;
+ initPersistence();
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+ }
+
+ try {
+ BrooklynMementoRawData memento = managementContext.getRebindManager().retrieveMementoRawData();
+ if (transformer != null) memento = transformer.transform(memento);
+
+ ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+
+ LOG.info("Persisting state to "+destinationDir+(destinationLocationSpec!=null ? " @ "+destinationLocationSpec : ""));
+ PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+ managementContext, destinationLocationSpec, destinationDir);
+ BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+ BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
+
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ LOG.debug("Error copying persisted state (rethrowing): " + e, e);
+ throw new FatalRuntimeException("Error copying persisted state: " +
+ Exceptions.collapseText(e), e);
+ }
+ }
+
+ /** @deprecated since 0.7.0 use {@link #copyPersistedState} instead */
+ // Make private after deprecation
+ @Deprecated
+ public BrooklynMementoRawData retrieveState() {
+ initManagementContext();
+ initPersistence();
+ return managementContext.getRebindManager().retrieveMementoRawData();
+ }
+
+ /**
+ * @param memento The state to copy
+ * @param destinationDir Directory for state to be copied to
+ * @param destinationLocation Optional location if target for copied state is a blob store.
+ * @deprecated since 0.7.0 use {@link #copyPersistedState} instead
+ */
+ // Make private after deprecation
+ @Deprecated
+ public void persistState(BrooklynMementoRawData memento, String destinationDir, @Nullable String destinationLocationSpec) {
+ initManagementContext();
+ PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+ managementContext, destinationLocationSpec, destinationDir);
+ BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+ }
+
+ /**
+ * Starts the web server (with web console) and Brooklyn applications, as per the specifications configured.
+ * @return An object containing details of the web server and the management context.
+ */
+ public BrooklynLauncher start() {
+ if (started) throw new IllegalStateException("Cannot start() or launch() multiple times");
+ started = true;
+
+ // Create the management context
+ initManagementContext();
+
+ // Inform catalog initialization that it is starting up
+ CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
+ catInit.setStartingUp(true);
+
+ // Start webapps as soon as mgmt context available -- can use them to detect progress of other processes
+ if (startWebApps) {
+ try {
+ startWebApps();
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignoreWebErrors, "core web apps", e);
+ }
+ }
+
+ // Add a CAMP platform
+ campPlatform = new BrooklynCampPlatformLauncherNoServer()
+ .useManagementContext(managementContext)
+ .launch()
+ .getCampPlatform();
+ // TODO start CAMP rest _server_ in the below (at /camp) ?
+
+ try {
+ initPersistence();
+ startPersistence();
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+ }
+
+ try {
+ // run cat init now if it hasn't yet been run;
+ // will also run if there was an ignored error in catalog above, allowing it to fail startup here if requested
+ if (catInit!=null && !catInit.hasRunOfficialInitialization()) {
+ if (persistMode==PersistMode.DISABLED) {
+ LOG.debug("Loading catalog as part of launch sequence (it was not loaded as part of any rebind sequence)");
+ catInit.populateCatalog(ManagementNodeState.MASTER, true, true, null);
+ } else {
+ // should have loaded during rebind
+ ManagementNodeState state = managementContext.getHighAvailabilityManager().getNodeState();
+ LOG.warn("Loading catalog for "+state+" as part of launch sequence (it was not loaded as part of the rebind sequence)");
+ catInit.populateCatalog(state, true, true, null);
+ }
+ }
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignoreCatalogErrors, "initial catalog", e);
+ }
+ catInit.setStartingUp(false);
+
+ // Create the locations. Must happen after persistence is started in case the
+ // management context's catalog is loaded from persisted state. (Location
+ // resolution uses the catalog's classpath to scan for resolvers.)
+ locations.addAll(managementContext.getLocationRegistry().resolve(locationSpecs));
+
+ // Already rebinded successfully, so previous apps are now available.
+ // Allow the startup to be visible in console for newly created apps.
+ ((LocalManagementContext)managementContext).noteStartupComplete();
+
+ // TODO create apps only after becoming master, analogously to catalog initialization
+ try {
+ createApps();
+ startApps();
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignoreAppErrors, "brooklyn autostart apps", e);
+ }
+
+ if (startBrooklynNode) {
+ try {
+ startBrooklynNode();
+ } catch (Exception e) {
+ handleSubsystemStartupError(ignoreAppErrors, "brooklyn node / self entity", e);
+ }
+ }
+
+ if (persistMode != PersistMode.DISABLED) {
+ // Make sure the new apps are persisted in case process exits immediately.
+ managementContext.getRebindManager().forcePersistNow(false, null);
+ }
+ return this;
+ }
+
+ private void initManagementContext() {
+ // Create the management context
+ if (managementContext == null) {
+ if (brooklynProperties == null) {
+ BrooklynProperties.Factory.Builder builder = BrooklynProperties.Factory.builderDefault();
+ if (globalBrooklynPropertiesFile != null) {
+ if (fileExists(globalBrooklynPropertiesFile)) {
+ LOG.debug("Using global properties file "+globalBrooklynPropertiesFile);
+ // brooklyn.properties stores passwords (web-console and cloud credentials),
+ // so ensure it has sensible permissions
+ checkFileReadable(globalBrooklynPropertiesFile);
+ checkFilePermissionsX00(globalBrooklynPropertiesFile);
+ } else {
+ LOG.debug("Global properties file "+globalBrooklynPropertiesFile+" does not exist, will ignore");
+ }
+ builder.globalPropertiesFile(globalBrooklynPropertiesFile);
+ } else {
+ LOG.debug("Global properties file disabled");
+ builder.globalPropertiesFile(null);
+ }
+
+ if (localBrooklynPropertiesFile != null) {
+ checkFileReadable(localBrooklynPropertiesFile);
+ checkFilePermissionsX00(localBrooklynPropertiesFile);
+ builder.localPropertiesFile(localBrooklynPropertiesFile);
+ }
+ managementContext = new LocalManagementContext(builder, brooklynAdditionalProperties);
+ } else {
+ if (globalBrooklynPropertiesFile != null)
+ LOG.warn("Ignoring globalBrooklynPropertiesFile "+globalBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+ if (localBrooklynPropertiesFile != null)
+ LOG.warn("Ignoring localBrooklynPropertiesFile "+localBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+ managementContext = new LocalManagementContext(brooklynProperties, brooklynAdditionalProperties);
+ }
+ brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+
+ // We created the management context, so we are responsible for terminating it
+ BrooklynShutdownHooks.invokeTerminateOnShutdown(managementContext);
+
+ } else if (brooklynProperties == null) {
+ brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+ brooklynProperties.addFromMap(brooklynAdditionalProperties);
+ }
+
+ if (catalogInitialization!=null) {
+ ((ManagementContextInternal)managementContext).setCatalogInitialization(catalogInitialization);
+ }
+
+ if (customizeManagement!=null) {
+ customizeManagement.apply(managementContext);
+ }
+ }
+
+ private boolean fileExists(String file) {
+ return new File(Os.tidyPath(file)).exists();
+ }
+
+ private void checkFileReadable(String file) {
+ File f = new File(Os.tidyPath(file));
+ if (!f.exists()) {
+ throw new FatalRuntimeException("File "+file+" does not exist");
+ }
+ if (!f.isFile()) {
+ throw new FatalRuntimeException(file+" is not a file");
+ }
+ if (!f.canRead()) {
+ throw new FatalRuntimeException(file+" is not readable");
+ }
+ }
+
+ private void checkFilePermissionsX00(String file) {
+ File f = new File(Os.tidyPath(file));
+
+ Maybe<String> permission = FileUtil.getFilePermissions(f);
+ if (permission.isAbsent()) {
+ LOG.debug("Could not determine permissions of file; assuming ok: "+f);
+ } else {
+ if (!permission.get().subSequence(4, 10).equals("------")) {
+ throw new FatalRuntimeException("Invalid permissions for file "+file+"; expected ?00 but was "+permission.get());
+ }
+ }
+ }
+
+ private void handleSubsystemStartupError(boolean ignoreSuchErrors, String system, Exception e) {
+ Exceptions.propagateIfFatal(e);
+ if (ignoreSuchErrors) {
+ LOG.error("Subsystem for "+system+" had startup error (continuing with startup): "+e, e);
+ if (managementContext!=null)
+ ((ManagementContextInternal)managementContext).errors().add(e);
+ } else {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ protected void startWebApps() {
+ // No security options in properties and no command line options overriding.
+ if (Boolean.TRUE.equals(skipSecurityFilter) && bindAddress==null) {
+ LOG.info("Starting Brooklyn web-console on loopback because security is explicitly disabled and no bind address specified");
+ bindAddress = Networking.LOOPBACK;
+ } else if (BrooklynWebConfig.hasNoSecurityOptions(brooklynProperties)) {
+ if (bindAddress==null) {
+ LOG.info("Starting Brooklyn web-console with passwordless access on localhost and protected access from any other interfaces (no bind address specified)");
+ } else {
+ if (Arrays.equals(new byte[] { 127, 0, 0, 1 }, bindAddress.getAddress())) {
+ LOG.info("Starting Brooklyn web-console with passwordless access on localhost");
+ } else if (Arrays.equals(new byte[] { 0, 0, 0, 0 }, bindAddress.getAddress())) {
+ LOG.info("Starting Brooklyn web-console with passwordless access on localhost and random password (logged) required from any other interfaces");
+ } else {
+ LOG.info("Starting Brooklyn web-console with passwordless access on localhost (if permitted) and random password (logged) required from any other interfaces");
+ }
+ }
+ brooklynProperties.put(
+ BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME,
+ BrooklynUserWithRandomPasswordSecurityProvider.class.getName());
+ } else {
+ LOG.debug("Starting Brooklyn using security properties: "+brooklynProperties.submap(ConfigPredicates.startingWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys());
+ }
+ if (bindAddress == null) bindAddress = Networking.ANY_NIC;
+
+ LOG.debug("Starting Brooklyn web-console with bindAddress "+bindAddress+" and properties "+brooklynProperties);
+ try {
+ webServer = new BrooklynWebServer(webconsoleFlags, managementContext);
+ webServer.setBindAddress(bindAddress);
+ webServer.setPublicAddress(publicAddress);
+ if (port!=null) webServer.setPort(port);
+ if (useHttps!=null) webServer.setHttpsEnabled(useHttps);
+ webServer.setShutdownHandler(shutdownHandler);
+ webServer.putAttributes(brooklynProperties);
+ if (skipSecurityFilter != Boolean.TRUE) {
+ webServer.setSecurityFilter(BrooklynPropertiesSecurityFilter.class);
+ }
+ for (Map.Entry<String, String> webapp : webApps.entrySet()) {
+ webServer.addWar(webapp.getKey(), webapp.getValue());
+ }
+ webServer.start();
+
+ } catch (Exception e) {
+ LOG.warn("Failed to start Brooklyn web-console (rethrowing): " + Exceptions.collapseText(e));
+ throw new FatalRuntimeException("Failed to start Brooklyn web-console: " + Exceptions.collapseText(e), e);
+ }
+ }
+
+ protected void initPersistence() {
+ // Prepare the rebind directory, and initialise the RebindManager as required
+ final PersistenceObjectStore objectStore;
+ if (persistMode == PersistMode.DISABLED) {
+ LOG.info("Persistence disabled");
+ objectStore = null;
+
+ } else {
+ try {
+ if (persistenceLocation == null) {
+ persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
+ }
+ persistenceDir = BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(persistenceLocation).dir(persistenceDir).resolve();
+ objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir,
+ persistMode, highAvailabilityMode);
+
+ RebindManager rebindManager = managementContext.getRebindManager();
+
+ BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+ objectStore,
+ ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+ managementContext.getCatalogClassLoader());
+ PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+ ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(persistPeriod);
+ rebindManager.setPersister(persister, persistenceExceptionHandler);
+ } catch (FatalConfigurationRuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ LOG.debug("Error initializing persistence subsystem (rethrowing): "+e, e);
+ throw new FatalRuntimeException("Error initializing persistence subsystem: "+
+ Exceptions.collapseText(e), e);
+ }
+ }
+
+ // Initialise the HA manager as required
+ if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+ LOG.info("High availability disabled");
+ } else {
+ if (objectStore==null)
+ throw new FatalConfigurationRuntimeException("Cannot run in HA mode when no persistence configured.");
+
+ HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+ ManagementPlaneSyncRecordPersister persister =
+ new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext,
+ objectStore,
+ managementContext.getCatalogClassLoader());
+ ((HighAvailabilityManagerImpl)haManager).setHeartbeatTimeout(haHeartbeatTimeoutOverride);
+ ((HighAvailabilityManagerImpl)haManager).setPollPeriod(haHeartbeatPeriodOverride);
+ haManager.setPersister(persister);
+ }
+ }
+
+ protected void startPersistence() {
+ // Now start the HA Manager and the Rebind manager, as required
+ if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+ HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+ haManager.disabled();
+
+ if (persistMode != PersistMode.DISABLED) {
+ startPersistenceWithoutHA();
+ }
+
+ } else {
+ // Let the HA manager decide when objectstore.prepare and rebindmgr.rebind need to be called
+ // (based on whether other nodes in plane are already running).
+
+ HighAvailabilityMode startMode=null;
+ switch (highAvailabilityMode) {
+ case AUTO:
+ case MASTER:
+ case STANDBY:
+ case HOT_STANDBY:
+ case HOT_BACKUP:
+ startMode = highAvailabilityMode;
+ break;
+ case DISABLED:
+ throw new IllegalStateException("Unexpected code-branch for high availability mode "+highAvailabilityMode);
+ }
+ if (startMode==null)
+ throw new IllegalStateException("Unexpected high availability mode "+highAvailabilityMode);
+
+ LOG.debug("Management node (with HA) starting");
+ HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+ // prepare after HA mode is known, to prevent backups happening in standby mode
+ haManager.start(startMode);
+ }
+ }
+
+ private void startPersistenceWithoutHA() {
+ RebindManager rebindManager = managementContext.getRebindManager();
+ if (Strings.isNonBlank(persistenceLocation))
+ LOG.info("Management node (no HA) rebinding to entities at "+persistenceLocation+" in "+persistenceDir);
+ else
+ LOG.info("Management node (no HA) rebinding to entities on file system in "+persistenceDir);
+
+ ClassLoader classLoader = managementContext.getCatalogClassLoader();
+ try {
+ rebindManager.rebind(classLoader, null, ManagementNodeState.MASTER);
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ LOG.debug("Error rebinding to persisted state (rethrowing): "+e, e);
+ throw new FatalRuntimeException("Error rebinding to persisted state: "+
+ Exceptions.collapseText(e), e);
+ }
+ rebindManager.startPersistence();
+ }
+
+ protected void createApps() {
+ for (ApplicationBuilder appBuilder : appBuildersToManage) {
+ StartableApplication app = appBuilder.manage(managementContext);
+ apps.add(app);
+ }
+ for (Application app : appsToManage) {
+ Entities.startManagement(app, managementContext);
+ apps.add(app);
+ }
+ for (String blueprint : yamlAppsToManage) {
+ Application app = getAppFromYaml(blueprint);
+ // Note: BrooklynAssemblyTemplateInstantiator automatically puts applications under management.
+ apps.add(app);
+ }
+ }
+
+ protected void startBrooklynNode() {
+ final String classpath = System.getenv("INITIAL_CLASSPATH");
+ if (Strings.isBlank(classpath)) {
+ LOG.warn("Cannot find INITIAL_CLASSPATH environment variable, skipping BrooklynNode entity creation");
+ return;
+ }
+ if (webServer == null || !startWebApps) {
+ LOG.info("Skipping BrooklynNode entity creation, BrooklynWebServer not running");
+ return;
+ }
+ ApplicationBuilder brooklyn = new ApplicationBuilder() {
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void doBuild() {
+ addChild(EntitySpec.create(LocalBrooklynNode.class)
+ .configure(SoftwareProcess.ENTITY_STARTED, true)
+ .configure(SoftwareProcess.RUN_DIR, System.getenv("ROOT"))
+ .configure(SoftwareProcess.INSTALL_DIR, System.getenv("BROOKLYN_HOME"))
+ .configure(BrooklynNode.ENABLED_HTTP_PROTOCOLS, ImmutableList.of(webServer.getHttpsEnabled() ? "https" : "http"))
+ .configure(webServer.getHttpsEnabled() ? BrooklynNode.HTTPS_PORT : BrooklynNode.HTTP_PORT, PortRanges.fromInteger(webServer.getActualPort()))
+ .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, bindAddress)
+ .configure(BrooklynNode.WEB_CONSOLE_PUBLIC_ADDRESS, publicAddress)
+ .configure(BrooklynNode.CLASSPATH, Splitter.on(":").splitToList(classpath))
+ .configure(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE.equals(skipSecurityFilter))
+ .displayName("Brooklyn Console"));
+ }
+ };
+ LocationSpec<?> spec = LocationSpec.create(LocalhostMachine.class).displayName("Local Brooklyn");
+ Location localhost = managementContext.getLocationManager().createLocation(spec);
+ brooklyn.appDisplayName("Brooklyn")
+ .manage(managementContext)
+ .start(ImmutableList.of(localhost));
+ }
+
+ protected Application getAppFromYaml(String input) {
+ AssemblyTemplate at = campPlatform.pdp().registerDeploymentPlan(new StringReader(input));
+ BrooklynAssemblyTemplateInstantiator instantiator;
+ try {
+ AssemblyTemplateInstantiator ati = at.getInstantiator().newInstance();
+ if (ati instanceof BrooklynAssemblyTemplateInstantiator) {
+ instantiator = BrooklynAssemblyTemplateInstantiator.class.cast(ati);
+ } else {
+ throw new IllegalStateException("Cannot create application with instantiator: " + ati);
+ }
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ Application app = instantiator.create(at, campPlatform);
+ return app;
+ }
+
+ protected void startApps() {
+ if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL) ||
+ (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+ BrooklynShutdownHooks.invokeStopAppsOnShutdown(managementContext);
+ }
+
+ List<Throwable> appExceptions = Lists.newArrayList();
+ for (Application app : apps) {
+ if (app instanceof Startable) {
+
+ if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE) ||
+ (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+ BrooklynShutdownHooks.invokeStopOnShutdown(app);
+ }
+ try {
+ LOG.info("Starting brooklyn application {} in location{} {}", new Object[] { app, locations.size()!=1?"s":"", locations });
+ ((Startable)app).start(locations);
+ } catch (Exception e) {
+ LOG.error("Error starting "+app+": "+Exceptions.collapseText(e), Exceptions.getFirstInteresting(e));
+ appExceptions.add(Exceptions.collapse(e));
+
+ if (Thread.currentThread().isInterrupted()) {
+ LOG.error("Interrupted while starting applications; aborting");
+ break;
+ }
+ }
+ }
+ }
+ if (!appExceptions.isEmpty()) {
+ Throwable t = Exceptions.create(appExceptions);
+ throw new FatalRuntimeException("Error starting applications: "+Exceptions.collapseText(t), t);
+ }
+ }
+
+ public boolean isStarted() {
+ return started;
+ }
+
+ /**
+ * Terminates this launch, but does <em>not</em> stop the applications (i.e. external processes
+ * are left running, etc). However, by terminating the management console the brooklyn applications
+ * become unusable.
+ */
+ public void terminate() {
+ if (!started) return; // no-op
+
+ if (webServer != null) {
+ try {
+ webServer.stop();
+ } catch (Exception e) {
+ LOG.warn("Error stopping web-server; continuing with termination", e);
+ }
+ }
+
+ // TODO Do we want to do this as part of managementContext.terminate, so after other threads are terminated etc?
+ // Otherwise the app can change between this persist and the terminate.
+ if (persistMode != PersistMode.DISABLED) {
+ try {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ if (managementContext.getHighAvailabilityManager().getPersister() != null) {
+ managementContext.getHighAvailabilityManager().getPersister().waitForWritesCompleted(Duration.TEN_SECONDS);
+ }
+ managementContext.getRebindManager().waitForPendingComplete(Duration.TEN_SECONDS, true);
+ LOG.info("Finished waiting for persist; took "+Time.makeTimeStringRounded(stopwatch));
+ } catch (RuntimeInterruptedException e) {
+ Thread.currentThread().interrupt(); // keep going with shutdown
+ LOG.warn("Persistence interrupted during shutdown: "+e, e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // keep going with shutdown
+ LOG.warn("Persistence interrupted during shutdown: "+e, e);
+ } catch (TimeoutException e) {
+ LOG.warn("Timeout after 10 seconds waiting for persistence to write all data; continuing");
+ }
+ }
+
+ if (managementContext instanceof ManagementContextInternal) {
+ ((ManagementContextInternal)managementContext).terminate();
+ }
+
+ for (Location loc : locations) {
+ if (loc instanceof Closeable) {
+ Streams.closeQuietly((Closeable)loc);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
new file mode 100644
index 0000000..1ffa8cf
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
@@ -0,0 +1,47 @@
+/*
+ * 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 brooklyn.management.ManagementContext;
+
+public class BrooklynServerDetails {
+
+ protected BrooklynWebServer webServer;
+ protected ManagementContext mgmtContext;
+
+ public BrooklynServerDetails(BrooklynWebServer webServer, ManagementContext mgmtContext) {
+ super();
+ this.webServer = webServer;
+ this.mgmtContext = mgmtContext;
+ }
+
+ public BrooklynWebServer getWebServer() {
+ return webServer;
+ }
+
+ public String getWebServerUrl() {
+ if (webServer==null) return null;
+ return webServer.getRootUrl();
+ }
+
+ public ManagementContext getManagementContext() {
+ return mgmtContext;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
new file mode 100644
index 0000000..1f2bc5d
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -0,0 +1,652 @@
+/*
+ * 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 java.io.File;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+import brooklyn.BrooklynVersion;
+import brooklyn.config.BrooklynServerPaths;
+import brooklyn.config.BrooklynServiceAttributes;
+import brooklyn.config.ConfigKey;
+import brooklyn.internal.BrooklynInitialization;
+import org.apache.brooklyn.launcher.config.CustomResourceLocator;
+import brooklyn.location.PortRange;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.rest.BrooklynRestApi;
+import brooklyn.rest.BrooklynWebConfig;
+import brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+import brooklyn.rest.filter.HaHotCheckResourceFilter;
+import brooklyn.rest.filter.HaMasterCheckFilter;
+import brooklyn.rest.filter.LoggingFilter;
+import brooklyn.rest.filter.NoCacheFilter;
+import brooklyn.rest.filter.RequestTaggingFilter;
+import brooklyn.rest.util.ManagementContextProvider;
+import brooklyn.rest.util.ShutdownHandler;
+import brooklyn.rest.util.ShutdownHandlerProvider;
+import brooklyn.util.BrooklynNetworkUtils;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.crypto.FluentKeySigner;
+import brooklyn.util.crypto.SecureKeys;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.io.FileUtil;
+import brooklyn.util.javalang.Threads;
+import brooklyn.util.logging.LoggingSetup;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
+
+/**
+ * Starts the web-app running, connected to the given management context
+ */
+public class BrooklynWebServer {
+ private static final Logger log = LoggerFactory.getLogger(BrooklynWebServer.class);
+
+ public static final String BROOKLYN_WAR_URL = "classpath://brooklyn.war";
+ static {
+ // support loading the WAR in dev mode from an alternate location
+ CustomResourceLocator.registerAlternateLocator(new CustomResourceLocator.SearchingClassPathInDevMode(
+ BROOKLYN_WAR_URL, "/usage/launcher/target",
+ "/usage/jsgui/target/brooklyn-jsgui-"+BrooklynVersion.get()+".war"));
+ }
+
+ static {
+ LoggingSetup.installJavaUtilLoggingBridge();
+ }
+
+ protected Server server;
+
+ private WebAppContext rootContext;
+
+ /** base port to use, for http if enabled or else https; if not set, it uses httpPort or httpsPort */
+ @SetFromFlag("port")
+ protected PortRange requestedPort = null;
+
+ @SetFromFlag
+ protected PortRange httpPort = PortRanges.fromString("8081+");
+ @SetFromFlag
+ protected PortRange httpsPort = PortRanges.fromString("8443+");
+
+ /** actual port where this gets bound; will be consistent with the "port" passed in
+ * but that might be a range and here it is a single port, or -1 if not yet set */
+ protected volatile int actualPort = -1;
+ /** actual NIC where this is listening; in the case of 0.0.0.0 being passed in as bindAddress,
+ * this will revert to one address (such as localhost) */
+ protected InetAddress actualAddress = null;
+
+ @SetFromFlag
+ protected String war = BROOKLYN_WAR_URL;
+
+ /** IP of NIC where this server should bind, or null to autodetect
+ * (e.g. 0.0.0.0 if security is configured, or loopback if no security) */
+ @SetFromFlag
+ protected InetAddress bindAddress = null;
+
+ /** The address that this server's management context will be publically available on. */
+ @SetFromFlag
+ protected InetAddress publicAddress = null;
+
+ /**
+ * map of context-prefix to file
+ */
+ @SetFromFlag
+ private Map<String, String> wars = new LinkedHashMap<String, String>();
+
+ @SetFromFlag
+ protected boolean ignoreWebappDeploymentFailures = false;
+
+ @SetFromFlag
+ private Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private ManagementContext managementContext;
+
+ @SetFromFlag
+ private Boolean httpsEnabled;
+
+ @SetFromFlag
+ private String sslCertificate;
+
+ @SetFromFlag
+ private String keystoreUrl;
+
+ @SetFromFlag @Deprecated /** @deprecated use keystoreUrl */
+ private String keystorePath;
+
+ @SetFromFlag
+ private String keystorePassword;
+
+ @SetFromFlag
+ private String keystoreCertAlias;
+
+ @SetFromFlag
+ private String truststorePath;
+
+ @SetFromFlag
+ private String trustStorePassword;
+
+ @SetFromFlag
+ private String transportProtocols;
+
+ @SetFromFlag
+ private String transportCiphers;
+
+ private File webappTempDir;
+
+ private Class<BrooklynPropertiesSecurityFilter> securityFilterClazz;
+
+ private ShutdownHandler shutdownHandler;
+
+ public BrooklynWebServer(ManagementContext managementContext) {
+ this(Maps.newLinkedHashMap(), managementContext);
+ }
+
+ /**
+ * accepts flags: port,
+ * war (url of war file which is the root),
+ * wars (map of context-prefix to url),
+ * attrs (map of attribute-name : object pairs passed to the servlet)
+ */
+ public BrooklynWebServer(Map<?,?> flags, ManagementContext managementContext) {
+ this.managementContext = managementContext;
+ Map<?,?> leftovers = FlagUtils.setFieldsFromFlags(flags, this);
+ if (!leftovers.isEmpty())
+ log.warn("Ignoring unknown flags " + leftovers);
+
+ webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext);
+ }
+
+ public BrooklynWebServer(ManagementContext managementContext, int port) {
+ this(managementContext, port, "brooklyn.war");
+ }
+
+ public BrooklynWebServer(ManagementContext managementContext, int port, String warUrl) {
+ this(MutableMap.of("port", port, "war", warUrl), managementContext);
+ }
+
+ public void setSecurityFilter(Class<BrooklynPropertiesSecurityFilter> filterClazz) {
+ this.securityFilterClazz = filterClazz;
+ }
+
+ public void setShutdownHandler(@Nullable ShutdownHandler shutdownHandler) {
+ this.shutdownHandler = shutdownHandler;
+ }
+
+ public BrooklynWebServer setPort(Object port) {
+ if (getActualPort()>0)
+ throw new IllegalStateException("Can't set port after port has been assigned to server (using "+getActualPort()+")");
+ this.requestedPort = TypeCoercions.coerce(port, PortRange.class);
+ return this;
+ }
+
+ @VisibleForTesting
+ File getWebappTempDir() {
+ return webappTempDir;
+ }
+
+ public BrooklynWebServer setHttpsEnabled(Boolean httpsEnabled) {
+ this.httpsEnabled = httpsEnabled;
+ return this;
+ }
+
+ public boolean getHttpsEnabled() {
+ return getConfig(httpsEnabled, BrooklynWebConfig.HTTPS_REQUIRED);
+ }
+
+ public PortRange getRequestedPort() {
+ return requestedPort;
+ }
+
+ /** returns port where this is running, or -1 if not yet known */
+ public int getActualPort() {
+ return actualPort;
+ }
+
+ /** interface/address where this server is listening;
+ * if bound to 0.0.0.0 (all NICs, e.g. because security is set) this will return one NIC where this is bound */
+ public InetAddress getAddress() {
+ return actualAddress;
+ }
+
+ /** URL for accessing this web server (root context) */
+ public String getRootUrl() {
+ String address = (publicAddress != null) ? publicAddress.getHostName() : getAddress().getHostName();
+ if (getActualPort()>0){
+ String protocol = getHttpsEnabled()?"https":"http";
+ return protocol+"://"+address+":"+getActualPort()+"/";
+ } else {
+ return null;
+ }
+ }
+
+ /** sets the WAR to use as the root context (only if server not yet started);
+ * cf deploy("/", url) */
+ public BrooklynWebServer setWar(String url) {
+ this.war = url;
+ return this;
+ }
+
+ /** specifies a WAR to use at a given context path (only if server not yet started);
+ * cf deploy(path, url) */
+ public BrooklynWebServer addWar(String path, String warUrl) {
+ wars.put(path, warUrl);
+ return this;
+ }
+
+ /** InetAddress to which server should bind;
+ * defaults to 0.0.0.0 (although common call path is to set to 127.0.0.1 when security is not set) */
+ public BrooklynWebServer setBindAddress(InetAddress address) {
+ bindAddress = address;
+ return this;
+ }
+
+ /**
+ * Sets the public address that the server's management context's REST API will be available on
+ */
+ public BrooklynWebServer setPublicAddress(InetAddress address) {
+ publicAddress = address;
+ return this;
+ }
+
+ /** @deprecated use setAttribute */
+ public BrooklynWebServer addAttribute(String field, Object value) {
+ return setAttribute(field, value);
+ }
+ /** Specifies an attribute passed to deployed webapps
+ * (in addition to {@link BrooklynServiceAttributes#BROOKLYN_MANAGEMENT_CONTEXT} */
+ public BrooklynWebServer setAttribute(String field, Object value) {
+ attributes.put(field, value);
+ return this;
+ }
+
+ public <T> BrooklynWebServer configure(ConfigKey<T> key, T value) {
+ return setAttribute(key.getName(), value);
+ }
+
+ /** Specifies attributes passed to deployed webapps
+ * (in addition to {@link BrooklynServiceAttributes#BROOKLYN_MANAGEMENT_CONTEXT} */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public BrooklynWebServer putAttributes(Map newAttrs) {
+ if (newAttrs!=null) attributes.putAll(newAttrs);
+ return this;
+ }
+
+ public void installAsServletFilter(ServletContextHandler context) {
+ ResourceConfig config = new DefaultResourceConfig();
+ // load all our REST API modules, JSON, and Swagger
+ for (Object r: BrooklynRestApi.getAllResources())
+ config.getSingletons().add(r);
+
+ // Accept gzipped requests and responses, disable caching for dynamic content
+ config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName());
+ config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ImmutableList.of(GZIPContentEncodingFilter.class, NoCacheFilter.class));
+ // Checks if appropriate request given HA status
+ config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, HaHotCheckResourceFilter.class.getName());
+ // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
+ // and treat that as static content
+ config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
+ // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
+ config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
+ // finally create this as a _filter_ which falls through to a web app or something (optionally)
+ FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
+
+ context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
+
+ ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+ config.getSingletons().add(new ManagementContextProvider(mgmt));
+
+ config.getSingletons().add(new ShutdownHandlerProvider(shutdownHandler));
+ }
+
+ ContextHandlerCollectionHotSwappable handlers = new ContextHandlerCollectionHotSwappable();
+
+ /**
+ * Starts the embedded web application server.
+ */
+ public synchronized void start() throws Exception {
+ if (server != null) throw new IllegalStateException(""+this+" already running");
+
+ if (actualPort == -1){
+ PortRange portRange = getConfig(requestedPort, BrooklynWebConfig.WEB_CONSOLE_PORT);
+ if (portRange==null) {
+ portRange = getHttpsEnabled() ? httpsPort : httpPort;
+ }
+ actualPort = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), portRange);
+ if (actualPort == -1)
+ throw new IllegalStateException("Unable to provision port for web console (wanted "+portRange+")");
+ }
+
+ server = new Server();
+ final Connector connector;
+ if (getHttpsEnabled()) {
+ connector = new SslSelectChannelConnector(createContextFactory());
+ } else {
+ connector = new SelectChannelConnector();
+ }
+ if (bindAddress != null) {
+ connector.setHost(bindAddress.getHostName());
+ }
+ connector.setPort(actualPort);
+ server.setConnectors(new Connector[]{connector});
+
+ if (bindAddress == null || bindAddress.equals(InetAddress.getByAddress(new byte[] { 0, 0, 0, 0 }))) {
+ actualAddress = BrooklynNetworkUtils.getLocalhostInetAddress();
+ } else {
+ actualAddress = bindAddress;
+ }
+
+ // use a nice name in the thread pool (otherwise this is exactly the same as Server defaults)
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setName("brooklyn-jetty-server-"+actualPort+"-"+threadPool.getName());
+ server.setThreadPool(threadPool);
+
+ if (log.isDebugEnabled())
+ log.debug("Starting Brooklyn console at "+getRootUrl()+", running " + war + (wars != null ? " and " + wars.values() : ""));
+
+ addShutdownHook();
+
+ MutableMap<String, String> allWars = MutableMap.copyOf(wars);
+ String rootWar = allWars.remove("/");
+ if (rootWar==null) rootWar = war;
+
+ for (Map.Entry<String, String> entry : allWars.entrySet()) {
+ String pathSpec = entry.getKey();
+ String warUrl = entry.getValue();
+ WebAppContext webapp = deploy(pathSpec, warUrl);
+ webapp.setTempDirectory(Os.mkdirs(new File(webappTempDir, newTimestampedDirName("war", 8))));
+ }
+ rootContext = deploy("/", rootWar);
+ rootContext.setTempDirectory(Os.mkdirs(new File(webappTempDir, "war-root")));
+
+ rootContext.addFilter(RequestTaggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+ if (securityFilterClazz != null) {
+ rootContext.addFilter(securityFilterClazz, "/*", EnumSet.allOf(DispatcherType.class));
+ }
+ rootContext.addFilter(LoggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+ rootContext.addFilter(HaMasterCheckFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+ installAsServletFilter(rootContext);
+
+ server.setHandler(handlers);
+ server.start();
+ //reinit required because some webapps (eg grails) might wipe our language extension bindings
+ BrooklynInitialization.reinitAll();
+
+ if (managementContext instanceof ManagementContextInternal) {
+ ((ManagementContextInternal) managementContext).setManagementNodeUri(new URI(getRootUrl()));
+ }
+
+ log.info("Started Brooklyn console at "+getRootUrl()+", running " + rootWar + (allWars!=null && !allWars.isEmpty() ? " and " + wars.values() : ""));
+ }
+
+ private SslContextFactory createContextFactory() throws KeyStoreException {
+ SslContextFactory sslContextFactory = new SslContextFactory();
+
+ // allow webconsole keystore & related properties to be set in brooklyn.properties
+ String ksUrl = getKeystoreUrl();
+ String ksPassword = getConfig(keystorePassword, BrooklynWebConfig.KEYSTORE_PASSWORD);
+ String ksCertAlias = getConfig(keystoreCertAlias, BrooklynWebConfig.KEYSTORE_CERTIFICATE_ALIAS);
+ String trProtos = getConfig(transportProtocols, BrooklynWebConfig.TRANSPORT_PROTOCOLS);
+ String trCiphers = getConfig(transportCiphers, BrooklynWebConfig.TRANSPORT_CIPHERS);
+
+ if (ksUrl!=null) {
+ sslContextFactory.setKeyStorePath(getLocalKeyStorePath(ksUrl));
+ if (Strings.isEmpty(ksPassword))
+ throw new IllegalArgumentException("Keystore password is required and non-empty if keystore is specified.");
+ sslContextFactory.setKeyStorePassword(ksPassword);
+ if (Strings.isNonEmpty(ksCertAlias))
+ sslContextFactory.setCertAlias(ksCertAlias);
+ } else {
+ log.info("No keystore specified but https enabled; creating a default keystore");
+
+ if (Strings.isEmpty(ksCertAlias))
+ ksCertAlias = "web-console";
+
+ // if password is blank the process will block and read from stdin !
+ if (Strings.isEmpty(ksPassword)) {
+ ksPassword = Identifiers.makeRandomId(8);
+ log.debug("created random password "+ksPassword+" for ad hoc internal keystore");
+ }
+
+ KeyStore ks = SecureKeys.newKeyStore();
+ KeyPair key = SecureKeys.newKeyPair();
+ X509Certificate cert = new FluentKeySigner("brooklyn").newCertificateFor("web-console", key);
+ ks.setKeyEntry(ksCertAlias, key.getPrivate(), ksPassword.toCharArray(),
+ new Certificate[] { cert });
+
+ sslContextFactory.setKeyStore(ks);
+ sslContextFactory.setKeyStorePassword(ksPassword);
+ sslContextFactory.setCertAlias(ksCertAlias);
+ }
+ if (!Strings.isEmpty(truststorePath)) {
+ sslContextFactory.setTrustStore(checkFileExists(truststorePath, "truststore"));
+ sslContextFactory.setTrustStorePassword(trustStorePassword);
+ }
+
+ if (Strings.isNonBlank(trProtos)) {
+ sslContextFactory.setIncludeProtocols(parseArray(trProtos));
+ }
+ if (Strings.isNonBlank(trCiphers)) {
+ sslContextFactory.setIncludeCipherSuites(parseArray(trCiphers));
+ }
+ return sslContextFactory;
+ }
+
+ private String[] parseArray(String list) {
+ List<String> arr = Splitter.on(",").omitEmptyStrings().trimResults().splitToList(list);
+ return arr.toArray(new String[arr.size()]);
+ }
+
+ private String getKeystoreUrl() {
+ if (keystoreUrl != null) {
+ if (Strings.isNonBlank(keystorePath) && !keystoreUrl.equals(keystorePath)) {
+ log.warn("Deprecated 'keystorePath' supplied with different value than 'keystoreUrl', preferring the latter: "+
+ keystorePath+" / "+keystoreUrl);
+ }
+ return keystoreUrl;
+ } else if (Strings.isNonBlank(keystorePath)) {
+ log.warn("Deprecated 'keystorePath' used; callers should use 'keystoreUrl'");
+ return keystorePath;
+ } else {
+ return managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_URL);
+ }
+ }
+
+ private <T> T getConfig(T override, ConfigKey<T> key) {
+ if (override!=null) {
+ return override;
+ } else {
+ return managementContext.getConfig().getConfig(key);
+ }
+ }
+
+ private String getLocalKeyStorePath(String keystoreUrl) {
+ ResourceUtils res = ResourceUtils.create(this);
+ res.checkUrlExists(keystoreUrl, BrooklynWebConfig.KEYSTORE_URL.getName());
+ if (new File(keystoreUrl).exists()) {
+ return keystoreUrl;
+ } else {
+ InputStream keystoreStream;
+ try {
+ keystoreStream = res.getResourceFromUrl(keystoreUrl);
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ throw new IllegalArgumentException("Unable to access URL: "+keystoreUrl, e);
+ }
+ File tmp = Os.newTempFile("brooklyn-keystore", "ks");
+ tmp.deleteOnExit();
+ FileUtil.copyTo(keystoreStream, tmp);
+ Streams.closeQuietly(keystoreStream);
+ return tmp.getAbsolutePath();
+ }
+ }
+
+ private String newTimestampedDirName(String prefix, int randomSuffixLength) {
+ return prefix + "-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "-" + Identifiers.makeRandomId(randomSuffixLength);
+ }
+
+ private String checkFileExists(String path, String name) {
+ if(!new File(path).exists()){
+ throw new IllegalArgumentException("Could not find "+name+": "+path);
+ }
+ return path;
+ }
+
+ /**
+ * Asks the app server to stop and waits for it to finish up.
+ */
+ public synchronized void stop() throws Exception {
+ if (server==null) return;
+ String root = getRootUrl();
+ if (shutdownHook != null) Threads.removeShutdownHook(shutdownHook);
+ if (log.isDebugEnabled())
+ log.debug("Stopping Brooklyn web console at "+root+ " (" + war + (wars != null ? " and " + wars.values() : "") + ")");
+
+ server.stop();
+ try {
+ server.join();
+ } catch (Exception e) {
+ /* NPE may be thrown e.g. if threadpool not started */
+ }
+ server = null;
+ LocalhostMachineProvisioningLocation.releasePort(getAddress(), actualPort);
+ actualPort = -1;
+ if (log.isDebugEnabled())
+ log.debug("Stopped Brooklyn web console at "+root);
+ }
+
+ /** serve given WAR at the given pathSpec; if not yet started, it is simply remembered until start;
+ * if server already running, the context for this WAR is started.
+ * @return the context created and added as a handler
+ * (and possibly already started if server is started,
+ * so be careful with any changes you make to it!) */
+ public WebAppContext deploy(final String pathSpec, final String warUrl) {
+ String cleanPathSpec = pathSpec;
+ while (cleanPathSpec.startsWith("/"))
+ cleanPathSpec = cleanPathSpec.substring(1);
+ boolean isRoot = cleanPathSpec.isEmpty();
+
+ WebAppContext context = new WebAppContext();
+ context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
+ for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) {
+ context.setAttribute(attributeEntry.getKey(), attributeEntry.getValue());
+ }
+
+ try {
+ File tmpWarFile = Os.writeToTempFile(new CustomResourceLocator(managementContext.getConfig(), ResourceUtils.create(this)).getResourceFromUrl(warUrl),
+ isRoot ? "ROOT" : ("embedded-" + cleanPathSpec), ".war");
+ context.setWar(tmpWarFile.getAbsolutePath());
+ } catch (Exception e) {
+ log.warn("Failed to deploy webapp "+pathSpec+" from "+warUrl
+ + (ignoreWebappDeploymentFailures ? "; launching run without WAR" : " (rethrowing)")
+ + ": "+Exceptions.collapseText(e));
+ if (!ignoreWebappDeploymentFailures) {
+ throw new IllegalStateException("Failed to deploy webapp "+pathSpec+" from "+warUrl+": "+Exceptions.collapseText(e), e);
+ }
+ log.debug("Detail on failure to deploy webapp: "+e, e);
+ context.setWar("/dev/null");
+ }
+
+ context.setContextPath("/" + cleanPathSpec);
+ context.setParentLoaderPriority(true);
+
+ deploy(context);
+ return context;
+ }
+
+ private Thread shutdownHook = null;
+
+ protected synchronized void addShutdownHook() {
+ if (shutdownHook!=null) return;
+ // some webapps can generate a lot of output if we don't shut down the browser first
+ shutdownHook = Threads.addShutdownHook(new Runnable() {
+ @Override
+ public void run() {
+ log.debug("BrooklynWebServer detected shutdown: stopping web-console");
+ try {
+ stop();
+ } catch (Exception e) {
+ log.error("Failure shutting down web-console: "+e, e);
+ }
+ }
+ });
+ }
+
+ public void deploy(WebAppContext context) {
+ try {
+ handlers.updateHandler(context);
+ } catch (Exception e) {
+ Throwables.propagate(e);
+ }
+ }
+
+ public Server getServer() {
+ return server;
+ }
+
+ public WebAppContext getRootContext() {
+ return rootContext;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
new file mode 100644
index 0000000..72732cd
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.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.launcher.camp;
+
+import io.brooklyn.camp.CampServer;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import brooklyn.entity.basic.BrooklynShutdownHooks;
+import org.apache.brooklyn.launcher.BrooklynLauncher;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.LocalManagementContext;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract;
+
+import com.google.common.annotations.Beta;
+
+/** variant of super who also starts a CampServer for convenience */
+@Beta
+public class BrooklynCampPlatformLauncher extends BrooklynCampPlatformLauncherAbstract {
+
+ protected BrooklynLauncher brooklynLauncher;
+ protected CampServer campServer;
+
+ @Override
+ public BrooklynCampPlatformLauncher launch() {
+ assert platform == null;
+
+ mgmt = newManagementContext();
+
+ // We created the management context, so we are responsible for terminating it
+ BrooklynShutdownHooks.invokeTerminateOnShutdown(mgmt);
+
+ brooklynLauncher = BrooklynLauncher.newInstance().managementContext(mgmt).start();
+ platform = new BrooklynCampPlatform(
+ PlatformRootSummary.builder().name("Brooklyn CAMP Platform").build(),
+ mgmt).setConfigKeyAtManagmentContext();
+
+ campServer = new CampServer(getCampPlatform(), "").start();
+
+ return this;
+ }
+
+ protected ManagementContext newManagementContext() {
+ return new LocalManagementContext();
+ }
+
+ public static void main(String[] args) {
+ new BrooklynCampPlatformLauncher().launch();
+ }
+
+ public void stopServers() throws Exception {
+ brooklynLauncher.getServerDetails().getWebServer().stop();
+ campServer.stop();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
new file mode 100644
index 0000000..200490a
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
@@ -0,0 +1,35 @@
+/*
+ * 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.camp;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract;
+import org.apache.brooklyn.camp.brooklyn.YamlLauncherAbstract;
+
+import com.google.common.annotations.Beta;
+
+/** convenience for launching YAML files directly */
+@Beta
+public class SimpleYamlLauncher extends YamlLauncherAbstract {
+
+ @Override
+ protected BrooklynCampPlatformLauncherAbstract newPlatformLauncher() {
+ return new BrooklynCampPlatformLauncher();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
new file mode 100644
index 0000000..13d2a3b
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
@@ -0,0 +1,93 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.os.Os;
+
+@Deprecated /** @deprecated since 0.7.0; see BrooklynVersion;
+* and anyway this was not really used, and if it were, it would be needed in core; autodetection is pretty good */
+public class BrooklynDevelopmentModes {
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynDevelopmentModes.class);
+
+ public static final ConfigKey<BrooklynDevelopmentMode> BROOKLYN_DEV_MODE = new BasicConfigKey<BrooklynDevelopmentMode>(
+ BrooklynDevelopmentMode.class, "brooklyn.developmentMode", "whether to run in development mode " +
+ "(default is to autodetect based on classpath)", BrooklynDevelopmentMode.AUTO);
+
+ private static AtomicBoolean loggedMode = new AtomicBoolean(false);
+
+ public static enum BrooklynDevelopmentMode {
+ TRUE(true), FALSE(false), AUTO(null);
+
+ private final Boolean enabled;
+
+ BrooklynDevelopmentMode(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ boolean enabled = computeEnabled();
+ if (!loggedMode.getAndSet(true)) {
+ // log on first invocation
+ String reason = (this.enabled==null ? "autodetected" : "forced");
+ if (enabled) {
+ log.info("Brooklyn running in development mode ("+reason+")");
+ } else {
+ log.debug("Brooklyn not running in development mode ("+reason+")");
+ }
+ }
+ return enabled;
+ }
+
+ protected boolean computeEnabled() {
+ if (enabled!=null) return enabled;
+ return getAutodectectedDevelopmentMode();
+ }
+ }
+
+ private static Boolean developmentMode = null;
+
+ public static boolean getAutodectectedDevelopmentMode() {
+ if (developmentMode!=null) return developmentMode;
+ developmentMode = computeAutodectectedDevelopmentMode();
+ return developmentMode;
+ }
+
+ private static final String segment = "/core/target/classes";
+
+ private static boolean computeAutodectectedDevelopmentMode() {
+ String cp = System.getProperty("java.class.path");
+ String platformSegment = Os.nativePath(segment);
+ if (cp==null) return false;
+ if (cp.endsWith(platformSegment) || cp.contains(platformSegment+File.pathSeparator)) {
+ log.debug("Brooklyn developmentMode autodetected (based on presence of '"+segment+"' in classpath)");
+ return true;
+ }
+ return false;
+ }
+
+}