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; + } + +}
