/*
* 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.core.server;
import java.io.File;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.StringConfigMap;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.text.TemplateProcessor;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.Identifiers;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Time;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
public class BrooklynServerPaths {
private static final Logger log = LoggerFactory.getLogger(BrooklynServerPaths.class);
/** Computes the base dir where brooklyn should read and write configuration.
* Defaults to <code>~/.brooklyn/</code>.
* <p>
* Also see other variants of this method if a {@link ManagementContext} is not yet available. */
public static String getMgmtBaseDir(ManagementContext mgmt) {
return getMgmtBaseDir(mgmt.getConfig());
}
/** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
@SuppressWarnings("deprecation")
public static String getMgmtBaseDir(StringConfigMap brooklynProperties) {
String base = (String) brooklynProperties.getConfigRaw(BrooklynServerConfig.MGMT_BASE_DIR, true).orNull();
if (base==null) {
base = brooklynProperties.getConfig(BrooklynServerConfig.BROOKLYN_DATA_DIR);
if (base!=null)
log.warn("Using deprecated "+BrooklynServerConfig.BROOKLYN_DATA_DIR.getName()+": use "+BrooklynServerConfig.MGMT_BASE_DIR.getName()+" instead; value: "+base);
}
if (base==null) base = brooklynProperties.getConfig(BrooklynServerConfig.MGMT_BASE_DIR);
return Os.tidyPath(base)+File.separator;
}
/** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
@SuppressWarnings("deprecation")
public static String getMgmtBaseDir(Map<String,?> brooklynProperties) {
String base = (String) brooklynProperties.get(BrooklynServerConfig.MGMT_BASE_DIR.getName());
if (base==null) base = (String) brooklynProperties.get(BrooklynServerConfig.BROOKLYN_DATA_DIR.getName());
if (base==null) base = BrooklynServerConfig.MGMT_BASE_DIR.getDefaultValue();
return Os.tidyPath(base)+File.separator;
}
protected static String resolveAgainstBaseDir(StringConfigMap brooklynProperties, String path) {
if (!Os.isAbsolutish(path)) path = Os.mergePaths(getMgmtBaseDir(brooklynProperties), path);
return Os.tidyPath(path);
}
// ---------- persistence
public static final String DEFAULT_PERSISTENCE_CONTAINER_NAME = "brooklyn-persisted-state";
/** on file system, the 'data' subdir is used so that there is an obvious place to put backup dirs */
public static final String DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM = Os.mergePaths(DEFAULT_PERSISTENCE_CONTAINER_NAME, "data");
/** @see PersistencePathResolver */
public static PersistencePathResolver newMainPersistencePathResolver(StringConfigMap brooklynProperties) {
return new PersistencePathResolver(brooklynProperties);
}
/** @see PersistencePathResolver */
public static PersistencePathResolver newMainPersistencePathResolver(ManagementContext mgmt) {
return new PersistencePathResolver(mgmt.getConfig());
}
/** @see PersistenceBackupPathResolver */
public static PersistenceBackupPathResolver newBackupPersistencePathResolver(ManagementContext mgmt) {
return new PersistenceBackupPathResolver(mgmt.getConfig());
}
/**
* Utility for computing the path (dir or container name) to use for persistence.
* <p>
* Usage is to invoke {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)}
* then to set {@link #location(String)} and {@link #dir(String)} as needed, and then to {@link #resolve()}.
*/
public static class PersistencePathResolver {
protected final StringConfigMap brooklynProperties;
protected String locationSpec;
protected String dirOrContainer;
private PersistencePathResolver(StringConfigMap brooklynProperties) {
this.brooklynProperties = brooklynProperties;
}
/**
* Optional location spec. If supplied, the {@link #resolve()} will return a container name suitable for use
* with the store, based on the {@link #dir(String)};
* if not supplied, or blank, or localhost this will cause resolution to give a full file system path,
* if relative taken with respect to the {@link BrooklynServerPaths#getMgmtBaseDir(ManagementContext)}.
* Config is <b>not</b> looked up for resolving a location spec. */
public PersistencePathResolver location(@Nullable String locationSpec) {
this.locationSpec = locationSpec;
return this;
}
/**
* Optional directory (for localhost/filesystem) or container name.
* If null or not supplied, config <b>is</b> looked up (because a value is always needed),
* followed by defaults for {@link BrooklynServerPaths#DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM} and
* {@link BrooklynServerPaths#DEFAULT_PERSISTENCE_CONTAINER_NAME} are used. */
public PersistencePathResolver dir(@Nullable String dirOrContainer) {
this.dirOrContainer = dirOrContainer;
return this;
}
public String resolve() {
String path = dirOrContainer;
if (path==null) path = getDefaultPathFromConfig();
if (Strings.isBlank(locationSpec) || "localhost".equals(locationSpec)) {
// file system
if (Strings.isBlank(path)) path=getDefaultDirForAnyFilesystem();
return resolveAgainstBaseDir(brooklynProperties, path);
} else {
// obj store
if (path==null) path=getDefaultContainerForAnyNonFilesystem();
return path;
}
}
protected String getDefaultPathFromConfig() {
return brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_DIR);
}
protected String getDefaultDirForAnyFilesystem() {
return DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM;
}
protected String getDefaultContainerForAnyNonFilesystem() {
return DEFAULT_PERSISTENCE_CONTAINER_NAME;
}
}
/**
* Similar to {@link PersistencePathResolver}, but designed for use for backup subpaths.
* If the container is not explicitly specified, "backups" is appended to the defaults from {@link PersistencePathResolver}.
* <p>
* It also includes conveniences for resolving further subpaths, cf {@link PersistenceBackupPathResolver#resolveWithSubpathFor(ManagementContextInternal, String)}.
*/
public static class PersistenceBackupPathResolver extends PersistencePathResolver {
private String nonBackupLocationSpec;
private PersistenceBackupPathResolver(StringConfigMap brooklynProperties) {
super(brooklynProperties);
}
public PersistenceBackupPathResolver nonBackupLocation(@Nullable String locationSpec) {
this.nonBackupLocationSpec = locationSpec;
return this;
}
@Override
public PersistenceBackupPathResolver dir(String dirOrContainer) {
super.dir(dirOrContainer);
return this;
}
@Override
public PersistenceBackupPathResolver location(String backupLocationSpec) {
super.location(backupLocationSpec);
return this;
}
protected boolean isBackupSameLocation() {
return Objects.equal(locationSpec, nonBackupLocationSpec);
}
/** Appends a sub-path to the path returned by {@link #resolve()} */
public String resolveWithSubpath(String subpath) {
return Urls.mergePaths(super.resolve(), subpath);
}
/** Appends a standard format subpath sub-path to the path returned by {@link #resolve()}.
* <p>
* For example, this might write to:
* <code>~/.brooklyn/brooklyn-persisted-state/backups/2014-11-13-1201-n0deId-promotion-sA1t */
public String resolveWithSubpathFor(ManagementContext managementContext, String label) {
return resolveWithSubpath(Time.makeDateSimpleStampString()+"-"+managementContext.getManagementNodeId()+"-"+label+"-"+Identifiers.makeRandomId(4));
}
@Override
protected String getDefaultPathFromConfig() {
Maybe<Object> result = brooklynProperties.getConfigRaw(BrooklynServerConfig.PERSISTENCE_BACKUPS_DIR, true);
if (result.isPresent()) return Strings.toString(result.get());
if (isBackupSameLocation()) {
return backupContainerFor(brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_DIR));
}
return null;
}
protected String backupContainerFor(String nonBackupContainer) {
if (nonBackupContainer==null) return null;
return Urls.mergePaths(nonBackupContainer, "backups");
}
@Override
protected String getDefaultDirForAnyFilesystem() {
return backupContainerFor(DEFAULT_PERSISTENCE_CONTAINER_NAME);
}
@Override
protected String getDefaultContainerForAnyNonFilesystem() {
return backupContainerFor(super.getDefaultContainerForAnyNonFilesystem());
}
}
// ------ web
public static File getBrooklynWebTmpDir(ManagementContext mgmt) {
String brooklynMgmtBaseDir = getMgmtBaseDir(mgmt);
File webappTempDir = new File(Os.mergePaths(brooklynMgmtBaseDir, "planes", mgmt.getManagementPlaneId(), mgmt.getManagementNodeId(), "jetty"));
try {
FileUtils.forceMkdir(webappTempDir);
Os.deleteOnExitRecursivelyAndEmptyParentsUpTo(webappTempDir, new File(brooklynMgmtBaseDir));
return webappTempDir;
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
IllegalStateException e2 = new IllegalStateException("Cannot create working directory "+webappTempDir+" for embedded jetty server: "+e, e);
log.warn(e2.getMessage()+" (rethrowing)");
throw e2;
}
}
public static File getOsgiCacheDir(ManagementContext mgmt) {
StringConfigMap brooklynProperties = mgmt.getConfig();
String cacheDir = brooklynProperties.getConfig(BrooklynServerConfig.OSGI_CACHE_DIR);
// note dir should be different for each instance if starting multiple instances
// hence default including management node ID
cacheDir = TemplateProcessor.processTemplateContents(cacheDir, (ManagementContextInternal)mgmt,
MutableMap.of(BrooklynServerConfig.MGMT_BASE_DIR.getName(), getMgmtBaseDir(mgmt),
BrooklynServerConfig.MANAGEMENT_NODE_ID_PROPERTY, mgmt.getManagementNodeId(),
Os.TmpDirFinder.BROOKLYN_OS_TMPDIR_PROPERTY, Os.tmp()));
cacheDir = resolveAgainstBaseDir(mgmt.getConfig(), cacheDir);
return new File(cacheDir);
}
public static boolean isOsgiCacheForCleaning(ManagementContext mgmt, File cacheDir) {
StringConfigMap brooklynProperties = mgmt.getConfig();
Boolean clean = brooklynProperties.getConfig(BrooklynServerConfig.OSGI_CACHE_CLEAN);
if (clean==null) {
// as per javadoc on key, clean defaults to iff it is a node-id-specific directory
clean = cacheDir.getName().contains(mgmt.getManagementNodeId());
}
return clean;
}
public static File getOsgiCacheDirCleanedIfNeeded(ManagementContext mgmt) {
File cacheDirF = getOsgiCacheDir(mgmt);
boolean clean = isOsgiCacheForCleaning(mgmt, cacheDirF);
log.debug("OSGi cache dir computed as "+cacheDirF.getName()+" ("+
(cacheDirF.exists() ? "already exists" : "does not exist")+", "+
(clean ? "cleaning now (and on exit)" : "cleaning not requested"));
if (clean) {
Os.deleteRecursively(cacheDirF);
Os.deleteOnExitRecursively(cacheDirF);
}
return cacheDirF;
}
}