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());
+    }
 }

Reply via email to