| /* |
| * 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.Closeable; |
| import java.net.InetAddress; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.TimeoutException; |
| |
| 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.PortRange; |
| import org.apache.brooklyn.api.mgmt.ManagementContext; |
| import org.apache.brooklyn.core.config.ConfigPredicates; |
| import org.apache.brooklyn.core.config.Sanitizer; |
| 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.internal.BrooklynShutdownHooks; |
| import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; |
| import org.apache.brooklyn.core.mgmt.persist.PersistMode; |
| 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.common.BasicLauncher; |
| import org.apache.brooklyn.launcher.common.BrooklynPropertiesFactoryHelper; |
| import org.apache.brooklyn.launcher.config.StopWhichAppsOnShutdown; |
| import org.apache.brooklyn.rest.BrooklynWebConfig; |
| import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider; |
| import org.apache.brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider; |
| import org.apache.brooklyn.core.mgmt.ShutdownHandler; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.exceptions.FatalRuntimeException; |
| import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; |
| 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.time.Duration; |
| import org.apache.brooklyn.util.time.Time; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Stopwatch; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableList; |
| 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 extends BasicLauncher<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 boolean startWebApps = true; |
| private PortRange port = null; |
| private Boolean useHttps = null; |
| private InetAddress bindAddress = null; |
| private InetAddress publicAddress = null; |
| private List<WebAppContextProvider> webApps = new LinkedList<>(); |
| private Map<String, ?> webconsoleFlags = Maps.newLinkedHashMap(); |
| private Boolean skipSecurityFilter = null; |
| |
| private boolean ignoreWebErrors = false; |
| |
| private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED; |
| private ShutdownHandler shutdownHandler; |
| |
| private Function<ManagementContext,Void> customizeManagement = null; |
| |
| private volatile BrooklynWebServer webServer; |
| |
| private String globalBrooklynPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties"); |
| private String localBrooklynPropertiesFile; |
| private Supplier<Map<?, ?>> brooklynPropertiesSupplier; |
| |
| public BrooklynServerDetails getServerDetails() { |
| if (!isStarted()) throw new IllegalStateException("Cannot retrieve server details until started"); |
| return new BrooklynServerDetails(webServer, getManagementContext()); |
| } |
| |
| /** |
| * 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.add(new WebAppContextProvider(contextPath, warUrl)); |
| return this; |
| } |
| |
| /** |
| * @see #webapp(String, String) |
| */ |
| public BrooklynLauncher webapp(WebAppContextProvider contextProvider) { |
| webApps.add(contextProvider); |
| return this; |
| } |
| |
| public BrooklynLauncher ignoreWebErrors(boolean ignoreWebErrors) { |
| this.ignoreWebErrors = ignoreWebErrors; |
| return this; |
| } |
| |
| public BrooklynLauncher stopWhichAppsOnShutdown(StopWhichAppsOnShutdown stopWhich) { |
| this.stopWhichAppsOnShutdown = stopWhich; |
| return this; |
| } |
| |
| public BrooklynLauncher customizeManagement(Function<ManagementContext,Void> customizeManagement) { |
| this.customizeManagement = customizeManagement; |
| 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| @Override |
| protected void initManagementContext() { |
| initBrooklynPropertiesBuilder(); |
| |
| super.initManagementContext(); |
| |
| if (customizeManagement!=null) { |
| customizeManagement.apply(getManagementContext()); |
| } |
| } |
| |
| protected void initBrooklynPropertiesBuilder() { |
| if (getBrooklynPropertiesBuilder() == null) { |
| setBrooklynPropertiesBuilder( |
| new BrooklynPropertiesFactoryHelper( |
| globalBrooklynPropertiesFile, |
| localBrooklynPropertiesFile, |
| getBrooklynProperties(), |
| getBrooklynPropertiesSupplier()) |
| .createPropertiesBuilder()); |
| } |
| } |
| |
| private Supplier<Map<?, ?>> getBrooklynPropertiesSupplier() { |
| return brooklynPropertiesSupplier; |
| } |
| |
| @Override |
| protected void startingUp() { |
| super.startingUp(); |
| |
| // 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); |
| } |
| } |
| } |
| |
| protected void startWebApps() { |
| ManagementContext managementContext = getManagementContext(); |
| BrooklynProperties brooklynProperties = (BrooklynProperties) managementContext.getConfig(); |
| |
| String securityProvider = managementContext.getConfig().getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME); |
| // The security provider will let anyone in, but still require a password to be entered. |
| // Skip password request dialog if we know the provider will let users through. |
| boolean anyoneSecurityProvider = AnyoneSecurityProvider.class.getName().equals(securityProvider); |
| boolean noSecurityOptions = BrooklynWebConfig.hasNoSecurityOptions(managementContext.getConfig()); |
| boolean skipSecurity = Boolean.TRUE.equals(skipSecurityFilter) || anyoneSecurityProvider || noSecurityOptions; |
| |
| // No security options in properties and no command line options overriding. |
| if (Boolean.TRUE.equals(skipSecurityFilter)) { |
| if (bindAddress == null) { |
| LOG.info("Starting Brooklyn web-console with security explicitly disabled, on loopback because no bind address specified"); |
| bindAddress = Networking.LOOPBACK; |
| } else { |
| LOG.info("Starting Brooklyn web-console with security explicitly disabled, on bind address {}", bindAddress.getHostAddress()); |
| } |
| |
| } else if (anyoneSecurityProvider) { |
| String bindAddressMsg = (bindAddress == null ? "<any>" : bindAddress.getHostAddress()); |
| LOG.info("Starting Brooklyn web-console with AnyoneSecurityProvider (no authentication), on bind address {}", bindAddressMsg); |
| |
| } else if (noSecurityOptions) { |
| String bindAddressMsg = (bindAddress == null ? "<any>" : bindAddress.getHostAddress()); |
| LOG.info("Starting Brooklyn web-console with no security options (defaulting to no authentication), on bind address {}", bindAddressMsg); |
| |
| } else { |
| String bindAddressMsg = (bindAddress == null ? "<any>" : bindAddress.getHostAddress()); |
| Map<?,?> securityProps = brooklynProperties.submap(ConfigPredicates.nameStartsWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys(); |
| LOG.debug("Starting Brooklyn (bind address {}), using security properties: {}", bindAddressMsg, Sanitizer.sanitize(securityProps)); |
| } |
| |
| 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); |
| webServer.skipSecurity(skipSecurity); |
| for (WebAppContextProvider webapp : webApps) { |
| webServer.addWar(webapp); |
| } |
| 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); |
| } |
| } |
| |
| @Override |
| protected void startBrooklynNode() { |
| if (webServer == null || !startWebApps) { |
| LOG.info("Skipping BrooklynNode entity creation, BrooklynWebServer not running"); |
| return; |
| } |
| super.startBrooklynNode(); |
| } |
| |
| protected EntitySpec<LocalBrooklynNode> customizeBrooklynNodeSpec(EntitySpec<LocalBrooklynNode> brooklynNodeSpec) { |
| return brooklynNodeSpec |
| .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.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE.equals(skipSecurityFilter)); |
| } |
| |
| protected void startApps() { |
| if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL) || |
| (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL_IF_NOT_PERSISTED && getPersistMode()==PersistMode.DISABLED)) { |
| BrooklynShutdownHooks.invokeStopAppsOnShutdown(getManagementContext()); |
| } |
| |
| for (Application app : getApplications()) { |
| if (app instanceof Startable) { |
| |
| if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE) || |
| (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED && getPersistMode()==PersistMode.DISABLED)) { |
| BrooklynShutdownHooks.invokeStopOnShutdown(app); |
| } |
| } |
| } |
| super.startApps(); |
| } |
| |
| |
| /** |
| * 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 (!isStarted()) return; // no-op |
| |
| if (webServer != null) { |
| try { |
| webServer.stop(); |
| } catch (Exception e) { |
| LOG.warn("Error stopping web-server; continuing with termination", e); |
| } |
| } |
| |
| ManagementContext managementContext = getManagementContext(); |
| |
| // 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 (getPersistMode() != PersistMode.DISABLED) { |
| try { |
| Stopwatch stopwatch = Stopwatch.createStarted(); |
| if (managementContext.getHighAvailabilityManager() != null && 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 : getLocations()) { |
| if (loc instanceof Closeable) { |
| Streams.closeQuietly((Closeable)loc); |
| } |
| } |
| } |
| |
| public BrooklynLauncher globalBrooklynPropertiesFile(String file) { |
| globalBrooklynPropertiesFile = file; |
| return this; |
| } |
| |
| public BrooklynLauncher localBrooklynPropertiesFile(String file) { |
| localBrooklynPropertiesFile = file; |
| return this; |
| } |
| |
| /** |
| * Poperties returned by the supplier have less precedence (get overwritten) by global & local properties |
| */ |
| public BrooklynLauncher brooklynPropertiesSupplier(Supplier<Map<?, ?>> brooklynPropertiesSupplier) { |
| this.brooklynPropertiesSupplier = brooklynPropertiesSupplier; |
| return this; |
| } |
| |
| |
| } |