blob: 4b6b8cee960ed0b940e08279f62e2cff697379e7 [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.mgmt;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.catalog.CatalogItem;
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.internal.AbstractBrooklynObjectSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.effector.Effectors;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityFunctions;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.TaskBuilder;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Runnables;
/** Utility methods for working with entities and applications */
public class EntityManagementUtils {
private static final Logger log = LoggerFactory.getLogger(EntityManagementUtils.class);
/**
* A marker config value which indicates that an {@link Application} entity was created automatically,
* needed because a plan might give multiple top-level entities or a non-Application top-level entity,
* in a context where Brooklyn requires an {@link Application} at the root.
* <p>
* Typically when such a wrapper app wraps another {@link Application}
* (or where we are looking for a single {@link Entity}, or a list to add, and they are so wrapped)
* it will be unwrapped.
* See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}.
*/
public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app");
/** creates an application from the given app spec, managed by the given management context */
public static <T extends Application> T createUnstarted(ManagementContext mgmt, EntitySpec<T> spec) {
T app = mgmt.getEntityManager().createEntity(spec);
return app;
}
/** as {@link #createUnstarted(ManagementContext, EntitySpec)} but for a string plan (e.g. camp yaml) */
public static Application createUnstarted(ManagementContext mgmt, String plan) {
EntitySpec<? extends Application> spec = createEntitySpecForApplication(mgmt, plan);
return createUnstarted(mgmt, spec);
}
@SuppressWarnings("unchecked")
public static EntitySpec<? extends Application> createEntitySpecForApplication(ManagementContext mgmt, final String plan) {
return mgmt.getTypeRegistry().createSpecFromPlan(null, plan, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class);
}
@Deprecated /** @deprecated since 0.9.0; use {@link BrooklynTypeRegistry#createSpec(RegisteredType, org.apache.brooklyn.api.typereg.RegisteredTypeConstraint, Class)} */
// not used in Brooklyn
public static <T,SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(ManagementContext mgmt, CatalogItem<T, SpecT> item) {
return createCatalogSpec(mgmt, item, ImmutableSet.<String>of());
}
@Deprecated /** @deprecated since 0.9.0; use {@link BrooklynTypeRegistry#createSpec(RegisteredType, org.apache.brooklyn.api.typereg.RegisteredTypeConstraint, Class)} */
// not used in Brooklyn
public static <T,SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(ManagementContext mgmt, final CatalogItem<T, SpecT> item, final Set<String> encounteredTypes) {
return BasicBrooklynCatalog.internalCreateSpecLegacy(mgmt, item, encounteredTypes, true);
}
/** container for operation which creates something and which wants to return both
* the items created and any pending create/start task */
public static class CreationResult<T,U> {
private final T thing;
@Nullable private final Task<U> task;
public CreationResult(T thing, Task<U> task) {
super();
this.thing = thing;
this.task = task;
}
protected static <T,U> CreationResult<T,U> of(T thing, @Nullable Task<U> task) {
return new CreationResult<T,U>(thing, task);
}
/** returns the thing/things created */
@Nullable public T get() { return thing; }
/** associated task, ie the one doing the creation/starting */
public Task<U> task() { return task; }
public CreationResult<T,U> blockUntilComplete(Duration timeout) { if (task!=null) task.blockUntilEnded(timeout); return this; }
public CreationResult<T,U> blockUntilComplete() { if (task!=null) task.blockUntilEnded(); return this; }
}
public static <T extends Application> CreationResult<T,Void> createStarting(ManagementContext mgmt, EntitySpec<T> appSpec) {
return start(createUnstarted(mgmt, appSpec));
}
public static CreationResult<? extends Application,Void> createStarting(ManagementContext mgmt, String appSpec) {
return start(createUnstarted(mgmt, appSpec));
}
public static <T extends Application> CreationResult<T,Void> start(T app) {
Task<Void> task = Entities.invokeEffector(app, app, Startable.START,
// locations already set in the entities themselves;
// TODO make it so that this arg does not have to be supplied to START !
MutableMap.of("locations", MutableList.of()));
return CreationResult.of(app, task);
}
public static CreationResult<List<Entity>, List<String>> addChildren(final Entity parent, String yaml, Boolean start) {
if (Boolean.FALSE.equals(start))
return CreationResult.of(addChildrenUnstarted(parent, yaml), null);
return addChildrenStarting(parent, yaml);
}
/** adds entities from the given yaml, under the given parent; but does not start them */
public static List<Entity> addChildrenUnstarted(final Entity parent, String yaml) {
log.debug("Creating child of "+parent+" from yaml:\n{}", yaml);
ManagementContext mgmt = parent.getApplication().getManagementContext();
EntitySpec<? extends Application> specA = createEntitySpecForApplication(mgmt, yaml);
// see whether we can promote children
List<EntitySpec<?>> specs = MutableList.of();
if (!canUnwrapEntity(specA)) {
// if not promoting, set a nice name if needed
if (Strings.isEmpty(specA.getDisplayName())) {
int size = specA.getChildren().size();
String childrenCountString = size+" "+(size!=1 ? "children" : "child");
specA.displayName("Dynamically added "+childrenCountString);
}
}
specs.add(unwrapEntity(specA));
final List<Entity> children = MutableList.of();
for (EntitySpec<?> spec: specs) {
Entity child = (Entity)parent.addChild(spec);
children.add(child);
}
return children;
}
public static CreationResult<List<Entity>,List<String>> addChildrenStarting(final Entity parent, String yaml) {
final List<Entity> children = addChildrenUnstarted(parent, yaml);
String childrenCountString;
int size = children.size();
childrenCountString = size+" "+(size!=1 ? "children" : "child");
TaskBuilder<List<String>> taskM = Tasks.<List<String>>builder().displayName("add children")
.dynamic(true)
.tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
.body(new Callable<List<String>>() {
@Override public List<String> call() throws Exception {
return ImmutableList.copyOf(Iterables.transform(children, EntityFunctions.id()));
}})
.description("Add and start "+childrenCountString);
TaskBuilder<?> taskS = Tasks.builder().parallel(true).displayName("add (parallel)").description("Start each new entity");
// autostart if requested
for (Entity child: children) {
if (child instanceof Startable) {
taskS.add(Effectors.invocation(child, Startable.START, ImmutableMap.of("locations", ImmutableList.of())));
} else {
// include a task, just to give feedback in the GUI
taskS.add(Tasks.builder().displayName("create").description("Skipping start (not a Startable Entity)")
.body(Runnables.doNothing())
.tag(BrooklynTaskTags.tagForTargetEntity(child))
.build());
}
}
taskM.add(taskS.build());
Task<List<String>> task = Entities.submit(parent, taskM.build());
return CreationResult.of(children, task);
}
/** Unwraps a single {@link Entity} if appropriate. See {@link #WRAPPER_APP_MARKER}.
* Also see {@link #canUnwrapEntity(EntitySpec)} to test whether it will unwrap. */
public static EntitySpec<? extends Entity> unwrapEntity(EntitySpec<? extends Entity> wrapperApplication) {
if (!canUnwrapEntity(wrapperApplication)) {
return wrapperApplication;
}
EntitySpec<?> wrappedEntity = Iterables.getOnlyElement(wrapperApplication.getChildren());
@SuppressWarnings("unchecked")
EntitySpec<? extends Application> wrapperApplicationTyped = (EntitySpec<? extends Application>) wrapperApplication;
EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplicationTyped, wrappedEntity);
return wrappedEntity;
}
/** Unwraps a wrapped {@link Application} if appropriate.
* This is like {@link #canUnwrapEntity(EntitySpec)} with an additional check that the wrapped child is an {@link Application}.
* See {@link #WRAPPER_APP_MARKER} for an overview.
* Also see {@link #canUnwrapApplication(EntitySpec)} to test whether it will unwrap. */
public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
if (!canUnwrapApplication(wrapperApplication)) {
return wrapperApplication;
}
@SuppressWarnings("unchecked")
EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) unwrapEntity(wrapperApplication);
return wrappedApplication;
}
/** Modifies the child so it includes the inessential setup of its parent,
* for use when unwrapping specific children, but a name or other item may have been set on the parent.
* See {@link #WRAPPER_APP_MARKER}. */
private static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) {
if (Strings.isNonEmpty(wrapperParent.getDisplayName()))
wrappedChild.displayName(wrapperParent.getDisplayName());
if (!wrapperParent.getLocations().isEmpty())
wrappedChild.locations(wrapperParent.getLocations());
if (!wrapperParent.getParameters().isEmpty()) {
wrappedChild.parameters(wrapperParent.getParameters());
}
// NB: this clobbers child config; might prefer to deeply merge maps etc
// (but this should not be surprising, as unwrapping is often parameterising the nested blueprint, so outer config should dominate)
Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(wrapperParent.getConfig(), Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER)));
wrappedChild.configure(configWithoutWrapperMarker);
wrappedChild.configure(wrapperParent.getFlags());
// TODO copying tags to all entities is not ideal;
// in particular the BrooklynTags.YAML_SPEC tag will show all entities if the root has multiple
wrappedChild.tags(wrapperParent.getTags());
}
public static EntitySpec<? extends Application> newWrapperApp() {
return EntitySpec.create(BasicApplication.class).configure(WRAPPER_APP_MARKER, true);
}
/** As {@link #canUnwrapEntity(EntitySpec)}
* but additionally requiring that the wrapped item is an {@link Application},
* for use when the context requires an {@link Application} ie a root of a spec.
* @see #WRAPPER_APP_MARKER */
public static boolean canUnwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
if (!canUnwrapEntity(wrapperApplication)) return false;
EntitySpec<?> childSpec = Iterables.getOnlyElement(wrapperApplication.getChildren());
return (childSpec.getType()!=null && Application.class.isAssignableFrom(childSpec.getType()));
}
/** @deprecated since 0.9.0 use {@link #canUnwrapApplication(EntitySpec)} */ @Deprecated
public static boolean canPromoteWrappedApplication(EntitySpec<? extends Application> app) {
return canUnwrapApplication(app);
}
/** Returns true if the spec is for a wrapper app with no important settings, wrapping a single child entity.
* for use when adding from a plan specifying multiple entities but there is nothing significant at the application level,
* and the context would like to flatten it to remove the wrapper yielding just a single entity.
* (but note the result is not necessarily an {@link Application};
* see {@link #canUnwrapApplication(EntitySpec)} if that is required).
* <p>
* Note callers will normally use one of {@link #unwrapEntity(EntitySpec)} or {@link #unwrapApplication(EntitySpec)}.
*
* @see #WRAPPER_APP_MARKER for an overview */
public static boolean canUnwrapEntity(EntitySpec<? extends Entity> spec) {
return isWrapperApp(spec) && hasSingleChild(spec) &&
//equivalent to no keys starting with "brooklyn."
spec.getEnrichers().isEmpty() &&
spec.getEnricherSpecs().isEmpty() &&
spec.getInitializers().isEmpty() &&
spec.getPolicies().isEmpty() &&
spec.getPolicySpecs().isEmpty();
}
/** @deprecated since 0.9.0 use {@link #canUnwrapEntity(EntitySpec)} */ @Deprecated
public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<? extends Application> spec) {
return canUnwrapEntity(spec);
}
public static boolean isWrapperApp(EntitySpec<?> spec) {
return Boolean.TRUE.equals(spec.getConfig().get(EntityManagementUtils.WRAPPER_APP_MARKER));
}
private static boolean hasSingleChild(EntitySpec<?> spec) {
return spec.getChildren().size() == 1;
}
}