| /* |
| * 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; |
| |
| import static org.testng.Assert.assertFalse; |
| import static org.testng.Assert.assertNotNull; |
| import static org.testng.Assert.fail; |
| |
| import java.util.concurrent.Callable; |
| |
| import org.apache.brooklyn.api.entity.EntitySpec; |
| import org.apache.brooklyn.api.entity.ImplementedBy; |
| import org.apache.brooklyn.api.location.Location; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.api.objs.BrooklynObject; |
| import org.apache.brooklyn.api.policy.Policy; |
| 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.BrooklynConfigKeys; |
| import org.apache.brooklyn.core.objs.BrooklynObjectPredicate; |
| import org.apache.brooklyn.core.policy.AbstractPolicy; |
| import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; |
| import org.apache.brooklyn.core.test.entity.TestApplication; |
| import org.apache.brooklyn.core.test.entity.TestEntity; |
| import org.apache.brooklyn.core.test.entity.TestEntityImpl; |
| import org.apache.brooklyn.core.test.policy.TestPolicy; |
| import org.apache.brooklyn.test.Asserts; |
| import org.apache.brooklyn.util.core.task.Tasks; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.javalang.JavaClassNames; |
| import org.apache.brooklyn.util.net.Networking; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.apache.brooklyn.util.time.Time; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.testng.annotations.DataProvider; |
| import org.testng.annotations.Test; |
| |
| 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 { |
| |
| private static final Logger log = LoggerFactory.getLogger(ConfigKeyConstraintTest.class); |
| |
| // ----------- Setup ----------------------------------------------------------------------------------------------- |
| |
| @ImplementedBy(EntityWithNonNullConstraintImpl.class) |
| public static interface EntityWithNonNullConstraint extends TestEntity { |
| ConfigKey<Object> NON_NULL_CONFIG = ConfigKeys.builder(Object.class) |
| .name("test.conf.non-null.without-default") |
| .description("Configuration key that must not be null") |
| .constraint(Predicates.notNull()) |
| .build(); |
| } |
| public static class EntityWithNonNullConstraintImpl extends TestEntityImpl implements EntityWithNonNullConstraint { |
| } |
| |
| @ImplementedBy(EntityWithNonNullConstraintWithNonNullDefaultImpl.class) |
| public static interface EntityWithNonNullConstraintWithNonNullDefault extends TestEntity { |
| ConfigKey<Object> NON_NULL_WITH_DEFAULT = ConfigKeys.builder(Object.class) |
| .name("test.conf.non-null.with-default") |
| .description("Configuration key that must not be null") |
| .defaultValue(new Object()) |
| .constraint(Predicates.notNull()) |
| .build(); |
| } |
| public static class EntityWithNonNullConstraintWithNonNullDefaultImpl extends TestEntityImpl implements EntityWithNonNullConstraintWithNonNullDefault { |
| } |
| |
| @ImplementedBy(EntityRequiringConfigKeyInRangeImpl.class) |
| public static interface EntityRequiringConfigKeyInRange extends TestEntity { |
| ConfigKey<Integer> RANGE = ConfigKeys.builder(Integer.class) |
| .name("test.conf.range") |
| .description("Configuration key that must not be between zero and nine") |
| .defaultValue(0) |
| .constraint(Range.closed(0, 9)) |
| .build(); |
| } |
| public static class EntityRequiringConfigKeyInRangeImpl extends TestEntityImpl implements EntityRequiringConfigKeyInRange { |
| } |
| |
| @ImplementedBy(EntityProvidingDefaultValueForConfigKeyInRangeImpl.class) |
| public static interface EntityProvidingDefaultValueForConfigKeyInRange extends EntityRequiringConfigKeyInRange { |
| ConfigKey<Integer> REVISED_RANGE = ConfigKeys.newConfigKeyWithDefault(RANGE, -1); |
| } |
| public static class EntityProvidingDefaultValueForConfigKeyInRangeImpl extends TestEntityImpl implements EntityProvidingDefaultValueForConfigKeyInRange { |
| } |
| |
| @ImplementedBy(EntityWithContextAwareConstraintImpl.class) |
| public static interface EntityWithContextAwareConstraint extends TestEntity { |
| ConfigKey<String> MUST_BE_DISPLAY_NAME = ConfigKeys.builder(String.class) |
| .name("must-be-display-name") |
| .description("Configuration key that must not be null") |
| .constraint(new MatchesEntityDisplayNamePredicate()) |
| .build(); |
| } |
| public static class EntityWithContextAwareConstraintImpl extends TestEntityImpl implements EntityWithContextAwareConstraint { |
| } |
| |
| 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") |
| .constraint(Predicates.notNull()) |
| .build(); |
| } |
| |
| public static class EnricherWithConfigConstraint extends AbstractEnricher { |
| public static final ConfigKey<String> PATTERN = ConfigKeys.builder(String.class) |
| .name("test.enricher.regex") |
| .description("Must match a valid IPv4 address") |
| .constraint(Predicates.containsPattern(Networking.VALID_IP_ADDRESS_REGEX)) |
| .build(); |
| } |
| |
| private static class MatchesEntityDisplayNamePredicate implements BrooklynObjectPredicate<String> { |
| @Override |
| public boolean apply(String input) { |
| return false; |
| } |
| |
| @Override |
| public boolean apply(String input, BrooklynObject context) { |
| return context != null && context.getDisplayName().equals(input); |
| } |
| } |
| |
| // ----------- Tests ----------------------------------------------------------------------------------------------- |
| |
| @Test |
| public void testExceptionWhenEntityHasNullConfig() { |
| try { |
| app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraint.class)); |
| fail("Expected exception when managing entity with missing config"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t); |
| } |
| } |
| |
| @Test |
| public void testNoExceptionWhenEntityHasValueForRequiredConfig() { |
| app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraint.class) |
| .configure(EntityWithNonNullConstraint.NON_NULL_CONFIG, new Object())); |
| } |
| |
| @Test |
| public void testNoExceptionWhenDefaultValueIsValid() { |
| app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class)); |
| } |
| |
| @Test |
| public void testExceptionWhenSubclassSetsInvalidDefaultValue() { |
| try { |
| app.createAndManageChild(EntitySpec.create(EntityProvidingDefaultValueForConfigKeyInRange.class)); |
| fail("Expected exception when managing entity setting invalid default value"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @Test |
| public void testExceptionIsThrownWhenUserNullsConfigWithNonNullDefault() { |
| try { |
| app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraintWithNonNullDefault.class) |
| .configure(EntityWithNonNullConstraintWithNonNullDefault.NON_NULL_WITH_DEFAULT, (Object) null)); |
| fail("Expected exception when config key set to null"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @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, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @Test |
| public void testExceptionWhenAppGrandchildHasInvalidConfig() { |
| app.start(ImmutableList.of(app.newSimulatedLocation())); |
| TestEntity testEntity = app.addChild(EntitySpec.create(TestEntity.class)); |
| try { |
| testEntity.addChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class) |
| .configure(EntityRequiringConfigKeyInRange.RANGE, -1)); |
| fail("Expected exception when managing child with invalid config"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| // Test fails because config keys that are not on an object's interfaces cannot be checked automatically. |
| @Test(enabled = false) |
| public void testExceptionWhenPolicyHasNullForeignConfig() { |
| Policy p = mgmt.getEntityManager().createPolicy(PolicySpec.create(TestPolicy.class) |
| .configure(EntityWithNonNullConstraint.NON_NULL_CONFIG, (Object) null)); |
| try { |
| ConfigConstraints.assertValid(p); |
| fail("Expected exception when validating policy with missing config"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @Test |
| public void testExceptionWhenPolicyHasInvalidConfig() { |
| try { |
| mgmt.getEntityManager().createPolicy(PolicySpec.create(PolicyWithConfigConstraint.class) |
| .configure(PolicyWithConfigConstraint.NON_NULL_CONFIG, (Object) null)); |
| fail("Expected exception when creating policy with missing config"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @Test |
| public void testExceptionWhenEnricherHasInvalidConfig() { |
| try { |
| mgmt.getEntityManager().createEnricher(EnricherSpec.create(EnricherWithConfigConstraint.class) |
| .configure(EnricherWithConfigConstraint.PATTERN, "123.123.256.10")); |
| fail("Expected exception when creating enricher with invalid config"); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| @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(); |
| // i.e. no exception. |
| assertFalse(key.isValueValid("abc")); |
| } |
| |
| @Test |
| public void testContextAwarePredicateInformedOfEntity() { |
| try { |
| app.createAndManageChild(EntitySpec.create(EntityWithContextAwareConstraint.class) |
| .displayName("Mr. Big") |
| .configure("must-be-display-name", "Mr. Bag")); |
| fail("Expected exception when managing entity with incorrect config"); |
| } catch (Exception e) { |
| Asserts.expectedFailureOfType(e, ConstraintViolationException.class); |
| } |
| } |
| |
| @Test |
| public void testQuickFutureResolved() { |
| // Result of task is -1, outside of the range specified by the config key. |
| try { |
| EntityRequiringConfigKeyInRange child = app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class) |
| .configure(EntityRequiringConfigKeyInRange.RANGE, sleepingTask(Duration.ZERO, -1))); |
| // may or may not fail above, depending on speed, but should fail if assert after forcing resolution |
| Object value = child.getConfig(EntityRequiringConfigKeyInRange.RANGE); |
| // NB the call above does not currently/necessarily apply validation |
| log.debug(JavaClassNames.niceClassAndMethod()+" got "+value+" for "+EntityRequiringConfigKeyInRange.RANGE+", now explicitly validating"); |
| ConfigConstraints.assertValid(child); |
| fail("Expected exception when managing entity with incorrect config; instead passed assertion and got: "+value); |
| } catch (Exception e) { |
| Asserts.expectedFailureOfType(e, ConstraintViolationException.class); |
| } |
| } |
| |
| @Test |
| public void testSlowFutureNotResolved() { |
| // i.e. no exception because task is too slow to resolve. |
| app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class) |
| .configure(EntityRequiringConfigKeyInRange.RANGE, sleepingTask(Duration.PRACTICALLY_FOREVER, -1))); |
| } |
| |
| private static <T> Task<T> sleepingTask(final Duration delay, final T result) { |
| return Tasks.<T>builder() |
| .body(new Callable<T>() { |
| @Override public T call() throws Exception { |
| Time.sleep(delay); |
| return result; |
| } |
| }) |
| .build(); |
| } |
| |
| // Supplies an entity, a policy and a location. |
| @DataProvider(name = "brooklynObjects") |
| public Object[][] createBrooklynObjects() throws Exception { |
| EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class) |
| .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution()); |
| setUp(); |
| TestApplication app = mgmt.getEntityManager().createEntity(appSpec); |
| |
| EntityRequiringConfigKeyInRange entity = app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class) |
| .configure(EntityRequiringConfigKeyInRange.RANGE, 5)); |
| Policy policy = entity.policies().add(PolicySpec.create(TestPolicy.class)); |
| Location location = app.newSimulatedLocation(); |
| return new Object[][]{{entity}, {policy}, {location}}; |
| } |
| |
| @Test(dataProvider = "brooklynObjects") |
| public void testCannotUpdateConfigToInvalidValue(BrooklynObject object) { |
| try { |
| object.config().set(EntityRequiringConfigKeyInRange.RANGE, -1); |
| fail("Expected exception when calling config().set with invalid value on " + object); |
| } catch (Exception e) { |
| Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class); |
| assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e)); |
| } |
| } |
| |
| } |