/* * 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 static com.google.common.base.Preconditions.checkNotNull; import java.io.ByteArrayOutputStream; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionManager; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.task.TaskTags; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.MemoryUsageTracker; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; import org.apache.brooklyn.util.text.Strings; import org.codehaus.jackson.annotate.JsonProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Functions; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableSet; /** Provides utilities for making Tasks easier to work with in Brooklyn. * Main thing at present is to supply (and find) wrapped entities for tasks to understand the * relationship of the entity to the task. * TODO Longer term it would be better to remove 'tags' on Tasks and use a strongly typed context object. * (Tags there are used mainly for determining who called it (caller), what they called it on (target entity), * and what type of task it is (effector, schedule/sensor, etc).) */ public class BrooklynTaskTags extends TaskTags { private static final Logger log = LoggerFactory.getLogger(BrooklynTaskTags.WrappedEntity.class); /** Tag for tasks which are running on behalf of the management server, rather than any entity */ public static final String BROOKLYN_SERVER_TASK_TAG = "BROOKLYN-SERVER"; /** Tag for a task which represents an effector */ public static final String EFFECTOR_TAG = "EFFECTOR"; /** Tag for a task which *is* interesting, in contrast to {@link #TRANSIENT_TASK_TAG} */ public static final String NON_TRANSIENT_TASK_TAG = "NON-TRANSIENT"; /** indicates a task is transient, roughly that is to say it is uninteresting -- * specifically this means it can be GC'd as soon as it is completed, * and that it need not appear in some task lists; * often used for framework lifecycle events and sensor polling */ public static final String TRANSIENT_TASK_TAG = "TRANSIENT"; // ------------- entity tags ------------------------- public static class WrappedEntity { public final String wrappingType; public final Entity entity; protected WrappedEntity(String wrappingType, Entity entity) { Preconditions.checkNotNull(wrappingType); Preconditions.checkNotNull(entity); this.wrappingType = wrappingType; this.entity = entity; } @Override public String toString() { return "Wrapped["+wrappingType+":"+entity+"]"; } @Override public int hashCode() { return Objects.hashCode(entity, wrappingType); } @Override public boolean equals(Object obj) { if (this==obj) return true; if (!(obj instanceof WrappedEntity)) return false; return Objects.equal(entity, ((WrappedEntity)obj).entity) && Objects.equal(wrappingType, ((WrappedEntity)obj).wrappingType); } } public static final String CONTEXT_ENTITY = "contextEntity"; public static final String CALLER_ENTITY = "callerEntity"; public static final String TARGET_ENTITY = "targetEntity"; public static WrappedEntity tagForContextEntity(Entity entity) { return new WrappedEntity(CONTEXT_ENTITY, entity); } public static WrappedEntity tagForCallerEntity(Entity entity) { return new WrappedEntity(CALLER_ENTITY, entity); } public static WrappedEntity tagForTargetEntity(Entity entity) { return new WrappedEntity(TARGET_ENTITY, entity); } public static Entity getWrappedEntityOfType(Task<?> t, String wrappingType) { if (t==null) return null; return getWrappedEntityOfType(t.getTags(), wrappingType); } public static Entity getWrappedEntityOfType(Collection<?> tags, String wrappingType) { for (Object x: tags) if ((x instanceof WrappedEntity) && ((WrappedEntity)x).wrappingType.equals(wrappingType)) return ((WrappedEntity)x).entity; return null; } public static Entity getContextEntity(Task<?> task) { return getWrappedEntityOfType(task, CONTEXT_ENTITY); } public static Entity getTargetOrContextEntity(Task<?> t) { if (t==null) return null; Entity result = getWrappedEntityOfType(t, CONTEXT_ENTITY); if (result!=null) return result; result = getWrappedEntityOfType(t, TARGET_ENTITY); if (result!=null) { log.warn("Context entity found by looking at target entity tag, not context entity"); return result; } result = Tasks.tag(t, Entity.class, false); if (result!=null) { log.warn("Context entity found by looking at 'Entity' tag, not wrapped entity"); } return result; } public static Set<Task<?>> getTasksInEntityContext(ExecutionManager em, Entity e) { return em.getTasksWithTag(tagForContextEntity(e)); } public static ManagementContext getManagementContext(Task<?> task) { for (Object tag : task.getTags()) if ((tag instanceof ManagementContext)) return (ManagementContext) tag; return null; } // ------------- stream tags ------------------------- public static class WrappedStream { public final String streamType; public final Supplier<String> streamContents; public final Supplier<Integer> streamSize; protected WrappedStream(String streamType, Supplier<String> streamContents, Supplier<Integer> streamSize) { Preconditions.checkNotNull(streamType); Preconditions.checkNotNull(streamContents); this.streamType = streamType; this.streamContents = streamContents; this.streamSize = streamSize != null ? streamSize : Suppliers.<Integer>ofInstance(streamContents.get().length()); } protected WrappedStream(String streamType, ByteArrayOutputStream stream) { Preconditions.checkNotNull(streamType); Preconditions.checkNotNull(stream); this.streamType = streamType; this.streamContents = Strings.toStringSupplier(stream); this.streamSize = Streams.sizeSupplier(stream); } // fix for https://github.com/FasterXML/jackson-databind/issues/543 (which also applies to codehaus jackson) @JsonProperty public Integer getStreamSize() { return streamSize.get(); } // there is a stream api so don't return everything unless explicitly requested! @JsonProperty("streamContents") public String getStreamContentsAbbreviated() { return Strings.maxlenWithEllipsis(streamContents.get(), 80); } @Override public String toString() { return "Stream["+streamType+"/"+Strings.makeSizeString(streamSize.get())+"]"; } @Override public int hashCode() { return Objects.hashCode(streamContents, streamType); } @Override public boolean equals(Object obj) { if (!(obj instanceof WrappedStream)) return false; return Objects.equal(streamContents, ((WrappedStream)obj).streamContents) && Objects.equal(streamType, ((WrappedStream)obj).streamType); } } public static final String STREAM_STDIN = "stdin"; public static final String STREAM_STDOUT = "stdout"; public static final String STREAM_STDERR = "stderr"; /** not a stream, but inserted with the same mechanism */ public static final String STREAM_ENV = "env"; private static final Maybe<ByteArrayOutputStream> STREAM_GARBAGE_COLLECTED_MAYBE = Maybe.of(Streams.byteArrayOfString("<contents-garbage-collected>")); /** creates a tag suitable for marking a stream available on a task */ public static WrappedStream tagForStream(String streamType, ByteArrayOutputStream stream) { return new WrappedStream(streamType, stream); } /** creates a tag suitable for marking a stream available on a task, but which might be GC'd */ public static WrappedStream tagForStreamSoft(String streamType, ByteArrayOutputStream stream) { MemoryUsageTracker.SOFT_REFERENCES.track(stream, stream.size()); Maybe<ByteArrayOutputStream> weakStream = Maybe.softThen(stream, STREAM_GARBAGE_COLLECTED_MAYBE); return new WrappedStream(streamType, Suppliers.compose(Functions.toStringFunction(), weakStream), Suppliers.compose(Streams.sizeFunction(), weakStream)); } /** creates a tag suitable for marking a stream available on a task */ public static WrappedStream tagForStream(String streamType, Supplier<String> contents, Supplier<Integer> size) { return new WrappedStream(streamType, contents, size); } /** creates a tag suitable for attaching a snapshot of an environment var map as a "stream" on a task; * mainly for use with STREAM_ENV */ public static WrappedStream tagForEnvStream(String streamEnv, Map<?, ?> env) { StringBuilder sb = new StringBuilder(); for (Map.Entry<?,?> kv: env.entrySet()) { Object val = kv.getValue(); sb.append(kv.getKey()+"=" + (val!=null ? BashStringEscapes.wrapBash(val.toString()) : "") + "\n"); } return BrooklynTaskTags.tagForStream(BrooklynTaskTags.STREAM_ENV, Streams.byteArrayOfString(sb.toString())); } /** returns the set of tags indicating the streams available on a task */ public static Set<WrappedStream> streams(Task<?> task) { Set<WrappedStream> result = new LinkedHashSet<BrooklynTaskTags.WrappedStream>(); for (Object tag: task.getTags()) { if (tag instanceof WrappedStream) { result.add((WrappedStream)tag); } } return ImmutableSet.copyOf(result); } /** returns the tag for the indicated stream, or null */ public static WrappedStream stream(Task<?> task, String streamType) { if (task==null) return null; for (Object tag: task.getTags()) if ((tag instanceof WrappedStream) && ((WrappedStream)tag).streamType.equals(streamType)) return (WrappedStream)tag; return null; } // ------ misc public static void setInessential(Task<?> task) { addTagDynamically(task, INESSENTIAL_TASK); } public static void setTransient(Task<?> task) { addTagDynamically(task, TRANSIENT_TASK_TAG); } public static boolean isTransient(Task<?> task) { if (hasTag(task, TRANSIENT_TASK_TAG)) return true; if (hasTag(task, NON_TRANSIENT_TASK_TAG)) return false; if (task.getSubmittedByTask()!=null) return isTransient(task.getSubmittedByTask()); return false; } public static boolean isSubTask(Task<?> task) { return hasTag(task, SUB_TASK_TAG); } public static boolean isEffectorTask(Task<?> task) { return hasTag(task, EFFECTOR_TAG); } // ------ effector tags public static class EffectorCallTag { protected final String entityId; protected final String effectorName; protected transient ConfigBag parameters; protected EffectorCallTag(String entityId, String effectorName, ConfigBag parameters) { this.entityId = checkNotNull(entityId, "entityId"); this.effectorName = checkNotNull(effectorName, "effectorName"); this.parameters = parameters; } public String toString() { return EFFECTOR_TAG+"@"+entityId+":"+effectorName; } @Override public int hashCode() { return Objects.hashCode(entityId, effectorName); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof EffectorCallTag)) return false; EffectorCallTag other = (EffectorCallTag) obj; return Objects.equal(entityId, other.entityId) && Objects.equal(effectorName, other.effectorName); } public String getEntityId() { return entityId; } public String getEffectorName() { return effectorName; } public ConfigBag getParameters() { return parameters; } public void setParameters(ConfigBag parameters) { this.parameters = parameters; } } public static EffectorCallTag tagForEffectorCall(Entity entity, String effectorName, ConfigBag parameters) { return new EffectorCallTag(entity.getId(), effectorName, parameters); } /** * checks if the given task is part of the given effector call on the given entity; * @param task the task to check (false if null) * @param entity the entity where this effector task should be running, or any entity if null * @param effector the effector (matching name) where this task should be running, or any effector if null * @param allowNestedEffectorCalls whether to match ancestor effector calls, e.g. if eff1 calls eff2, * and we are checking eff2, whether to match eff1 * @return whether the given task is part of the given effector */ public static boolean isInEffectorTask(Task<?> task, @Nullable Entity entity, @Nullable Effector<?> effector, boolean allowNestedEffectorCalls) { Task<?> t = task; while (t!=null) { Set<Object> tags = t.getTags(); if (tags.contains(EFFECTOR_TAG)) { for (Object tag: tags) { if (tag instanceof EffectorCallTag) { EffectorCallTag et = (EffectorCallTag)tag; if (entity!=null && !et.getEntityId().equals(entity.getId())) continue; if (effector!=null && !et.getEffectorName().equals(effector.getName())) continue; return true; } } if (!allowNestedEffectorCalls) return false; } t = t.getSubmittedByTask(); } return false; } /** * finds the task up the {@code child} hierarchy handling the {@code effector} call, * returns null if one doesn't exist. */ @Beta public static Task<?> getClosestEffectorTask(Task<?> child, Effector<?> effector) { Task<?> t = child; while (t != null) { Set<Object> tags = t.getTags(); if (tags.contains(EFFECTOR_TAG)) { for (Object tag: tags) { if (tag instanceof EffectorCallTag) { EffectorCallTag et = (EffectorCallTag) tag; if (effector != null && !et.getEffectorName().equals(effector.getName())) continue; return t; } } } t = t.getSubmittedByTask(); } return null; } /** finds the first {@link EffectorCallTag} tag on this tag, or optionally on submitters, or null */ public static EffectorCallTag getEffectorCallTag(Task<?> task, boolean recurse) { Task<?> t = task; while (t!=null) { for (Object tag: t.getTags()) { if (tag instanceof EffectorCallTag) return (EffectorCallTag)tag; } if (!recurse) return null; t = t.getSubmittedByTask(); } return null; } /** finds the first {@link EffectorCallTag} tag on this tag or a submitter, and returns the effector name */ public static String getEffectorName(Task<?> task) { EffectorCallTag result = getEffectorCallTag(task, true); return (result == null) ? null : result.getEffectorName(); } public static ConfigBag getEffectorParameters(Task<?> task) { EffectorCallTag result = getEffectorCallTag(task, true); return (result == null) ? null : result.getParameters(); } public static ConfigBag getCurrentEffectorParameters() { return getEffectorParameters(Tasks.current()); } public static void setEffectorParameters(Task<?> task, ConfigBag parameters) { EffectorCallTag result = getEffectorCallTag(task, true); if (result == null) { throw new IllegalStateException("No EffectorCallTag found, is the task an effector? Task: " + task); } result.setParameters(parameters); } // ---------------- entitlement tags ---------------- public static class EntitlementTag { private EntitlementContext entitlementContext; } public static EntitlementContext getEntitlement(Task<?> task) { if (task==null) return null; return getEntitlement(task.getTags()); } public static EntitlementContext getEntitlement(Collection<?> tags) { if (tags==null) return null; for (Object tag: tags) { if (tag instanceof EntitlementTag) { return ((EntitlementTag)tag).entitlementContext; } } return null; } public static EntitlementContext getEntitlement(EntitlementTag tag) { if (tag==null) return null; return tag.entitlementContext; } public static EntitlementTag tagForEntitlement(EntitlementContext context) { EntitlementTag tag = new EntitlementTag(); tag.entitlementContext = context; return tag; } }