Repository: brooklyn-server Updated Branches: refs/heads/master 0f649fe17 -> 38396711d
BROOKLYN-433: regex/obj config key constraint in yaml Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/4bda12b7 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/4bda12b7 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/4bda12b7 Branch: refs/heads/master Commit: 4bda12b70ffc2146aa60ac0ab40de52b3b266a8d Parents: 0f649fe Author: Aled Sage <aled.s...@gmail.com> Authored: Fri Feb 3 13:30:09 2017 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Mon Feb 13 14:22:56 2017 +0000 ---------------------------------------------------------------------- .../camp/brooklyn/ConfigParametersYamlTest.java | 178 +++++++++++++++++++ .../brooklyn/core/objs/BasicSpecParameter.java | 69 +++++-- 2 files changed, 235 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4bda12b7/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java index d7040e5..fe602fe 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java @@ -22,6 +22,8 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Date; import java.util.List; import java.util.Map; @@ -31,6 +33,7 @@ import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.PortRange; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.ConstraintViolationException; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.location.PortRanges; import org.apache.brooklyn.core.sensor.Sensors; @@ -52,6 +55,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Joiner; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -750,6 +754,180 @@ public class ConfigParametersYamlTest extends AbstractYamlRebindTest { assertEquals(entity.sensors().get(Sensors.newSensor(Object.class, "my.param.key")), 1234); } + @Test + public void testConfigParameterConstraintRequired() throws Exception { + addCatalogItems( + "brooklyn.catalog:", + " itemType: entity", + " items:", + " - id: entity-with-keys", + " item:", + " type: "+TestEntity.class.getName(), + " brooklyn.parameters:", + " - name: testRequired", + " type: String", + " constraints:", + " - required"); + + String yamlNoVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys"); + + String yamlWithVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys", + " brooklyn.config:", + " testRequired: myval"); + + try { + createStartWaitAndLogApplication(yamlNoVal); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + // success + } + + Entity app = createStartWaitAndLogApplication(yamlWithVal); + TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + assertKeyEquals(entity, "testRequired", null, String.class, null, "myval"); + + // Rebind, and then check again that the config key is listed + Entity newApp = rebind(); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + assertKeyEquals(newEntity, "testRequired", null, String.class, null, "myval"); + } + + @Test + public void testConfigParameterConstraintRegex() throws Exception { + addCatalogItems( + "brooklyn.catalog:", + " itemType: entity", + " items:", + " - id: entity-with-keys", + " item:", + " type: "+TestEntity.class.getName(), + " brooklyn.parameters:", + " - name: testRequired", + " type: String", + " constraints:", + " - regex: myprefix.*"); + + String yamlNoVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys"); + + String yamlWrongVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys", + " brooklyn.config:", + " testRequired: wrongval"); + + String yamlWithVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys", + " brooklyn.config:", + " testRequired: myprefix-myVal"); + + try { + createStartWaitAndLogApplication(yamlNoVal); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + Asserts.expectedFailureContains(e, "matchesRegex"); // success + } + + try { + createStartWaitAndLogApplication(yamlWrongVal); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + Asserts.expectedFailureContains(e, "Invalid value for", "wrongval"); // success + } + + Entity app = createStartWaitAndLogApplication(yamlWithVal); + TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + assertKeyEquals(entity, "testRequired", null, String.class, null, "myprefix-myVal"); + + // Rebind, and then check again that the config key is listed + Entity newApp = rebind(); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + assertKeyEquals(newEntity, "testRequired", null, String.class, null, "myprefix-myVal"); + } + + @Test + public void testConfigParameterConstraintObject() throws Exception { + addCatalogItems( + "brooklyn.catalog:", + " itemType: entity", + " items:", + " - id: entity-with-keys", + " item:", + " type: "+TestEntity.class.getName(), + " brooklyn.parameters:", + " - name: testRequired", + " type: String", + " constraints:", + " - $brooklyn:object:", + " type: " + PredicateRegexPojo.class.getName(), + " object.fields:", + " regex: myprefix.*"); + + + String yamlNoVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys"); + + String yamlWrongVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys", + " brooklyn.config:", + " testRequired: wrongval"); + + String yamlWithVal = Joiner.on("\n").join( + "services:", + "- type: entity-with-keys", + " brooklyn.config:", + " testRequired: myprefix-myVal"); + + try { + createStartWaitAndLogApplication(yamlNoVal); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + Asserts.expectedFailureContains(e, "Error configuring", "PredicateRegexPojo(myprefix.*)"); // success + } + + try { + createStartWaitAndLogApplication(yamlWrongVal); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + Asserts.expectedFailureContains(e, "Invalid value for", "wrongval"); // success + } + + Entity app = createStartWaitAndLogApplication(yamlWithVal); + TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + assertKeyEquals(entity, "testRequired", null, String.class, null, "myprefix-myVal"); + + // Rebind, and then check again that the config key is listed + Entity newApp = rebind(); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + assertKeyEquals(newEntity, "testRequired", null, String.class, null, "myprefix-myVal"); + } + + public static class PredicateRegexPojo implements Predicate<Object> { + private String regex; + + public void setRegex(final String regex) { + this.regex = checkNotNull(regex, "regex"); + } + + @Override + public boolean apply(Object input) { + return (input instanceof String) && ((String)input).matches(regex); + } + + @Override + public String toString() { + return "PredicateRegexPojo("+regex+")"; + } + } + protected <T> void assertKeyEquals(Entity entity, String keyName, String expectedDescription, Class<T> expectedType, T expectedDefaultVal, T expectedEntityVal) { ConfigKey<?> key = entity.getEntityType().getConfigKey(keyName); assertNotNull(key, "No key '"+keyName+"'; keys="+entity.getEntityType().getConfigKeys()); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4bda12b7/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java index cc9d66a..d8c0522 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.objs; +import static com.google.common.base.Preconditions.checkArgument; + import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; @@ -56,6 +58,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.reflect.TypeToken; public class BasicSpecParameter<T> implements SpecParameter<T>{ @@ -203,7 +206,16 @@ public class BasicSpecParameter<T> implements SpecParameter<T>{ private static final Map<String, Predicate<?>> BUILT_IN_CONSTRAINTS = ImmutableMap.<String, Predicate<?>>of( "required", StringPredicates.isNonBlank()); - + + private static final Map<String, Function<Object, Predicate<?>>> BUILT_IN_CONSTRAINT_FACTORIES = ImmutableMap.<String, Function<Object, Predicate<?>>>of( + "regex", new Function<Object, Predicate<?>>() { + @Override public Predicate<?> apply(Object input) { + // TODO Could try to handle deferred supplier as well? + checkArgument(input instanceof String, "Constraint regex value must be a string, but got %s (%s)", + (input == null ? "null" : input.getClass().getName()), input); + return StringPredicates.matchesRegex((String)input); + }}); + public static List<SpecParameter<?>> parseParameters(List<?> inputsRaw, Function<Object, Object> specialFlagTransformer, BrooklynClassLoadingContext loader) { if (inputsRaw == null) return ImmutableList.of(); List<SpecParameter<?>> inputs = new ArrayList<>(inputsRaw.size()); @@ -279,25 +291,21 @@ public class BasicSpecParameter<T> implements SpecParameter<T>{ } @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Predicate parseConstraints(Object obj, BrooklynClassLoadingContext loader) { - List constraintsRaw; + private static Predicate<?> parseConstraints(Object obj, BrooklynClassLoadingContext loader) { + List<?> constraintsRaw; if (obj == null) { constraintsRaw = ImmutableList.of(); } else if (obj instanceof String) { constraintsRaw = ImmutableList.of(obj); } else if (obj instanceof List) { - constraintsRaw = (List) obj; + constraintsRaw = (List<?>) obj; } else { - throw new IllegalArgumentException ("The constraint '" + obj + "' for a catalog input is invalid format - string or list supported"); + throw new IllegalArgumentException ("The constraint '" + obj + "' for a catalog input is invalid format - " + + "string or list supported"); } - List<Predicate> constraints = new ArrayList(constraintsRaw.size()); + List<Predicate<?>> constraints = new ArrayList<>(constraintsRaw.size()); for (Object untypedConstraint : constraintsRaw) { - String constraint = (String)untypedConstraint; - if (BUILT_IN_CONSTRAINTS.containsKey(constraint)) { - constraints.add(BUILT_IN_CONSTRAINTS.get(constraint)); - } else { - throw new IllegalArgumentException("The constraint '" + constraint + "' for a catalog input is not recognized as a built-in (" + BUILT_IN_CONSTRAINTS.keySet() + ")"); - } + constraints.add(parseConstraint(untypedConstraint, loader)); } if (!constraints.isEmpty()) { if (constraints.size() == 1) { @@ -310,6 +318,43 @@ public class BasicSpecParameter<T> implements SpecParameter<T>{ } } + private static Predicate<?> parseConstraint(Object untypedConstraint, BrooklynClassLoadingContext loader) { + // TODO Could try to handle deferred supplier as well? + if (untypedConstraint instanceof Predicate) { + // An explicit predicate (e.g. via "$brooklyn:object: ...") + return (Predicate<?>) untypedConstraint; + } else if (untypedConstraint instanceof String) { + // build-in simple declaration, such as "required" + String constraint = (String)untypedConstraint; + if (BUILT_IN_CONSTRAINTS.containsKey(constraint)) { + return BUILT_IN_CONSTRAINTS.get(constraint); + } else { + throw new IllegalArgumentException("The constraint '" + constraint + "' for a catalog input is not " + + "recognized as a built-in (" + BUILT_IN_CONSTRAINTS.keySet() + " or " + + BUILT_IN_CONSTRAINT_FACTORIES.keySet() + ")"); + } + } else if (untypedConstraint instanceof Map) { + // For example "regex: foo.*" + Map<?,?> constraint = (Map<?,?>)untypedConstraint; + if (constraint.size() == 1) { + Object key = Iterables.getOnlyElement(constraint.keySet()); + Object val = constraint.get(key); + if (BUILT_IN_CONSTRAINT_FACTORIES.containsKey(key)) { + Function<Object, Predicate<?>> factory = BUILT_IN_CONSTRAINT_FACTORIES.get(key); + return factory.apply(val); + } else { + throw new IllegalArgumentException("The constraint '" + constraint + "' for a catalog input is not " + + "recognized as a built-in (" + BUILT_IN_CONSTRAINTS.keySet() + ")"); + } + } else { + throw new IllegalArgumentException("The config key constraint '" + constraint + "' is not supported - " + + "it can handle only single key:value constraint."); + } + } else { + throw new IllegalArgumentException("The constraint '" + untypedConstraint + "' for a catalog input is not recognized"); + } + } + private static ConfigInheritance parseInheritance(Object obj, BrooklynClassLoadingContext loader) { if (obj == null || obj instanceof String) { // TODO