This is an automated email from the ASF dual-hosted git repository. tbouron pushed a commit to branch feature/default-initializers in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit 856f033034f5f39006844c599c46a3cc1b6f0406 Author: Thomas Bouron <[email protected]> AuthorDate: Wed Jul 14 13:41:19 2021 +0100 Add support setup default initializers for all deployment This looks up a new configuration options called `brooklyn.deployment.initializers` (comma separated list). If specified on a Brooklyn instance, all deployments will load and execute these initializers. Theses classes are expected to be `EntityInitializer`, if an error occur (either cast or anything else) then the deployment will fail. The code will try to: 1. load the class from the default class loader. 2. if (1) fails, it will try to load the class from the `TypeRegistry`. This is to allow execution of custom initializers that might be installed in the catalog later on. 3. if (1) and (2) fails, then the deployment is aborted. --- .../core/effector/AddDeploySensorsInitializer.java | 54 ++++++++++++ .../core/objs/proxy/InternalEntityFactory.java | 97 +++++++++++++++------- 2 files changed, 121 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddDeploySensorsInitializer.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddDeploySensorsInitializer.java new file mode 100644 index 0000000..0285cd0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddDeploySensorsInitializer.java @@ -0,0 +1,54 @@ +/* + * 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.effector; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import org.apache.brooklyn.api.entity.EntityInitializer; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.sensor.Sensors; + +public class AddDeploySensorsInitializer implements EntityInitializer { + @Override + public void apply(EntityLocal entity) { + // We want to set the metadata only on the root node of an application + if (entity.getParent() != null) { + return; + } + EntitlementContext entitlementContext = Entitlements.getEntitlementContext(); + AttributeSensor<String> sensor = Sensors.newSensor( + String.class, + "deployment.metadata", + "Metadata information about this particular deployment. Contains at least who triggered it and when."); + ((EntityInternal) entity).getMutableEntityType().addSensor(sensor); + try { + entity.sensors().set(sensor, new ObjectMapper().writeValueAsString(ImmutableMap.of( + "user", entitlementContext != null ? entitlementContext.user() : "Unknown", + "deploy_time", System.currentTimeMillis() + ))); + } catch (JsonProcessingException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java index cf8821b..7b92138 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java @@ -31,8 +31,10 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.objs.SpecParameter; import org.apache.brooklyn.api.policy.PolicySpec; import org.apache.brooklyn.api.sensor.EnricherSpec; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigConstraints; +import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConstraintViolationException; import org.apache.brooklyn.core.entity.*; import org.apache.brooklyn.core.mgmt.BrooklynTags; @@ -46,46 +48,59 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.AggregateClassLoader; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; /** - * Creates entities (and proxies) of required types, given the - * + * Creates entities (and proxies) of required types, given the + * * This is an internal class for use by core-brooklyn. End-users are strongly discouraged from * using this class directly. - * + * * Used in three situations: * <ul> * <li>Normal entity creation (through entityManager.createEntity) * <li>rebind (i.e. Brooklyn restart, or promotion of HA standby manager node) * <li>yaml parsing * </ul> - * + * * @author aled */ public class InternalEntityFactory extends InternalFactory { private static final Logger log = LoggerFactory.getLogger(InternalEntityFactory.class); - + private final EntityTypeRegistry entityTypeRegistry; private final InternalPolicyFactory policyFactory; private final ClassLoaderCache classLoaderCache; - + + /** + * The initializers to be add to any application deployed by Brooklyn. + * e.g. <code>brooklyn.deployment.initializers=org.apache.brooklyn.core.effector.AddDeploySensorsInitializer</code> + * will automatically add tags to the root node of any deployed application. + */ + public final static ConfigKey<String> DEFAULT_INITIALIZERS_CLASSNAMES = ConfigKeys.newStringConfigKey( + "brooklyn.deployment.initializers", + "Comma separated list of class names corresponding to Brooklyn Initializers to be automatically added and ran on every application deployed", + ""); + public InternalEntityFactory(ManagementContextInternal managementContext, EntityTypeRegistry entityTypeRegistry, InternalPolicyFactory policyFactory) { super(managementContext); this.entityTypeRegistry = checkNotNull(entityTypeRegistry, "entityTypeRegistry"); @@ -103,7 +118,7 @@ public class InternalEntityFactory extends InternalFactory { interfaces.addAll(Reflections.getAllInterfaces(spec.getType())); } interfaces.addAll(spec.getAdditionalInterfaces()); - + return createEntityProxy(interfaces, entity); } @@ -151,7 +166,7 @@ public class InternalEntityFactory extends InternalFactory { /** creates a new entity instance from a spec, with all children, policies, etc, * fully initialized ({@link AbstractEntity#init()} invoked) and ready for - * management -- commonly the caller will next call + * management -- commonly the caller will next call * {@link Entities#manage(Entity)} (if it's in a managed application) * or {@link Entities#startManagement(org.apache.brooklyn.api.entity.Application, org.apache.brooklyn.api.mgmt.ManagementContext)} * (if it's an application) */ @@ -162,7 +177,7 @@ public class InternalEntityFactory extends InternalFactory { * It seems we need access to the parent (indeed the root application) when running some initializers (esp children initializers). * <p> * Now we do two passes, so that hierarchy is fully populated before initialization and policies. - * (This is needed where some config or initializer might reference another entity by its ID, e.g. yaml $brooklyn:component("id"). + * (This is needed where some config or initializer might reference another entity by its ID, e.g. yaml $brooklyn:component("id"). * Initialization is done in parent-first order with depth-first children traversal. */ @@ -171,7 +186,7 @@ public class InternalEntityFactory extends InternalFactory { // (maps needed because we need the spec, and we need to keep the AbstractEntity to call init, not a proxy) Map<String,Entity> entitiesByEntityId = MutableMap.of(); Map<String,EntitySpec<?>> specsByEntityId = MutableMap.of(); - + T entity = createEntityAndDescendantsUninitialized(0, spec, options, entitiesByEntityId, specsByEntityId); try { initEntityAndDescendants(entity.getId(), entitiesByEntityId, specsByEntityId, options); @@ -190,7 +205,7 @@ public class InternalEntityFactory extends InternalFactory { } return entity; } - + private <T extends Entity> T createEntityAndDescendantsUninitialized(int depth, EntitySpec<T> spec, EntityManager.EntityCreationOptions options, Map<String,Entity> entitiesByEntityId, Map<String,EntitySpec<?>> specsByEntityId) { @@ -205,11 +220,11 @@ public class InternalEntityFactory extends InternalFactory { } Class<? extends T> clazz = getImplementedBy(spec); - + entity = constructEntity(clazz, spec, depth==0 ? options.getRequiredUniqueId() : null); - + loadUnitializedEntity(entity, spec, options); - + List<NamedStringTag> upgradedFrom = BrooklynTags.findAllNamedStringTags(BrooklynTags.UPGRADED_FROM, spec.getTags()); if (!upgradedFrom.isEmpty()) { log.warn("Entity "+entity.getId()+" created with upgraded type "+entity.getCatalogItemId()+" "+upgradedFrom+" (in "+entity.getApplicationId()+", under "+entity.getParent()+")"); @@ -230,7 +245,7 @@ public class InternalEntityFactory extends InternalFactory { Entity child = createEntityAndDescendantsUninitialized(depth+1, childSpec, options, entitiesByEntityId, specsByEntityId); entity.addChild(child); } - + for (Entity member: spec.getMembers()) { if (!(entity instanceof Group)) { throw new IllegalStateException("Entity "+entity+" must be a group to add members "+spec.getMembers()); @@ -243,13 +258,13 @@ public class InternalEntityFactory extends InternalFactory { } return entity; - + } catch (Exception ex) { options.onException(ex, Exceptions::propagate); return entity; } } - + @SuppressWarnings({ "unchecked", "rawtypes" }) protected <T extends Entity> T loadUnitializedEntity(final T entity, final EntitySpec<T> spec, EntityManager.EntityCreationOptions options) { try { @@ -321,11 +336,11 @@ public class InternalEntityFactory extends InternalFactory { queue.addAll(e1.getChildren()); } } - + protected <T extends Entity> void initEntityAndDescendants(String entityId, final Map<String,Entity> entitiesByEntityId, final Map<String,EntitySpec<?>> specsByEntityId, EntityManager.EntityCreationOptions options) { final Entity entity = entitiesByEntityId.get(entityId); final EntitySpec<?> spec = specsByEntityId.get(entityId); - + if (entity==null || spec==null) { log.debug("Skipping initialization of "+entityId+" found as child of entity being initialized, " + "but this child is not one we created; likely it was created by an initializer, " @@ -338,17 +353,17 @@ public class InternalEntityFactory extends InternalFactory { validateDescendantConfig(entity, options); /* Marked transient so that the task is not needlessly kept around at the highest level. - * Note that the task is not normally visible in the GUI, because + * Note that the task is not normally visible in the GUI, because * (a) while it is running, the entity is often parentless (and so not in the tree); * and (b) when it is completed it is GC'd, as it is transient. * However task info is available via the API if you know its ID, - * and if better subtask querying is available it will be picked up as a background task + * and if better subtask querying is available it will be picked up as a background task * of the parent entity creating this child entity * (note however such subtasks are currently filtered based on parent entity so is excluded). * <p> * Some of these (initializers and enrichers) submit background scheduled tasks, * which currently show up at the top level once the initializer task completes. - * TODO It would be nice if these schedule tasks were grouped in a bucket! + * TODO It would be nice if these schedule tasks were grouped in a bucket! */ ((EntityInternal)entity).getExecutionContext().get(Tasks.builder().dynamic(false).displayName("Entity initialization") .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) @@ -369,6 +384,12 @@ public class InternalEntityFactory extends InternalFactory { } ((AbstractEntity)entity).addLocations(spec.getLocations()); + List<EntityInitializer> initializers = Stream.concat(spec.getInitializers().stream(), getDefaultInitializers().stream()) + .collect(Collectors.toList()); + for (EntityInitializer initializer: initializers) { + initializer.apply((EntityInternal)entity); + } + for (EntityInitializer initializer: spec.getInitializers()) { initializer.apply((EntityInternal)entity); } @@ -393,16 +414,16 @@ public class InternalEntityFactory extends InternalFactory { } }).build()); } - + /** * Constructs an entity, i.e. instantiate the actual class given a spec, * and sets the entity's proxy. Used by this factory to {@link #createEntity(EntitySpec, org.apache.brooklyn.api.mgmt.EntityManager.EntityCreationOptions)} * and also used during rebind. - * <p> + * <p> * If the entityId is provided, then uses that to override the entity's id, * but that behaviour is deprecated. * <p> - * The new-style no-arg constructor is preferred, and + * The new-style no-arg constructor is preferred, and * configuration from the {@link EntitySpec} is <b>not</b> normally applied, * although for old-style entities flags from the spec are passed to the constructor. * <p> @@ -426,7 +447,7 @@ public class InternalEntityFactory extends InternalFactory { } checkNotNull(entityId, "entityId"); checkState(interfaces != null && !Iterables.isEmpty(interfaces), "must have at least one interface for entity %s:%s", clazz, entityId); - + T entity = constructEntityImpl(clazz, null, null, entityId); if (((AbstractEntity)entity).getProxy() == null) { Entity proxy = managementContext.getEntityManager().getEntity(entity.getId()); @@ -442,10 +463,10 @@ public class InternalEntityFactory extends InternalFactory { return entity; } - private <T extends Entity> T constructEntityImpl(Class<? extends T> clazz, EntitySpec<?> optionalSpec, + private <T extends Entity> T constructEntityImpl(Class<? extends T> clazz, EntitySpec<?> optionalSpec, Map<String, ?> optionalConstructorFlags, String entityId) { T entity = construct(clazz, optionalSpec, optionalConstructorFlags); - + if (entityId!=null) { FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity); } @@ -465,7 +486,7 @@ public class InternalEntityFactory extends InternalFactory { } return super.constructOldStyle(clazz, flags); } - + private <T extends Entity> Class<? extends T> getImplementedBy(EntitySpec<T> spec) { if (spec.getImplementation() != null) { return spec.getImplementation(); @@ -473,4 +494,20 @@ public class InternalEntityFactory extends InternalFactory { return entityTypeRegistry.getImplementedBy(spec.getType()); } } + + private List<EntityInitializer> getDefaultInitializers() { + return Arrays.stream(managementContext.getConfig().getConfig(DEFAULT_INITIALIZERS_CLASSNAMES).split(",")) + .map(clazz -> { + try { + Class<?> initializerClass = getClass().getClassLoader().loadClass(clazz); + return (EntityInitializer) initializerClass.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + RegisteredType registeredType = managementContext.getTypeRegistry() + .getMaybe(clazz, null) + .orThrow("Failed to load default initializer with class name: " + clazz); + return managementContext.getTypeRegistry().create(registeredType, null, null); + } + }) + .collect(Collectors.toList()); + } }
