You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sj...@apache.org on 2015/10/14 15:55:50 UTC

[04/17] incubator-brooklyn git commit: Config constraint improvements

Config constraint improvements

* Entity descendants are checked at management
* Adds ConfigKey#isValueValid(T)
* Incorporates into ApplicationResource#create


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/7a728b48
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/7a728b48
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/7a728b48

Branch: refs/heads/master
Commit: 7a728b48ce327692320e345193393bbe6edcba2a
Parents: 8ff866b
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Sep 11 13:47:26 2015 +0100
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Thu Oct 8 17:51:27 2015 +0100

----------------------------------------------------------------------
 .../brooklyn/core/config/BasicConfigKey.java    | 24 +++++++-
 .../brooklyn/core/config/ConfigConstraints.java | 39 +++++++++---
 .../config/ConstraintViolationException.java    | 38 ++++++++++++
 .../mgmt/internal/EntityManagementSupport.java  |  5 +-
 .../core/mgmt/internal/LocalEntityManager.java  | 14 ++---
 .../core/objs/proxy/InternalEntityFactory.java  | 37 +++++++++---
 .../core/config/ConfigKeyConstraintTest.java    | 63 ++++++++++++++++++--
 .../rest/resources/ApplicationResource.java     |  8 ++-
 .../org/apache/brooklyn/config/ConfigKey.java   |  9 ++-
 9 files changed, 196 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
index 9056eac..fe5e064 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
@@ -88,7 +88,7 @@ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializab
         private String description;
         private T defaultValue;
         private boolean reconfigurable;
-        private Predicate<? super T> constraint;
+        private Predicate<? super T> constraint = Predicates.alwaysTrue();
         private ConfigInheritance inheritance;
         
         public Builder<T> name(String val) {
@@ -173,9 +173,12 @@ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializab
         this.defaultValue = builder.defaultValue;
         this.reconfigurable = builder.reconfigurable;
         this.inheritance = builder.inheritance;
-        this.constraint = builder.constraint;
+        // Note: it's intentionally possible to have default values that are not valid
+        // per the configured constraint. If validity were checked here any class that
+        // contained a weirdly-defined config key would fail to initialise.
+        this.constraint = checkNotNull(builder.constraint, "constraint");
     }
-    
+
     /** @see ConfigKey#getName() */
     @Override public String getName() { return name; }
 
@@ -199,21 +202,36 @@ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializab
         return defaultValue != null;
     }
 
+    /** @see ConfigKey#isReconfigurable() */
     @Override
     public boolean isReconfigurable() {
         return reconfigurable;
     }
     
+    /** @see ConfigKey#getInheritance() */
     @Override @Nullable
     public ConfigInheritance getInheritance() {
         return inheritance;
     }
 
+    /** @see ConfigKey#getConstraint() */
     @Override @Nonnull
     public Predicate<? super T> getConstraint() {
         return constraint;
     }
 
