/* * 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.IOException; import java.net.InetAddress; import java.nio.file.Files; 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.api.entity.Application; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.location.PortRange; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager; import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord; import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecordPersister; import org.apache.brooklyn.api.mgmt.rebind.PersistenceExceptionHandler; import org.apache.brooklyn.api.mgmt.rebind.RebindManager; import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData; import org.apache.brooklyn.camp.CampPlatform; import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.catalog.internal.CatalogInitialization; import org.apache.brooklyn.core.config.ConfigPredicates; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.StartableApplication; import org.apache.brooklyn.core.entity.factory.ApplicationBuilder; import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.location.PortRanges; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; import org.apache.brooklyn.core.mgmt.ha.HighAvailabilityManagerImpl; import org.apache.brooklyn.core.mgmt.ha.ManagementPlaneSyncRecordPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.internal.BrooklynShutdownHooks; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils; import org.apache.brooklyn.core.mgmt.persist.PersistMode; import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore; import org.apache.brooklyn.core.mgmt.rebind.PersistenceExceptionHandlerImpl; import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl; import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.entity.brooklynnode.BrooklynNode; import org.apache.brooklyn.entity.brooklynnode.LocalBrooklynNode; import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.launcher.config.StopWhichAppsOnShutdown; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation.LocalhostMachine; import org.apache.brooklyn.rest.BrooklynWebConfig; import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter; import org.apache.brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider; import org.apache.brooklyn.rest.util.ShutdownHandler; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException; import org.apache.brooklyn.util.exceptions.FatalRuntimeException; import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.io.FileUtil; import org.apache.brooklyn.util.net.Networking; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; 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; /** * 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; @SuppressWarnings("unused") 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) * * @deprecated since 0.9.0; instead use {@link #application(String)} for YAML apps, or {@link #application(EntitySpec)}. * Note that apps are now auto-managed on construction through EntitySpec/YAML. */ @Deprecated 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 destinationLocationSpec 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) { File globalProperties = new File(Os.tidyPath(globalBrooklynPropertiesFile)); if (globalProperties.exists()) { globalProperties = resolveSymbolicLink(globalProperties); checkFileReadable(globalProperties); // brooklyn.properties stores passwords (web-console and cloud credentials), // so ensure it has sensible permissions checkFilePermissionsX00(globalProperties); LOG.debug("Using global properties file " + globalProperties); } else { LOG.debug("Global properties file " + globalProperties + " does not exist, will ignore"); } builder.globalPropertiesFile(globalProperties.getAbsolutePath()); } else { LOG.debug("Global properties file disabled"); builder.globalPropertiesFile(null); } if (localBrooklynPropertiesFile != null) { File localProperties = new File(Os.tidyPath(localBrooklynPropertiesFile)); localProperties = resolveSymbolicLink(localProperties); checkFileReadable(localProperties); checkFilePermissionsX00(localProperties); builder.localPropertiesFile(localProperties.getAbsolutePath()); } 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); } } /** * @return The canonical path of the argument. */ private File resolveSymbolicLink(File f) { File f2 = f; try { f2 = f.getCanonicalFile(); if (Files.isSymbolicLink(f.toPath())) { LOG.debug("Resolved symbolic link: {} -> {}", f, f2); } } catch (IOException e) { LOG.warn("Could not determine canonical name of file "+f+"; returning original file", e); } return f2; } private void checkFileReadable(File f) { if (!f.exists()) { throw new FatalRuntimeException("File " + f + " does not exist"); } if (!f.isFile()) { throw new FatalRuntimeException(f + " is not a file"); } if (!f.canRead()) { throw new FatalRuntimeException(f + " is not readable"); } } private void checkFilePermissionsX00(File f) { 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 " + f + "; 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)) { LOG.info("No security provider options specified. Define a security provider or users to prevent a random password being created and logged."); 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_INSTANCE, new BrooklynUserWithRandomPasswordSecurityProvider(managementContext)); } 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 = EntityManagementUtils.createUnstarted(managementContext, 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 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); } } } }