blob: a0a36ef8af38e8088a7fc067afc212fea5e02748 [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.entity;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.core.mgmt.BrooklynTags;
import org.apache.brooklyn.core.mgmt.internal.*;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import static org.apache.brooklyn.util.guava.Functionals.isSatisfied;
import java.io.Closeable;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.apache.brooklyn.api.effector.Effector;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.drivers.EntityDriver;
import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.EntityManager;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.LocationManager;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.TaskAdaptable;
import org.apache.brooklyn.api.mgmt.TaskFactory;
import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Enricher;
import org.apache.brooklyn.api.sensor.Feed;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.core.config.Sanitizer;
import org.apache.brooklyn.core.effector.Effectors;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.core.entity.trait.StartableMethods;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.core.objs.proxy.EntityProxyImpl;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.core.task.ParallelTask;
import org.apache.brooklyn.util.core.task.TaskTags;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
import org.apache.brooklyn.util.core.task.system.SystemTasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.time.Duration;
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.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.Atomics;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
/**
* Convenience methods for working with entities.
* <p>
* Also see the various {@code *Methods} classes for traits,
* such as {@link StartableMethods} for {@link Startable} implementations.
*/
public class Entities {
private static final Logger log = LoggerFactory.getLogger(Entities.class);
// Don't use `new Object()` - deserialization creates a different object from the constant.
// Instead, use this enum.
private enum ValueMarkers {
UNCHANGED,
REMOVE;
}
/**
* Special object used by some setting methods to indicate that a value should be ignored.
* <p>
* See specific usages of this field to confirm where.
*/
public static final Object UNCHANGED = ValueMarkers.UNCHANGED;
/**
* Special object used by some setting methods to indicate that a value should be removed.
* <p>
* See specific usages of this field to confirm where.
*/
public static final Object REMOVE = ValueMarkers.REMOVE;
/**
* Invokes an {@link Effector} on multiple entities, with the named arguments from the parameters {@link Map}
* using the context of the provided {@link Entity}.
* <p>
* Intended for use only from the callingEntity.
* <p>
* Returns a {@link ParallelTask} containing the results from each tasks invocation. Calling
* {@link java.util.concurrent.Future#get() get()} on this will block until all tasks are complete,
* and will throw an exception if any task resulted in an error.
*
* @return {@link ParallelTask} containing results from each invocation
*/
public static <T> Task<List<T>> invokeEffectorList(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<T> effector, final Map<String,?> parameters) {
// formulation is complicated, but it is building up a list of tasks, without blocking on them initially,
// but ensuring that when the parallel task is gotten it does block on all of them
if (entitiesToCall == null){
entitiesToCall = ImmutableList.of();
}
List<TaskAdaptable<T>> tasks = Lists.newArrayList();
for (final Entity entity : entitiesToCall) {
tasks.add( Effectors.invocation(entity, effector, parameters) );
}
ParallelTask<T> invoke = new ParallelTask<T>(
MutableMap.of(
"displayName", effector.getName()+" (parallel)",
"description", "Invoking effector \""+effector.getName()+"\" on "+tasks.size()+(tasks.size() == 1 ? " entity" : " entities"),
"tag", BrooklynTaskTags.tagForCallerEntity(callingEntity)),
tasks);
TaskTags.markInessential(invoke);
return DynamicTasks.queueIfPossible(invoke).orSubmitAsync(callingEntity).asTask();
}
public static <T> Task<List<T>> invokeEffectorListWithMap(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<T> effector, final Map<String,?> parameters) {
return invokeEffectorList(callingEntity, entitiesToCall, effector, parameters);
}
@SuppressWarnings("unchecked")
public static <T> Task<List<T>> invokeEffectorListWithArgs(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<T> effector, Object ...args) {
return invokeEffectorListWithMap(callingEntity, entitiesToCall, effector,
// putting into a map, unnecessarily, as it ends up being the array again...
EffectorUtils.prepareArgsForEffectorAsMapFromArray(effector, args));
}
public static <T> Task<List<T>> invokeEffectorList(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<T> effector) {
return invokeEffectorList(callingEntity, entitiesToCall, effector, Collections.<String,Object>emptyMap());
}
public static <T> Task<T> invokeEffector(Entity callingEntity, Entity entityToCall,
final Effector<T> effector, final Map<String,?> parameters) {
Task<T> t = Effectors.invocation(entityToCall, effector, parameters).asTask();
TaskTags.markInessential(t);
// we pass to callingEntity for consistency above, but in exec-context it should be re-dispatched to targetEntity
// reassign t as the return value may be a wrapper, if it is switching execution contexts; see submitInternal's javadoc
t = ((EntityInternal)callingEntity).getExecutionContext().submit(
MutableMap.of("tag", BrooklynTaskTags.tagForCallerEntity(callingEntity)), t);
if (DynamicTasks.getTaskQueuingContext()!=null) {
// include it as a child (in the gui), marked inessential, because the caller is invoking programmatically
DynamicTasks.queue(t);
}
return t;
}
@SuppressWarnings("unchecked")
public static <T> Task<T> invokeEffectorWithArgs(Entity callingEntity, Entity entityToCall,
final Effector<T> effector, Object ...args) {
return invokeEffector(callingEntity, entityToCall, effector,
EffectorUtils.prepareArgsForEffectorAsMapFromArray(effector, args));
}
public static <T> Task<T> invokeEffector(Entity callingEntity, Entity entityToCall,
final Effector<T> effector) {
return invokeEffector(callingEntity, entityToCall, effector, Collections.<String,Object>emptyMap());
}
/** Invokes in parallel if multiple, but otherwise invokes the item directly. */
public static Task<?> invokeEffector(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<?> effector, final Map<String,?> parameters) {
if (Iterables.size(entitiesToCall)==1)
return invokeEffector(callingEntity, entitiesToCall.iterator().next(), effector, parameters);
else
return invokeEffectorList(callingEntity, entitiesToCall, effector, parameters);
}
/** Invokes in parallel if multiple, but otherwise invokes the item directly. */
public static Task<?> invokeEffector(Entity callingEntity, Iterable<? extends Entity> entitiesToCall,
final Effector<?> effector) {
return invokeEffector(callingEntity, entitiesToCall, effector, Collections.<String,Object>emptyMap());
}
/**
* @deprecated since 0.7; instead use {@link Sanitizer#IS_SECRET_PREDICATE}
*/
@Deprecated
public static boolean isSecret(String name) {
return Sanitizer.IS_SECRET_PREDICATE.apply(name);
}
public static boolean isTrivial(Object v) {
if (v instanceof Maybe) {
if (!((Maybe<?>)v).isPresent())
return true;
v = ((Maybe<?>) v).get();
}
return v==null || (v instanceof Map && ((Map<?,?>)v).isEmpty()) ||
(v instanceof Collection && ((Collection<?>)v).isEmpty()) ||
(v instanceof CharSequence&& ((CharSequence)v).length() == 0);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Iterable<? extends Entity> entities) {
Dumper.dumpInfo(entities);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Entity e) {
Dumper.dumpInfo(e);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Entity e, Writer out) throws IOException {
Dumper.dumpInfo(e, out);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Entity e, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(e, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Entity e, Writer out, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(e, out, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Location loc) {
Dumper.dumpInfo(loc);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Location loc, Writer out) throws IOException {
Dumper.dumpInfo(loc, out);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Location loc, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(loc, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Location loc, Writer out, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(loc, out, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Task<?> t) {
Dumper.dumpInfo(t);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Enricher enr) {
Dumper.dumpInfo(enr);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Enricher enr, Writer out) throws IOException {
Dumper.dumpInfo(enr, out);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Enricher enr, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(enr, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Enricher enr, Writer out, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(enr, out, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Feed feed, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(feed, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Feed feed, Writer out, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(feed, out, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Policy pol) {
Dumper.dumpInfo(pol);
}
/**
* @deprecated since 1.0.0; instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Policy pol, Writer out) throws IOException {
Dumper.dumpInfo(pol, out);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Policy pol, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(pol, currentIndentation, tab);
}
/**
* @deprecated since 1.0.0; such fine-grained customization will be removed from API, instead use {@link Dumper} methods
*/
@Deprecated
public static void dumpInfo(Policy pol, Writer out, String currentIndentation, String tab) throws IOException {
Dumper.dumpInfo(pol, out, currentIndentation, tab);
}
/**
* Sorts the sensors into alphabetical order by name.
* @deprecated since 1.0.0; viewed as unnecessary in the API, will be removed
*/
@Deprecated
public static List<Sensor<?>> sortSensors(Set<Sensor<?>> sensors) {
return Dumper.sortSensors(sensors);
}
/**
* Sorts the sensors into alphabetical order by name.
* @deprecated since 1.0.0; viewed as unnecessary in the API, will be removed
*/
@Deprecated
public static List<ConfigKey<?>> sortConfigKeys(Set<ConfigKey<?>> configs) {
return Dumper.sortConfigKeys(configs);
}
/**
* Sorts the sensors into alphabetical order by name.
* @deprecated since 1.0.0; viewed as unnecessary in the API, will be removed
*/
@Deprecated
public static <T> Map<String, T> sortMap(Map<String, T> map) {
return Dumper.sortMap(map);
}
/**
* Returns true if the given descendant includes the given ancestor in its chain.
* Does <i>NOT</i> count a node as its ancestor.
*/
public static boolean isAncestor(Entity descendant, Entity potentialAncestor) {
Entity ancestor = descendant.getParent();
while (ancestor != null) {
if (ancestor.equals(potentialAncestor)) return true;
ancestor = ancestor.getParent();
}
return false;
}
/**
* Checks whether the descendants of the given ancestor contains the given potentialDescendant.
* <p>
* In this test, unlike in {@link #descendants(Entity)}, an entity is not counted as a descendant.
* note, it is usually preferred to use isAncestor() and swap the order, it is a cheaper method.
*/
public static boolean isDescendant(Entity ancestor, Entity potentialDescendant) {
Set<Entity> inspected = Sets.newLinkedHashSet();
Stack<Entity> toinspect = new Stack<Entity>();
toinspect.add(ancestor);
while (!toinspect.isEmpty()) {
Entity e = toinspect.pop();
if (e.getChildren().contains(potentialDescendant)) {
return true;
}
inspected.add(e);
toinspect.addAll(e.getChildren());
toinspect.removeAll(inspected);
}
return false;
}
/**
* Return all descendants of given entity matching the given predicate and optionally the entity itself.
*
* @see EntityPredicates
*/
@SuppressWarnings("unused")
public static Iterable<Entity> descendants(Entity root, Predicate<? super Entity> matching, boolean includeSelf) {
if (false) {
// Keeping this unused code, so that anonymous inner class numbers don't change!
Iterable<Entity> descs = Iterables.concat(Iterables.transform(root.getChildren(), new Function<Entity,Iterable<Entity>>() {
@Override
public Iterable<Entity> apply(Entity input) {
return descendants(input);
}
}));
return Iterables.filter(Iterables.concat(descs, Collections.singleton(root)), matching);
}
if (includeSelf) {
return descendantsAndSelf(root, matching);
} else {
return Iterables.filter(descendantsWithoutSelf(root), matching);
}
}
/**
* Returns the entities matching the given predicate.
*
* @see #descendants(Entity, Predicate, boolean)
*/
public static Iterable<Entity> descendantsAndSelf(Entity root, Predicate<? super Entity> matching) {
return Iterables.filter(descendantsAndSelf(root), matching);
}
/**
* Returns the entity, its children, and all its children, and so on.
*
* @see #descendants(Entity, Predicate, boolean)
*/
public static Iterable<Entity> descendantsAndSelf(Entity root) {
Set<Entity> result = Sets.newLinkedHashSet();
result.add(root);
descendantsWithoutSelf(root, result);
return result;
}
/**
* Returns the entity's children, its children's children, and so on.
*
* @see #descendants(Entity, Predicate, boolean)
*/
public static Iterable<Entity> descendantsWithoutSelf(Entity root) {
Set<Entity> result = Sets.newLinkedHashSet();
descendantsWithoutSelf(root, result);
return result;
}
/**
* Return all descendants of given entity of the given type, potentially including the given root.
*
* @see #descendants(Entity)
* @see Iterables#filter(Iterable, Class)
*/
public static <T extends Entity> Iterable<T> descendantsAndSelf(Entity root, Class<T> ofType) {
return Iterables.filter(descendantsAndSelf(root), ofType);
}
/** Returns the entity, its parent, its parent, and so on. */
@SuppressWarnings("unused")
public static Iterable<Entity> ancestorsAndSelf(final Entity root) {
if (false) {
// Keeping this unused code, so that anonymous inner class numbers don't change!
return new Iterable<Entity>() {
@Override
public Iterator<Entity> iterator() {
return new Iterator<Entity>() {
Entity next = root;
@Override
public boolean hasNext() {
return next!=null;
}
@Override
public Entity next() {
Entity result = next;
next = next.getParent();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
Set<Entity> result = Sets.newLinkedHashSet();
Entity current = root;
while (current != null) {
result.add(current);
current = current.getParent();
}
return result;
}
/** Returns the entity's parent, its parent's parent, and so on. */
public static Iterable<Entity> ancestorsWithoutSelf(Entity root) {
Set<Entity> result = Sets.newLinkedHashSet();
Entity parent = (root != null) ? root.getParent() : null;
while (parent != null) {
result.add(parent);
parent = parent.getParent();
}
return result;
}
/**
* Side-effects {@code result} to return descendants (not including {@code root}).
*/
private static void descendantsWithoutSelf(Entity root, Collection<Entity> result) {
Stack<Entity> tovisit = new Stack<Entity>();
tovisit.add(root);
while (!tovisit.isEmpty()) {
Entity e = tovisit.pop();
result.addAll(e.getChildren());
tovisit.addAll(e.getChildren());
}
}
/**
* @deprecated since 0.10.0; see {@link #descendantsAndSelf(Entity, Predicate)}
*/
@Deprecated
public static Iterable<Entity> descendants(Entity root, Predicate<? super Entity> matching) {
return descendantsAndSelf(root, matching);
}
/**
* @deprecated since 0.10.0; see {@link #descendantsAndSelf(Entity)}
*/
@Deprecated
public static Iterable<Entity> descendants(Entity root) {
return descendantsAndSelf(root);
}
/**
* @deprecated since 0.10.0; see {@link #descendantsAndSelf(Entity)}
*/
@Deprecated
public static <T extends Entity> Iterable<T> descendants(Entity root, Class<T> ofType) {
return descendantsAndSelf(root, ofType);
}
/**
* @deprecated since 0.10.0; see {@link #ancestorsAndSelf(Entity)}
*/
@Deprecated
public static Iterable<Entity> ancestors(final Entity root) {
return ancestorsAndSelf(root);
}
/**
* Registers a {@link BrooklynShutdownHooks#invokeStopOnShutdown(Entity)} to shutdown this entity when the JVM exits.
* (Convenience method located in this class for easy access.)
*/
public static void invokeStopOnShutdown(Entity entity) {
BrooklynShutdownHooks.invokeStopOnShutdown(entity);
}
/** convenience for starting an entity, esp a new Startable instance which has been created dynamically
* (after the application is started) */
public static void start(Entity e, Collection<? extends Location> locations) {
if (!isManaged(e) && !manage(e)) {
log.warn("Using deprecated discouraged mechanism to start management -- Entities.start(Application, Locations) -- caller should create and use the preferred management context");
startManagement(e);
}
if (e instanceof Startable) Entities.invokeEffector(e, e, Startable.START,
MutableMap.of("locations", locations)).getUnchecked();
}
public static void destroy(Entity e) {
destroy(e, false);
}
/**
* Attempts to stop, destroy, and unmanage the given entity.
* <p>
* Actual actions performed will depend on the entity type and its current state.
*/
public static void destroy(Entity e, boolean unmanageOnErrors) {
if (isManaged(e)) {
if (isReadOnly(e)) {
unmanage(e);
log.debug("destroyed and unmanaged read-only copy of "+e);
} else {
List<Exception> errors = MutableList.of();
try {
if (e instanceof Startable) Entities.invokeEffector(e, e, Startable.STOP).getUnchecked();
} catch (Exception error) {
Exceptions.propagateIfFatal(error);
if (!unmanageOnErrors) Exceptions.propagate(error);
errors.add(error);
}
// if destroying gracefully we might also want to do this (currently gets done by GC after unmanage,
// which is good enough for leaks, but not sure if that's ideal for subscriptions etc)
// ((LocalEntityManager)e.getApplication().getManagementContext().getEntityManager()).stopTasks(e, null);
try {
if (e instanceof EntityInternal) ((EntityInternal) e).destroy();
} catch (Exception error) {
Exceptions.propagateIfFatal(error);
if (!unmanageOnErrors) Exceptions.propagate(error);
errors.add(error);
}
try {
unmanage(e);
} catch (Exception error) {
Exceptions.propagateIfFatal(error);
if (!unmanageOnErrors) Exceptions.propagate(error);
errors.add(error);
}
if (!errors.isEmpty()) {
log.warn("destroyed and unmanaged "+e+", with errors; mgmt now "+
(e.getApplicationId()==null ? "(no app)" : e.getApplication().getManagementContext())+" - managed? "+isManaged(e)+"; errors: "+errors);
throw Exceptions.propagate("Errors destroying "+e, errors);
}
log.debug("destroyed and unmanaged "+e+"; mgmt now "+
(e.getApplicationId()==null ? "(no app)" : e.getApplication().getManagementContext())+" - managed? "+isManaged(e));
}
} else {
log.debug("skipping destroy of "+e+": not managed");
}
}
/** Same as {@link #destroy(Entity)} but catching all errors. */
public static void destroyCatching(Entity entity) {
try {
destroy(entity);
} catch (Exception e) {
log.warn("ERROR destroying "+entity+" (ignoring): "+e, e);
Exceptions.propagateIfFatal(e);
}
}
/** Destroys the given location. */
public static void destroy(Location loc) {
// TODO unmanage the location, if possible?
if (loc instanceof Closeable) {
Streams.closeQuietly((Closeable)loc);
log.debug("closed "+loc);
}
}
/** Same as {@link #destroy(Location)} but catching all errors. */
public static void destroyCatching(Location loc) {
try {
destroy(loc);
} catch (Exception e) {
log.warn("ERROR destroying "+loc+" (ignoring): "+e, e);
Exceptions.propagateIfFatal(e);
}
}
/**
* Stops, destroys, and unmanages all apps in the given context, and then terminates the management context.
*
* Apps will be stopped+destroyed+unmanaged concurrently, waiting for all to complete.
*/
public static void destroyAll(final ManagementContext mgmt) {
final int MAX_THREADS = 100;
if (mgmt instanceof NonDeploymentManagementContext) {
// log here because it is easy for tests to destroyAll(app.getMgmtContext())
// which will *not* destroy the mgmt context if the app has been stopped!
log.warn("Entities.destroyAll invoked on non-deployment "+mgmt+" - not likely to have much effect! " +
"(This usually means the mgmt context has been taken from an entity that has been destroyed. " +
"To destroy other things on the management context ensure you keep a handle to the context " +
"before the entity is destroyed, such as by creating the management context first.)");
}
if (!mgmt.isRunning()) return;
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(MAX_THREADS));
List<ListenableFuture<?>> futures = Lists.newArrayList();
final AtomicReference<Exception> error = Atomics.newReference();
try {
log.debug("destroying all apps in "+mgmt+": "+mgmt.getApplications());
for (final Application app: mgmt.getApplications()) {
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
log.debug("destroying app "+app+" (managed? "+isManaged(app)+"; mgmt is "+mgmt+")");
try {
destroy(app);
log.debug("destroyed app "+app+"; mgmt now "+mgmt);
} catch (Exception e) {
log.warn("problems destroying app "+app+" (mgmt now "+mgmt+", will rethrow at least one exception): "+e);
error.compareAndSet(null, e);
}
}}));
}
Futures.allAsList(futures).get();
for (Location loc : mgmt.getLocationManager().getLocations()) {
destroyCatching(loc);
}
if (mgmt instanceof ManagementContextInternal) {
((ManagementContextInternal)mgmt).terminate();
}
if (error.get() != null) throw Exceptions.propagate(error.get());
} catch (Exception e) {
if (!mgmt.isRunning()) {
// we've checked this above so it would only happen if a different thread stopped it;
// this does happen sometimes e.g. in CliTest where the server shutdown occurs concurrently
log.debug("Destroying apps gave an error, but mgmt context was concurrently stopped so not really a problem; swallowing (unless fatal): "+e);
Exceptions.propagateIfFatal(e);
} else {
throw Exceptions.propagate(e);
}
} finally {
executor.shutdownNow();
}
}
/** Same as {@link #destroyAll(ManagementContext)} but catching all errors */
public static void destroyAllCatching(ManagementContext mgmt) {
try {
destroyAll(mgmt);
} catch (Exception e) {
log.warn("ERROR destroying "+mgmt+" (ignoring): "+e, e);
Exceptions.propagateIfFatal(e);
}
}
/** As {@link #isManagedOrReadOnly(Entity)}. Consider using {@link #isManagedActive(Entity)} instead. */
public static boolean isManaged(Entity e) {
return isManagedOrReadOnly(e);
}
/** If entity is under management and the management node is the primary for this entity, i.e. not read-only. */
public static boolean isManagedActive(Entity e) {
return ((EntityInternal)e).getManagementSupport().isActive() && ((EntityInternal)e).getManagementContext().isRunning() && !isReadOnly(e);
}
/** If entity is under management or coming up and the management node is the primary for this entity, i.e. not read-only. */
public static boolean isManagedActiveOrComingUp(Entity e) {
return (isManagedActive(e) || !((EntityInternal)e).getManagementSupport().wasDeployed()) && !isReadOnly(e);
}
/** If entity is under management either as primary or in read-only (hot-standby) state. */
public static boolean isManagedOrReadOnly(Entity e) {
return ((EntityInternal)e).getManagementSupport().isActive() && ((EntityInternal)e).getManagementContext().isRunning();
}
public static boolean isNoLongerManaged(Entity e) {
return ((EntityInternal)e).getManagementSupport().isNoLongerManaged();
}
public static boolean isUnmanaging(Entity e) {
return ((EntityInternal)e).getManagementSupport().isUnmanaging();
}
public static boolean isUnmanagingOrNoLongerManaged(Entity e) {
return isNoLongerManaged(e) || isUnmanaging(e);
}
/** if entity is managed, but in a read-only state */
public static boolean isReadOnly(Entity e) {
return Boolean.TRUE.equals( ((EntityInternal)e).getManagementSupport().isReadOnlyRaw() );
}
/** Unwraps a proxy to retrieve the real item, if available.
* <p>
* Only intended for use in tests and occasional internal usage, e.g. persistence.
* For normal operations, callers should ensure the method is available on an interface and accessed via the proxy. */
@Beta @VisibleForTesting
public static AbstractEntity deproxy(Entity e) {
if (!(Proxy.isProxyClass(e.getClass()))) {
// there are a few valid cases where callers might want to deproxy, so don't warn
// log.warn("Attempt to deproxy non-proxy "+e, new Throwable("Location of attempt to deproxy non-proxy "+e));
return (AbstractEntity) e;
}
return (AbstractEntity) ((EntityProxyImpl)Proxy.getInvocationHandler(e)).getDelegate();
}
/**
* Returns the proxy form (if available) of the entity. If already a proxy, returns unmodified.
*
* If null is passed in, then null is returned.
*
* For legacy entities (that did not use {@link EntitySpec} or YAML for creation), the
* proxy may not be avilable; in which case the concrete class passed in will be returned.
*/
@Beta
@SuppressWarnings("unchecked")
public static <T extends Entity> T proxy(T e) {
return (e == null) ? null : e instanceof Proxy ? e : (T) ((AbstractEntity)e).getProxyIfAvailable();
}
/**
* Brings this entity under management only if its ancestor is managed.
* <p>
* Returns true if successful, otherwise returns false in the expectation that the ancestor
* will become managed, or throws exception if it has no parent or a non-application root.
*
* @throws IllegalStateException if {@literal e} is an {@link Application}.
* @see #startManagement(Entity)
*
* @deprecated since 0.9.0; entities are automatically managed when created via {@link Entity#addChild(EntitySpec)},
* or with {@link EntityManager#createEntity(EntitySpec)} (it is strongly encouraged to include the parent
* if using the latter for anything but a top-level app).
*/
@Deprecated
public static boolean manage(Entity e) {
if (Entities.isManaged(e)) {
return true; // no-op
}
log.warn("Deprecated use of Entities.manage(Entity), for unmanaged entity "+e);
Entity o = e.getParent();
Entity eum = e; // Highest unmanaged ancestor
if (o==null) throw new IllegalArgumentException("Can't manage "+e+" because it is an orphan");
while (o.getParent()!=null) {
if (!isManaged(o)) eum = o;
o = o.getParent();
}
if (isManaged(o)) {
ManagementContextInternal mgmt = (ManagementContextInternal) ((EntityInternal) o).getManagementContext();
premanageRecursively(mgmt, eum);
mgmt.getEntityManager().manage(eum);
return true;
}
if (!(o instanceof Application)) {
throw new IllegalStateException("Can't manage "+e+" because it is not rooted at an application");
}
return false;
}
/**
* Brings this entity under management, creating a local management context if necessary,
* assuming root is an application.
* <p>
* Returns existing management context if there is one (non-deployment) or a new local management
* context if not, or throws an exception if root is not an application. Callers are recommended
* to use {@link #manage(Entity)} instead unless they know a plain-vanilla non-root management
* context is sufficient e.g. in tests.
* <p>
* <b>NOTE</b> This method may change, but is provided as a stop-gap to prevent ad-hoc things
* being done in the code which are even more likely to break!
*
* @deprecated since 0.9.0; entities are automatically managed when created via {@link Entity#addChild(EntitySpec)},
* or with {@link EntityManager#createEntity(EntitySpec)}.
*/
@Deprecated
@Beta
public static ManagementContext startManagement(Entity e) {
log.warn("Deprecated use of Entities.startManagement(Entity), for entity "+e);
Entity o = e;
Entity eum = e; // Highest unmanaged ancestor
while (o.getParent()!=null) {
if (!isManaged(o)) eum = o;
o = o.getParent();
}
if (isManaged(o)) {
ManagementContext mgmt = ((EntityInternal)o).getManagementContext();
mgmt.getEntityManager().manage(eum);
return mgmt;
}
if (!(o instanceof Application))
throw new IllegalStateException("Can't manage "+e+" because it is not rooted at an application");
log.warn("Deprecated invocation of startManagement for "+e+" without a management context present; "
+ "a new local management context is being created! (Not recommended unless you really know what you are doing.)");
ManagementContext mgmt = new LocalManagementContext();
mgmt.getEntityManager().manage(o);
return mgmt;
}
/**
* Starts managing the given (unmanaged) app, using the given management context.
*
* @see #startManagement(Entity)
*
* @deprecated since 0.9.0; entities are automatically managed when created with
* {@link EntityManager#createEntity(EntitySpec)}. For top-level apps, use code like
* {@code managementContext.getEntityManager().createEntity(EntitySpec.create(...))}.
*/
@Deprecated
public static ManagementContext startManagement(Application app, ManagementContext mgmt) {
log.warn("Deprecated use of Entities.startManagement(Application, ManagementContext), for app "+app);
if (isManaged(app)) {
if (app.getManagementContext() == mgmt) {
// no-op; app was presumably auto-managed
return mgmt;
} else {
throw new IllegalStateException("Application "+app+" is already managed by "+app.getManagementContext()+", so cannot be managed by "+mgmt);
}
}
premanageRecursively((ManagementContextInternal)mgmt, app);
mgmt.getEntityManager().manage(app);
return mgmt;
}
private static void premanageRecursively(ManagementContextInternal mgmt, Entity e) {
if (Entities.isUnmanagingOrNoLongerManaged(e) || Entities.isManagedActive(e)) {
log.warn("Skipping premanagement for "+e+", as it has some form of known management");
// skip for active and unmanaged entities
return;
}
mgmt.prePreManage(e);
e.getChildren().forEach(e2 -> premanageRecursively(mgmt, e2));
}
/**
* Starts managing the given (unmanaged) app, setting the given brooklyn properties on the new
* management context.
*
* @see #startManagement(Entity)
*
* @deprecated since 0.9.0; entities are automatically managed when created via {@link Entity#addChild(EntitySpec)},
* or with {@link EntityManager#createEntity(EntitySpec)}. For top-level apps, use code like
* {@code managementContext.getEntityManager().createEntity(EntitySpec.create(...))}.
*/
@Deprecated
public static ManagementContext startManagement(Application app, BrooklynProperties props) {
log.warn("Deprecated use of Entities.startManagement(Application, BrooklynProperties), for app "+app);
if (isManaged(app)) {
throw new IllegalStateException("Application "+app+" is already managed, so can't set brooklyn properties");
}
ManagementContext mgmt = new LocalManagementContext(props);
mgmt.getEntityManager().manage(app);
return mgmt;
}
public static ManagementContext newManagementContext() {
return new LocalManagementContext();
}
public static ManagementContext newManagementContext(BrooklynProperties props) {
return new LocalManagementContext(props);
}
public static ManagementContext newManagementContext(Map<?,?> props) {
return new LocalManagementContext( BrooklynProperties.Factory.newEmpty().addFromMap(props));
}
public static ManagementContext getManagementContext(Entity entity) {
return ((EntityInternal) entity).getManagementContext();
}
public static void unmanage(Entity entity) {
if (((EntityInternal)entity).getManagementSupport().isDeployed()) {
((EntityInternal)entity).getManagementContext().getEntityManager().unmanage(entity);
}
}
public static DownloadResolver newDownloader(EntityDriver driver) {
return newDownloader(driver, ImmutableMap.<String,Object>of());
}
public static DownloadResolver newDownloader(EntityDriver driver, Map<String,?> properties) {
EntityInternal internal = (EntityInternal) driver.getEntity();
return internal.getManagementContext().getEntityDownloadsManager().newDownloader(driver, properties);
}
public static DownloadResolver newDownloader(EntityDriver driver, String addon) {
return newDownloader(driver, addon, ImmutableMap.<String,Object>of());
}
public static DownloadResolver newDownloader(EntityDriver driver, String addon, Map<String,?> properties) {
EntityInternal internal = (EntityInternal) driver.getEntity();
return internal.getManagementContext().getEntityDownloadsManager().newDownloader(driver, addon, properties);
}
public static <T> Supplier<T> attributeSupplier(Entity entity, AttributeSensor<T> sensor) {
return EntityAndAttribute.create(entity, sensor);
}
public static <T> Supplier<T> attributeSupplier(EntityAndAttribute<T> tuple) { return tuple; }
public static <T> Supplier<T> attributeSupplierWhenReady(EntityAndAttribute<T> tuple) {
return attributeSupplierWhenReady(tuple.getEntity(), tuple.getAttribute());
}
@SuppressWarnings({ "unchecked", "serial" })
public static <T> Supplier<T> attributeSupplierWhenReady(final Entity entity, final AttributeSensor<T> sensor) {
final Task<T> task = DependentConfiguration.attributeWhenReady(entity, sensor);
return new Supplier<T>() {
@Override public T get() {
try {
TypeToken<T> type = new TypeToken<T>(sensor.getType()) {};
return Tasks.resolveValue(task, type, ((EntityInternal) entity).getExecutionContext(), "attributeSupplierWhenReady");
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
};
}
/**
* @since 0.6.0 Added only for backwards compatibility, where locations are being created directly.
* @deprecated in 0.6.0; use {@link LocationManager#createLocation(LocationSpec)} instead
*/
@Deprecated
public static void manage(Location loc, ManagementContext managementContext) {
Locations.manage(loc, managementContext);
}
/** Fails-fast if value of the given key is null or unresolveable. */
public static String getRequiredUrlConfig(Entity entity, ConfigKey<String> urlKey) {
String url = entity.getConfig(urlKey);
Preconditions.checkNotNull(url, "Key %s on %s should not be null", urlKey, entity);
if (!ResourceUtils.create(entity).doesUrlExist(url)) {
throw new IllegalStateException(String.format("Key %s on %s contains unavailable URL %s", urlKey, entity, url));
}
return url;
}
/** @see #getRequiredUrlConfig(Entity, ConfigKey) */
public static String getRequiredUrlConfig(Entity entity, HasConfigKey<String> urlKey) {
return getRequiredUrlConfig(entity, urlKey.getConfigKey());
}
/** Fails-fast if value of the given URL is null or unresolveable. */
public static String checkRequiredUrl(Entity entity, String url) {
Preconditions.checkNotNull(url, "url");
if (!ResourceUtils.create(entity).doesUrlExist(url)) {
throw new IllegalStateException(String.format("URL %s on %s is unavailable", url, entity));
}
return url;
}
/**
* Submits a {@link TaskFactory} to construct its task at the entity (in a precursor task) and then to submit it.
* <p>
* Important if task construction relies on an entity being in scope (in tags, via {@link BrooklynTaskTags})
*/
public static <T extends TaskAdaptable<?>> T submit(final Entity entity, final TaskFactory<T> taskFactory) {
// TODO it is messy to have to do this, but not sure there is a cleaner way :(
final Semaphore s = new Semaphore(0);
final AtomicReference<T> result = new AtomicReference<T>();
final ExecutionContext executionContext = ((EntityInternal)entity).getExecutionContext();
executionContext.execute(new Runnable() {
// TODO could give this task a name, like "create task from factory"
@Override
public void run() {
T t = taskFactory.newTask();
result.set(t);
s.release();
}
});
try {
s.acquire();
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
}
executionContext.submit(result.get().asTask());
return result.get();
}
/**
* Submits a task to run at the entity.
*
* @return the task passed in, for fluency
*/
public static <T extends TaskAdaptable<?>> T submit(final Entity entity, final T task) {
final ExecutionContext executionContext = ((EntityInternal)entity).getExecutionContext();
executionContext.submit(task.asTask());
return task;
}
/** Logs a warning if an entity has a value for a config key. */
public static void warnOnIgnoringConfig(Entity entity, ConfigKey<?> key) {
if (((EntityInternal)entity).config().getRaw(key).isPresentAndNonNull())
log.warn("Ignoring "+key+" set on "+entity+" ("+entity.getConfig(key)+")");
}
/** Waits until the passed entity satisfies the supplied predicate. */
public static void waitFor(Entity entity, Predicate<Entity> condition, Duration timeout) {
try {
String description = "Waiting for " + condition + " on " + entity;
Tasks.setBlockingDetails(description);
Repeater repeater = Repeater.create(description)
.until(isSatisfied(entity, condition))
.limitTimeTo(timeout)
.backoffTo(Duration.ONE_SECOND)
.rethrowException();
if (!repeater.run()) {
throw new IllegalStateException("Timeout waiting for " + condition + " on " + entity);
}
} finally {
Tasks.resetBlockingDetails();
}
log.debug("Detected {} for {}", condition, entity);
}
/** Waits until {@link Startable#SERVICE_UP} is true. */
public static void waitForServiceUp(final Entity entity, Duration timeout) {
waitFor(entity, EntityPredicates.isServiceUp(), timeout);
}
public static void waitForServiceUp(final Entity entity, long duration, TimeUnit units) {
waitForServiceUp(entity, Duration.of(duration, units));
}
public static void waitForServiceUp(final Entity entity) {
Duration timeout = entity.getConfig(BrooklynConfigKeys.START_TIMEOUT);
waitForServiceUp(entity, timeout);
}
/**
* Convenience for creating and submitted a given shell command against the given mgmt context,
* primarily intended for use in the groovy GUI console.
*/
@Beta
public static ProcessTaskWrapper<Integer> shell(ManagementContext mgmt, String command) {
ProcessTaskWrapper<Integer> t = SystemTasks.exec(command).newTask();
mgmt.getServerExecutionContext().submit(t).getUnchecked();
System.out.println(t.getStdout());
System.err.println(t.getStderr());
return t;
}
public static Entity catalogItemScopeRoot(Entity entity) {
Entity root = entity;
Integer depth = BrooklynTags.getDepthInAncestorTag(root.tags().getTags());
if (depth!=null && depth>0) {
while (depth>0) {
root = root.getParent();
depth--;
}
return root;
}
while (root.getParent() != null &&
root != root.getParent() &&
Objects.equal(root.getParent().getCatalogItemId(), root.getCatalogItemId())) {
root = root.getParent();
}
return root;
}
public static Set<Location> getAllInheritedLocations(Entity entity) {
Set<Location> result = MutableSet.of();
while (entity!=null) {
result.addAll(entity.getLocations());
entity = entity.getParent();
}
return result;
}
}