+    /** @see ConfigKey#isValueValid(T) */
+    @Override
+    public boolean isValueValid(T value) {
+        // The likeliest source of an exception is a constraint from Guava that expects a non-null input.
+        try {
+            return getConstraint().apply(value);
+        } catch (Exception e) {
+            log.debug("Suppressing exception when testing validity of " + this, e);
+            return false;
+        }
+    }
+
     /** @see ConfigKey#getNameParts() */
     @Override public Collection<String> getNameParts() {
         return Lists.newArrayList(dots.split(name));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java b/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
index ce6f39b..24142a5 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
@@ -19,6 +19,7 @@
 
 package org.apache.brooklyn.core.config;
 
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.brooklyn.api.entity.Entity;
@@ -42,21 +43,47 @@ public abstract class ConfigConstraints<T extends BrooklynObject> {
 
     /**
      * Checks all constraints of all config keys available to an entity.
+     * <p>
+     * If a constraint is a {@link BrooklynObjectAwarePredicate} then it will be
+     * informed of the entity before the predicate is tested.
      */
     public static void assertValid(Entity entity) {
         Iterable<ConfigKey<?>> violations = new EntityConfigConstraints(entity).getViolations();
         if (!Iterables.isEmpty(violations)) {
-            throw new ConstraintViolationException("ConfigKeys violate constraints: " + violations);
+            throw new ConstraintViolationException(errorMessage(entity, violations));
         }
     }
 
+    /**
+     * Checks all constraints of all config keys available to an entity adjunct.
+     * <p>
+     * If a constraint is a {@link BrooklynObjectAwarePredicate} then it will be
+     * informed of the adjunct before the predicate is tested.
+     */
     public static void assertValid(EntityAdjunct adjunct) {
         Iterable<ConfigKey<?>> violations = new EntityAdjunctConstraints(adjunct).getViolations();
         if (!Iterables.isEmpty(violations)) {
-            throw new ConstraintViolationException("ConfigKeys violate constraints: " + violations);
+            throw new ConstraintViolationException(errorMessage(adjunct, violations));
         }
     }
 
+    private static String errorMessage(BrooklynObject object, Iterable<ConfigKey<?>> violations) {
+        StringBuilder message = new StringBuilder("Error configuring ")
+                .append(object.getDisplayName())
+                .append(": [");
+        Iterator<ConfigKey<?>> it = violations.iterator();
+        while (it.hasNext()) {
+            ConfigKey<?> config = it.next();
+            message.append(config.getName())
+                    .append(":")
+                    .append(config.getConstraint());
+            if (it.hasNext()) {
+                message.append(", ");
+            }
+        }
+        return message.append("]").toString();
+    }
+
     public ConfigConstraints(T brooklynObject) {
         this.brooklynObject = brooklynObject;
     }
@@ -73,7 +100,7 @@ public abstract class ConfigConstraints<T extends BrooklynObject> {
 
     @SuppressWarnings("unchecked")
     private Iterable<ConfigKey<?>> validateAll() {
-        List<ConfigKey<?>> violating = Lists.newArrayList();
+        List<ConfigKey<?>> violating = Lists.newLinkedList();
         BrooklynObjectInternal.ConfigurationSupportInternal configInternal = getConfigurationSupportInternal();
 
         Iterable<ConfigKey<?>> configKeys = getBrooklynObjectTypeConfigKeys();
@@ -128,10 +155,4 @@ public abstract class ConfigConstraints<T extends BrooklynObject> {
         }
     }
 
-    public static class ConstraintViolationException extends RuntimeException {
-        public ConstraintViolationException(String message) {
-            super(message);
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/config/ConstraintViolationException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ConstraintViolationException.java b/core/src/main/java/org/apache/brooklyn/core/config/ConstraintViolationException.java
new file mode 100644
index 0000000..55c7f07
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/config/ConstraintViolationException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.config;
+
+/**
+ * A {@link ConstraintViolationException} indicates one or more problems applying
+ * values for {@link org.apache.brooklyn.config.ConfigKey ConfigKeys} when creating
+ * a {@link org.apache.brooklyn.api.objs.BrooklynObject}.
+ */
+public class ConstraintViolationException extends RuntimeException {
+    private static final long serialVersionUID = -6719912119648996815L;
+
+    public ConstraintViolationException(String message) {
+        super(message);
+    }
+
+    public ConstraintViolationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagementSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagementSupport.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagementSupport.java
index c7d3d04..6b61bf6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagementSupport.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagementSupport.java
@@ -65,7 +65,7 @@ import com.google.common.base.Stopwatch;
  * <p>
  * on unmanage it hits onManagementStoppingHere() then onManagementStopping().
  * <p>
- * When an entity's management migrates, it invoked onManagementStoppingHere() at the old location,
+ * When an entity's management migrates, it invokes onManagementStoppingHere() at the old location,
  * then onManagementStartingHere() at the new location.
  */
 public class EntityManagementSupport {
@@ -85,9 +85,6 @@ public class EntityManagementSupport {
     protected transient SubscriptionContext subscriptionContext;
     protected transient ExecutionContext executionContext;
     
-    // TODO the application
-    // (elaborate or remove ^^^ ? -AH, Sept 2014)
-    
     protected final AtomicBoolean managementContextUsable = new AtomicBoolean(false);
     protected final AtomicBoolean currentlyDeployed = new AtomicBoolean(false);
     protected final AtomicBoolean everDeployed = new AtomicBoolean(false);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
index f1fde20..6c9022d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
@@ -44,7 +44,6 @@ import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.Enricher;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.core.BrooklynLogging;
-import org.apache.brooklyn.core.config.ConfigConstraints;
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
@@ -269,8 +268,6 @@ public class LocalEntityManager implements EntityManagerInternal {
                     new Exception("source of duplicate management of "+e));
             return;
         }
-        ConfigConstraints.assertValid(e);
-
         manageRecursive(e, ManagementTransitionMode.guessing(BrooklynObjectManagementMode.NONEXISTENT, BrooklynObjectManagementMode.MANAGED_PRIMARY));
     }
 
@@ -311,7 +308,7 @@ public class LocalEntityManager implements EntityManagerInternal {
     protected void manageRecursive(Entity e, final ManagementTransitionMode initialMode) {
         checkManagementAllowed(e);
 
-        final List<EntityInternal> allEntities =  Lists.newArrayList();
+        final List<EntityInternal> allEntities = Lists.newArrayList();
         Predicate<EntityInternal> manageEntity = new Predicate<EntityInternal>() { public boolean apply(EntityInternal it) {
             ManagementTransitionMode mode = getLastManagementTransitionMode(it.getId());
             if (mode==null) {
@@ -390,7 +387,7 @@ public class LocalEntityManager implements EntityManagerInternal {
             }
         }
     }
-    
+
     @Override
     public void unmanage(final Entity e) {
         unmanage(e, ManagementTransitionMode.guessing(BrooklynObjectManagementMode.MANAGED_PRIMARY, BrooklynObjectManagementMode.NONEXISTENT));
@@ -600,7 +597,7 @@ public class LocalEntityManager implements EntityManagerInternal {
     /**
      * Should ensure that the entity is now known about, but should not be accessible from other entities yet.
      * 
-     * Records that the given entity is about to be managed (used for answering {@link isPreManaged(Entity)}.
+     * Records that the given entity is about to be managed (used for answering {@link #isPreManaged(Entity)}.
      * Note that refs to the given entity are stored in a a weak hashmap so if the subsequent management
      * attempt fails then this reference to the entity will eventually be discarded (if no-one else holds 
      * a reference).
@@ -628,7 +625,6 @@ public class LocalEntityManager implements EntityManagerInternal {
     /**
      * Should ensure that the entity is now managed somewhere, and known about in all the lists.
      * Returns true if the entity has now become managed; false if it was already managed (anything else throws exception)
-     * @param isOrWasReadOnly 
      */
     private synchronized boolean manageNonRecursive(Entity e, ManagementTransitionMode mode) {
         Entity old = entitiesById.get(e.getId());
@@ -641,8 +637,8 @@ public class LocalEntityManager implements EntityManagerInternal {
             }
             return false;
         }
-        
-        BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(e), 
+
+        BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(e),
             "{} starting management of entity {}", this, e);
         Entity realE = toRealEntity(e);
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
----------------------------------------------------------------------
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 f258cdc..9552583 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
@@ -24,6 +24,7 @@ import static com.google.common.base.Preconditions.checkState;
 import java.lang.reflect.InvocationTargetException;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
 
 import org.apache.brooklyn.api.entity.Entity;
@@ -37,6 +38,7 @@ import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.Enricher;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigConstraints;
 import org.apache.brooklyn.core.entity.AbstractApplication;
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Entities;
@@ -56,6 +58,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
 /**
@@ -258,6 +261,20 @@ public class InternalEntityFactory extends InternalFactory {
             throw Exceptions.propagate(e);
         }
     }
+
+    /**
+     * Calls {@link ConfigConstraints#assertValid(Entity)} on the given entity and all of
+     * its descendants.
+     */
+    private void validateDescendantConfig(Entity e) {
+        Queue<Entity> queue = Lists.newLinkedList();
+        queue.add(e);
+        while (!queue.isEmpty()) {
+            Entity e1 = queue.poll();
+            ConfigConstraints.assertValid(e1);
+            queue.addAll(e1.getChildren());
+        }
+    }
     
     protected <T extends Entity> void initEntityAndDescendants(String entityId, final Map<String,Entity> entitiesByEntityId, final Map<String,EntitySpec<?>> specsByEntityId) {
         final Entity entity = entitiesByEntityId.get(entityId);
@@ -269,7 +286,11 @@ public class InternalEntityFactory extends InternalFactory {
                 + "and thus it should be already fully initialized.");
             return;
         }
-        
+
+        // Validate all config before attempting to manage any entity. Do this here rather
+        // than in manageRecursive so that rebind is unaffected.
+        validateDescendantConfig(entity);
+
         /* 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 
          * (a) while it is running, the entity is parentless (and so not in the tree);
@@ -286,36 +307,36 @@ public class InternalEntityFactory extends InternalFactory {
             @Override
             public void run() {
                 ((AbstractEntity)entity).init();
-                
+
                 ((AbstractEntity)entity).addLocations(spec.getLocations());
 
                 for (EntityInitializer initializer: spec.getInitializers()) {
                     initializer.apply((EntityInternal)entity);
                 }
-                
+
                 for (Enricher enricher : spec.getEnrichers()) {
                     entity.enrichers().add(enricher);
                 }
-                
+
                 for (EnricherSpec<?> enricherSpec : spec.getEnricherSpecs()) {
                     entity.enrichers().add(policyFactory.createEnricher(enricherSpec));
                 }
-                
+
                 for (Policy policy : spec.getPolicies()) {
                     entity.policies().add((AbstractPolicy)policy);
                 }
-                
+
                 for (PolicySpec<?> policySpec : spec.getPolicySpecs()) {
                     entity.policies().add(policyFactory.createPolicy(policySpec));
                 }
-                                
+
                 for (Entity child: entity.getChildren()) {
                     // right now descendants are initialized depth-first (see the getUnchecked() call below)
                     // they could be done in parallel, but OTOH initializers should be very quick
                     initEntityAndDescendants(child.getId(), entitiesByEntityId, specsByEntityId);
                 }
             }
-        }).build()).getUnchecked();        
+        }).build()).getUnchecked();
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java b/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
index 6636944..6cf42ab 100644
--- a/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.brooklyn.core.config;
 
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.fail;
 
@@ -29,6 +30,7 @@ import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.enricher.AbstractEnricher;
+import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestEntity;
@@ -37,9 +39,11 @@ import org.apache.brooklyn.core.test.policy.TestPolicy;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.net.Networking;
 import org.testng.annotations.Test;
-import org.apache.brooklyn.core.config.ConfigConstraints.ConstraintViolationException;
 
+import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Range;
 
 public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
@@ -90,7 +94,7 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     public static class EntityProvidingDefaultValueForConfigKeyInRangeImpl extends TestEntityImpl implements EntityProvidingDefaultValueForConfigKeyInRange {
     }
 
-    public static class PolicyWithConfigConstraint extends AbstractPolicy{
+    public static class PolicyWithConfigConstraint extends AbstractPolicy {
         public static final ConfigKey<Object> NON_NULL_CONFIG = ConfigKeys.builder(Object.class)
                 .name("test.policy.non-null")
                 .description("Configuration key that must not be null")
@@ -106,7 +110,6 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
                 .build();
     }
 
-
     @Test
     public void testExceptionWhenEntityHasNullConfig() {
         try {
@@ -141,7 +144,7 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     }
 
     @Test
-    public void testExceptionIsThrownWhenUserSetsNullValueToConfigWithNonNullDefault() {
+    public void testExceptionIsThrownWhenUserNullsConfigWithNonNullDefault() {
         try {
             app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraintWithNonNullDefault.class)
                     .configure(EntityWithNonNullConstraintWithNonNullDefault.NON_NULL_WITH_DEFAULT, (Object) null));
@@ -152,6 +155,33 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         }
     }
 
+    @Test
+    public void testExceptionWhenValueSetByName() {
+        try {
+            app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class)
+                    .configure(ImmutableMap.of("test.conf.range", -1)));
+            fail("Expected exception when managing entity with invalid config");
+        } catch (Exception e) {
+            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
+            assertNotNull(t);
+        }
+    }
+
+    @Test
+    public void testExceptionWhenAppGrandchildHasInvalidConfig() {
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+        TestEntity testEntity = app.addChild(EntitySpec.create(TestEntity.class));
+        testEntity.addChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class)
+                .configure(EntityRequiringConfigKeyInRange.RANGE, -1));
+        try {
+            Entities.manage(testEntity);
+            fail("Expected exception when managing child with invalid config");
+        } catch (Exception e) {
+            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
+            assertNotNull(t);
+        }
+    }
+
     // Test fails because config keys that are not on an object's interfaces cannot be checked automatically.
     @Test(enabled = false)
     public void testExceptionWhenPolicyHasNullForeignConfig() {
@@ -167,7 +197,7 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     }
 
     @Test
-    public void testExceptionWhenPolicyHasNullConfig() {
+    public void testExceptionWhenPolicyHasInvalidConfig() {
         try {
             mgmt.getEntityManager().createPolicy(PolicySpec.create(PolicyWithConfigConstraint.class)
                     .configure(PolicyWithConfigConstraint.NON_NULL_CONFIG, (Object) null));
@@ -190,4 +220,27 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         }
     }
 
+    @Test
+    public void testDefaultValueDoesNotNeedToObeyConstraint() {
+        ConfigKeys.builder(String.class)
+                .name("foo")
+                .defaultValue("a")
+                .constraint(Predicates.equalTo("b"))
+                .build();
+    }
+
+    @Test
+    public void testIsValidWithBadlyBehavedPredicate() {
+        ConfigKey<String> key = ConfigKeys.builder(String.class)
+                .name("foo")
+                .constraint(new Predicate<String>() {
+                    @Override
+                    public boolean apply(String input) {
+                        throw new RuntimeException("It's my day off");
+                    }
+                })
+                .build();
+        assertFalse(key.isValueValid("abc"));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 33752b2..68a9198 100644
--- a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -45,6 +45,7 @@ import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.Sensor;
 import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.config.ConstraintViolationException;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.EntityPredicates;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
@@ -71,6 +72,7 @@ import org.apache.brooklyn.rest.util.WebResourceUtils;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.UserFacingException;
 import org.apache.brooklyn.util.text.Strings;
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.node.ArrayNode;
@@ -286,7 +288,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
         try {
             Application app = EntityManagementUtils.createUnstarted(mgmt(), spec);
             CreationResult<Application,Void> result = EntityManagementUtils.start(app);
-            
+
             boolean isEntitled = Entitlements.isEntitled(
                     mgmt().getEntitlementManager(),
                     Entitlements.INVOKE_EFFECTOR,
@@ -301,9 +303,11 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
 
             URI ref = URI.create(app.getApplicationId());
             ResponseBuilder response = created(ref);
-            if (result.task() != null) 
+            if (result.task() != null)
                 response.entity(TaskTransformer.FROM_TASK.apply(result.task()));
             return response.build();
+        } catch (ConstraintViolationException e) {
+            throw new UserFacingException(e);
         } catch (Exception e) {
             throw Exceptions.propagate(e);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a728b48/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
index 9dab87d..92f174f 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
@@ -89,12 +89,19 @@ public interface ConfigKey<T> {
     @Nullable ConfigInheritance getInheritance();
 
     /**
-     * @return the predicate constraining the key's value.
+     * @return The predicate constraining the key's value.
      */
     @Beta
     @Nonnull
     Predicate<? super T> getConstraint();
 
+    /**
+     * @param value The value to test
+     * @return True if the given value is acceptable per the {@link #getConstraint constraints} on this key.
+     */
+    @Beta
+    boolean isValueValid(T value);
+
     /** Interface for elements which want to be treated as a config key without actually being one
      * (e.g. config attribute sensors).
      */