blob: f5e57b6e25109aefac910d34ab3af15e4c34fbee [file] [log] [blame]
/*
* 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;
import java.util.Map;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
import org.apache.brooklyn.util.core.task.DeferredSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
/**
* For enabling/disabling experimental features.
* Feature enablement can be set in a number of ways, with the following precedence (most important first):
* <ol>
* <li>Explicit call to {@link #setEnablement(String, boolean)} (or to {@link #enable(String)} or {@link #disable(String)})).
* <li>Java system properties
* <li>Brooklyn properties (passed using {@link #init(BrooklynProperties)})
* <li>Defaults set explicitly by calling {@link #setDefault(String, boolean)}
* <li>Hard-coded defaults
* </ol>
* <p>
* For example, start Brooklyn with {@code -Dbrooklyn.executionManager.renameThreads=true}
*/
@Beta
public class BrooklynFeatureEnablement {
private static final Logger LOG = LoggerFactory.getLogger(BrooklynFeatureEnablement.class);
public static final String FEATURE_PROPERTY_PREFIX = "brooklyn.experimental.feature";
public static final String FEATURE_POLICY_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".policyPersistence";
public static final String FEATURE_ENRICHER_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".enricherPersistence";
public static final String FEATURE_FEED_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".feedPersistence";
/**
* When persisting an entity that changes, whether to persist its adjuncts and locations
* (i.e. its enrichers, policies, feeds and locations).
*
* This was previously the default behaviour, which meant that (legacy) java-based Brooklyn objects
* (e.g. entities, locations, policies, enrichers or feeds) could get away with bad practices and
* still be persisted. For example, they could change 'state' without telling the listener, and hope
* that the entity they were attached to would soon persist (thus piggy-backing off it).
*/
public static final String FEATURE_REFERENCED_OBJECTS_REPERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".referencedObjectsRepersistence";
/** whether feeds are automatically registered when set on entities, so that they are persisted */
public static final String FEATURE_FEED_REGISTRATION_PROPERTY = FEATURE_PROPERTY_PREFIX+".feedRegistration";
public static final String FEATURE_CORS_CXF_PROPERTY = FEATURE_PROPERTY_PREFIX + ".corsCxfFeature";
public static final String FEATURE_BUNDLE_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".bundlePersistence";
public static final String FEATURE_CATALOG_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".catalogPersistence";
/** whether the default standby mode is {@link HighAvailabilityMode#HOT_STANDBY} or falling back to the traditional
* {@link HighAvailabilityMode#STANDBY} */
public static final String FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY = FEATURE_PROPERTY_PREFIX+".defaultStandbyIsHot";
/** whether $brooklyn:entitySpec blocks persist a {@link DeferredSupplier} containing YAML or the resolved {@link EntitySpec};
* the former allows upgrades and deferred resolution of other flags, whereas the latter is the traditional
* behaviour (prior to 2017-11) which resolves the {@link EntitySpec} earlier and persists that */
public static final String FEATURE_PERSIST_ENTITY_SPEC_AS_SUPPLIER = FEATURE_PROPERTY_PREFIX+".persist.entitySpecAsSupplier";
/**
* Renaming threads can really helps with debugging etc; however it's a massive performance hit (2x)
* <p>
* We get 55000 tasks per sec with this off, 28k/s with this on.
* <p>
* Defaults to false if system property is not set.
*/
public static final String FEATURE_RENAME_THREADS = "brooklyn.executionManager.renameThreads";
/**
* Add a jitter to the startup of tasks for testing concurrency code.
* Use {@code brooklyn.executionManager.jitterThreads.maxDelay} to tune the maximum time task
* startup gets delayed in milliseconds. The actual time will be a random value between [0, maxDelay).
* Default is 200 milliseconds.
*/
public static final String FEATURE_JITTER_THREADS = "brooklyn.executionManager.jitterThreads";
/**
* When rebinding to state created from very old versions, the catalogItemId properties will be missing which
* results in errors when OSGi bundles are used. When enabled the code tries to infer the catalogItemId from
* <ul>
* <li> parent entities
* <li> catalog items matching the type that needs to be deserialized
* <li> iterating through all catalog items and checking if they can provide the needed type
* </ul>
*/
public static final String FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND = "brooklyn.backwardCompatibility.feature.inferCatalogItemOnRebind";
/**
* When rebinding, an entity could reference a catalog item that no longer exists. This option
* will automatically update the catalog item reference to what is inferred as the most
* suitable catalog symbolicName:version.
*/
public static final String FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND = "brooklyn.quickfix.fixDanglingCatalogItemOnRebind";
/**
* When executing over ssh, whether to support the "async exec" approach, or only the classic approach.
*
* If this feature is disabled, then even if the {@link ShellTool#PROP_EXEC_ASYNC} is configured it
* will still use the classic ssh approach.
*/
public static final String FEATURE_SSH_ASYNC_EXEC = FEATURE_PROPERTY_PREFIX+".ssh.asyncExec";
public static final String FEATURE_VALIDATE_LOCATION_SSH_KEYS = "brooklyn.validate.locationSshKeys";
/**
* In previous versions, reparenting was not allowed. This feature restores that behaviour.
*/
public static final String FEATURE_DISALLOW_REPARENTING = "brooklyn.disallowReparenting";
/**
* Whether lookup of the location of the host is permitted, either 'true' or 'false'.
* Since 1.1 defaults to 'false' normally to prevent odd outbound traffic, but 'true' in development environments for tests to run.
*/
public static final String FEATURE_HOST_GEO_LOOKUP = "brooklyn.hostGeoLookup";
/**
* Values explicitly set by Java calls.
*/
private static final Map<String, Boolean> FEATURE_ENABLEMENTS = Maps.newLinkedHashMap();
/**
* Values set from brooklyn.properties
*/
private static final Map<String, Boolean> FEATURE_ENABLEMENTS_PROPERTIES = Maps.newLinkedHashMap();
/**
* Defaults (e.g. set by the static block's call to {@link #setDefaults()}, or by downstream projects
* calling {@link #setDefault(String, boolean)}).
*/
private static final Map<String, Boolean> FEATURE_ENABLEMENT_DEFAULTS = Maps.newLinkedHashMap();
private static final Object MUTEX = new Object();
static void setDefaults() {
// Idea is here one can put experimental features that are *enabled* by default, but
// that can be turned off via system properties, or vice versa.
// Typically this is useful where a feature is deemed risky!
setDefault(FEATURE_POLICY_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_ENRICHER_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_FEED_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_FEED_REGISTRATION_PROPERTY, false);
setDefault(FEATURE_BUNDLE_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_CATALOG_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_REFERENCED_OBJECTS_REPERSISTENCE_PROPERTY, false);
setDefault(FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY, true);
setDefault(FEATURE_PERSIST_ENTITY_SPEC_AS_SUPPLIER, true);
setDefault(FEATURE_RENAME_THREADS, false);
setDefault(FEATURE_JITTER_THREADS, false);
setDefault(FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND, false);
setDefault(FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, false);
setDefault(FEATURE_SSH_ASYNC_EXEC, false);
setDefault(FEATURE_VALIDATE_LOCATION_SSH_KEYS, true);
setDefault(FEATURE_HOST_GEO_LOOKUP, BrooklynVersion.isDevelopmentEnvironment());
}
static {
setDefaults();
}
public static boolean isEnabled(String property) {
synchronized (MUTEX) {
if (FEATURE_ENABLEMENTS.containsKey(property)) {
return FEATURE_ENABLEMENTS.get(property);
} else if (System.getProperty(property) != null) {
String rawVal = System.getProperty(property);
return Boolean.parseBoolean(rawVal);
} else if (FEATURE_ENABLEMENTS_PROPERTIES.containsKey(property)) {
return FEATURE_ENABLEMENTS_PROPERTIES.get(property);
} else if (FEATURE_ENABLEMENT_DEFAULTS.containsKey(property)) {
return FEATURE_ENABLEMENT_DEFAULTS.get(property);
} else {
return false;
}
}
}
/**
* Initialises the feature-enablement from brooklyn properties. For each
* property, prefer a system-property if present; otherwise use the value
* from brooklyn properties.
*/
public static void init(BrooklynProperties props) {
boolean found = false;
synchronized (MUTEX) {
for (Map.Entry<String, Object> entry : props.asMapWithStringKeys().entrySet()) {
String property = entry.getKey();
if (property.startsWith(FEATURE_PROPERTY_PREFIX)) {
found = true;
Boolean oldVal = isEnabled(property);
boolean val = Boolean.parseBoolean(""+entry.getValue());
FEATURE_ENABLEMENTS_PROPERTIES.put(property, val);
Boolean newVal = isEnabled(property);
if (Objects.equal(oldVal, newVal)) {
LOG.debug("Enablement of "+property+" set to "+val+" from brooklyn properties (no-op as continues to resolve to "+oldVal+")");
} else {
LOG.debug("Enablement of "+property+" set to "+val+" from brooklyn properties (resolved value previously "+oldVal+")");
}
}
}
if (!found) {
LOG.debug("Init feature enablement did nothing, as no settings in brooklyn properties");
}
}
}
public static boolean enable(String property) {
return setEnablement(property, true);
}
public static boolean disable(String property) {
return setEnablement(property, false);
}
public static boolean setEnablement(String property, boolean val) {
synchronized (MUTEX) {
boolean oldVal = isEnabled(property);
FEATURE_ENABLEMENTS.put(property, val);
if (val == oldVal) {
LOG.debug("Enablement of "+property+" set to explicit "+val+" (no-op as resolved to same value previously)");
} else {
LOG.debug("Enablement of "+property+" set to explicit "+val+" (previously resolved to "+oldVal+")");
}
return oldVal;
}
}
public static void setDefault(String property, boolean val) {
synchronized (MUTEX) {
Boolean oldDefaultVal = FEATURE_ENABLEMENT_DEFAULTS.get(property);
FEATURE_ENABLEMENT_DEFAULTS.put(property, val);
if (oldDefaultVal != null) {
if (oldDefaultVal.equals(val)) {
LOG.debug("Default enablement of "+property+" set to "+val+" (no-op as same as previous default value)");
} else {
LOG.debug("Default enablement of "+property+" set to "+val+" (overwriting previous default of "+oldDefaultVal+")");
}
} else {
LOG.debug("Default enablement of "+property+" set to "+val);
}
}
}
@VisibleForTesting
public static void clearCache() {
synchronized (MUTEX) {
FEATURE_ENABLEMENTS.clear();
FEATURE_ENABLEMENTS_PROPERTIES.clear();
FEATURE_ENABLEMENT_DEFAULTS.clear();
setDefaults();
}
}
}