blob: 07d7059a433f14d30c369a9121e0f5b1863daf76 [file] [log] [blame]
/*
* 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.entity;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigMap.ConfigMapWithInheritance;
import org.apache.brooklyn.config.ConfigValueAtContainer;
import org.apache.brooklyn.core.config.BasicConfigInheritance;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl;
import org.apache.brooklyn.core.entity.EntityConfigTest.MyOtherEntity;
import org.apache.brooklyn.core.entity.EntityConfigTest.MyOtherEntityImpl;
import org.apache.brooklyn.core.entity.internal.EntityConfigMap;
import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
import org.apache.brooklyn.core.sensor.BasicAttributeSensorAndConfigKey.IntegerAttributeSensorAndConfigKey;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.entity.stock.BasicEntity;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.testng.Assert;
import org.testng.annotations.Test;
public class ConfigEntityInheritanceTest extends BrooklynAppUnitTestSupport {
protected void checkKeys(Entity entity2, Integer value) {
Assert.assertEquals(entity2.getConfig(MyOtherEntity.INT_KEY), value);
Assert.assertEquals(entity2.getConfig(MyOtherEntity.SENSOR_AND_CONFIG_KEY), value);
}
@Test
public void testConfigKeysIncludesHasConfigKeys() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MyOtherEntity.class)), 1);
}
@Test
public void testConfigKeysIncludesHasConfigKeysInheritsOverwritten() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MyOtherEntityOverwriting.class)), 2);
}
@Test
public void testConfigKeysIncludesHasConfigKeysInheritsOverwrittenThenInherited() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MyOtherEntityOverwritingThenInheriting.class)), 2);
}
public static class MyOtherEntityOverwriting extends MyOtherEntityImpl {
public static final ConfigKey<Integer> INT_KEY = ConfigKeys.newConfigKeyWithDefault(MyOtherEntity.INT_KEY, 2);
public static final IntegerAttributeSensorAndConfigKey SENSOR_AND_CONFIG_KEY =
new IntegerAttributeSensorAndConfigKey(MyOtherEntity.SENSOR_AND_CONFIG_KEY, 2);
}
public static class MyOtherEntityOverwritingThenInheriting extends MyOtherEntityOverwriting {
}
// --------------------
@Test
public void testConfigKeysHere() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MyEntityHere.class)), 3);
}
@Test
public void testConfigKeysSub() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MySubEntityHere.class)), 4);
}
@Test
public void testConfigKeysSubExtended() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MySubEntityHere.class)), 4);
}
@Test
public void testConfigKeysSubInheriting() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MySubEntityHereInheriting.class)), 4);
}
@Test
public void testConfigKeysHereSubRight() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MySubEntityHereLeft.class)), 4);
}
@Test
public void testConfigKeysSubLeft() throws Exception {
checkKeys(app.addChild(EntitySpec.create(MySubEntityHereRight.class)), 4);
}
@Test
public void testConfigKeysExtAndImplIntTwoRight() throws Exception {
// this mirrors the bug observed in kafka entities;
// the right-side interface normally dominates, but not when it is transitive
// (although we shouldn't rely on order in any case;
// new routines check whether one config key extends another and if so it takes the extending one)
checkKeys(app.addChild(EntitySpec.create(MyEntityHereExtendingAndImplementingInterfaceImplementingTwoRight.class)), 4);
}
public interface MyInterfaceDeclaring {
public static final ConfigKey<Integer> INT_KEY =
ConfigKeys.newIntegerConfigKey("intKey", "int key", 3);
public static final AttributeSensorAndConfigKey<Integer,Integer> SENSOR_AND_CONFIG_KEY =
new IntegerAttributeSensorAndConfigKey("sensorConfigKey", "sensor+config key", 3);
}
public interface MyInterfaceRedeclaringAndInheriting extends MyInterfaceDeclaring {
public static final ConfigKey<Integer> INT_KEY = ConfigKeys.newConfigKeyWithDefault(MyInterfaceDeclaring.INT_KEY, 4);
public static final IntegerAttributeSensorAndConfigKey SENSOR_AND_CONFIG_KEY =
new IntegerAttributeSensorAndConfigKey(MyInterfaceDeclaring.SENSOR_AND_CONFIG_KEY, 4);
}
public interface MyInterfaceRedeclaring {
public static final ConfigKey<Integer> INT_KEY = ConfigKeys.newConfigKeyWithDefault(MyInterfaceDeclaring.INT_KEY, 4);
public static final IntegerAttributeSensorAndConfigKey SENSOR_AND_CONFIG_KEY =
new IntegerAttributeSensorAndConfigKey(MyInterfaceDeclaring.SENSOR_AND_CONFIG_KEY, 4);
}
public interface MyInterfaceRedeclaringThenExtending extends MyInterfaceRedeclaring {
}
public interface MyInterfaceExtendingLeft extends MyInterfaceRedeclaring, MyInterfaceDeclaring {
}
public interface MyInterfaceExtendingRight extends MyInterfaceDeclaring, MyInterfaceRedeclaring {
}
public static class MyEntityHere extends AbstractEntity implements MyInterfaceDeclaring {
}
public static class MySubEntityHere extends MyEntityHere implements MyInterfaceRedeclaring {
}
public static class MySubEntityHereInheriting extends MyEntityHere implements MyInterfaceRedeclaringAndInheriting {
}
public static class MySubEntityHereExtended extends MyEntityHere implements MyInterfaceRedeclaringThenExtending {
}
public static class MySubEntityHereLeft extends MyEntityHere implements MyInterfaceRedeclaring, MyInterfaceDeclaring {
}
public static class MySubEntityHereRight extends MyEntityHere implements MyInterfaceDeclaring, MyInterfaceRedeclaring {
}
public static class MyEntityHereExtendingAndImplementingInterfaceImplementingTwoRight extends MyEntityHere implements MyInterfaceExtendingRight {
}
// --------------------
@Override
protected Boolean shouldSkipOnBoxBaseDirResolution() {
return null; //because it adds extra config we do not want to test for
}
protected Entity setupBasicInheritanceTest() {
app.config().set(MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT, "heritable");
app.config().set(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE, "always_heritable");
app.config().set(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT, "uninheritable");
app.config().set(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE, "maybe");
return app.addChild(EntitySpec.create(MyEntityWithPartiallyHeritableConfig.class));
}
private boolean isKeyInInternalConfigMapInherited(Entity entity, ConfigKey k) {
return isKeyInInternalConfigMapInherited(entity, k.getName());
}
private boolean isKeyInInternalConfigMapInherited(Entity entity, String s) {
Map<ConfigKey<?>, Object> cfgMap = ((EntityInternal) entity).config().getInternalConfigMap().getAllConfigInheritedRawValuesIgnoringErrors();
Set<String> cfgs = cfgMap.keySet().stream().map(k -> k.getName()).collect(Collectors.toSet());
return cfgs.contains(s);
}
@Test
public void testConfigKeysInheritance() throws Exception {
ConfigKey<Object> WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE = ConfigKeys.builder(Object.class, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT.getName()).build();
Entity child = setupBasicInheritanceTest();
Assert.assertNotNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT));
Assert.assertNotNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE));
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Assert.assertNull(child.getConfig(WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE));
// it's reinheritable unless explicitly declared
Assert.assertNotNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
Asserts.assertTrue(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT));
Asserts.assertTrue(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Asserts.assertTrue(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
// if reinheritable is _declared_ at the app, it is null at the child
app.getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
// and not found
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
// and returns to previous state when reverted
app.getMutableEntityType().removeConfigKey(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
Assert.assertNotNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
Asserts.assertTrue(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE));
// whereas never inherit is not found at child whether declared or not on parent or child
app.getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
app.getMutableEntityType().removeConfigKey(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
((EntityInternal)child).getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Assert.assertNull(child.getConfig(WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE));
((EntityInternal)child).getMutableEntityType().removeConfigKey(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
// even if removed, if the key is set, it will keep that type
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
((EntityInternal) child).config().getInternalConfigMap().getAllConfigInheritedRawValuesIgnoringErrors();
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Assert.assertNull(child.getConfig(WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, WAS_NEVER_INHERIT_BUT_NOW_DEFAULT_INHERITANCE));
// redefining at descendant doesn't change visibility
ConfigKey<Object> WAS_NEVER_INHERIT_BUT_NOW_OVERWRITE_AT_CHILD = ConfigKeys.builder(Object.class, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT.getName())
.runtimeInheritance(BasicConfigInheritance.OVERWRITE).build();
((EntityInternal)child).getMutableEntityType().addConfigKey(WAS_NEVER_INHERIT_BUT_NOW_OVERWRITE_AT_CHILD);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
// (but the key is known, of course)
Asserts.assertTrue(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
((EntityInternal)child).getMutableEntityType().removeConfigKey(WAS_NEVER_INHERIT_BUT_NOW_OVERWRITE_AT_CHILD);
Assert.assertNull(child.getConfig(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
Asserts.assertFalse(isKeyInInternalConfigMapInherited(child, MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT));
}
@SuppressWarnings("unchecked")
@Test
public void testConfigKeysAllInheritanceMethods() throws Exception {
Entity child = setupBasicInheritanceTest();
Assert.assertEquals(((EntityInternal) child).config().getInternalConfigMap().getAllConfigLocalRaw(), MutableMap.of());
Assert.assertEquals(((EntityInternal) child).config().getInternalConfigMap().getAllConfigInheritedRawValuesIgnoringErrors(),
MutableMap.of().add(MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT, "heritable")
.add(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE, "always_heritable")
.add(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE, "maybe"));
Map<ConfigKey<?>, ?> rawReinherit = ((EntityInternal) child).config().getInternalConfigMap().getAllReinheritableConfigRaw();
Assert.assertEquals(rawReinherit.keySet(), MutableSet.of(
MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT,
MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE));
// check the hierarchical list of values is correct
List<ConfigValueAtContainer<Entity, ?>> rawHierarchy = ((ConfigMapWithInheritance<Entity>) ((EntityInternal) child).config().getInternalConfigMap()).
getConfigAllInheritedRaw(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE);
Assert.assertEquals(rawHierarchy.size(), 1);
Assert.assertEquals(Iterables.getOnlyElement(rawHierarchy).getContainer(), app);
rawHierarchy = ((ConfigMapWithInheritance<Entity>) ((EntityInternal) child).config().getInternalConfigMap()).
getConfigAllInheritedRaw(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
Assert.assertEquals(rawHierarchy, MutableList.of());
// and try with and without NOT_RE declared at the app
rawHierarchy = ((ConfigMapWithInheritance<Entity>) ((EntityInternal) child).config().getInternalConfigMap()).
getConfigAllInheritedRaw(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
Assert.assertEquals(rawHierarchy.size(), 1);
Assert.assertEquals(Iterables.getOnlyElement(rawHierarchy).getContainer(), app);
app.getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
rawHierarchy = ((ConfigMapWithInheritance<Entity>) ((EntityInternal) child).config().getInternalConfigMap()).
getConfigAllInheritedRaw(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
Assert.assertEquals(rawHierarchy, MutableList.of());
Asserts.assertEquals(((EntityInternal) child).config().getInternalConfigMap().getAllConfigInheritedRawValuesIgnoringErrors().keySet(),
MutableSet.of(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE, MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT));
}
@Test
public void testAllInheritanceOnAnonymousChild() {
Entity child0 = setupBasicInheritanceTest();
Entity child = app.createAndManageChild(EntitySpec.create(BasicEntity.class));
app.getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NEVER_INHERIT);
app.getMutableEntityType().addConfigKey(MyEntityWithPartiallyHeritableConfig.NOT_REINHERITABLE);
Asserts.assertEquals(((EntityInternal) child).config().getInternalConfigMap().getAllConfigInheritedRawValuesIgnoringErrors().keySet(),
MutableSet.of(MyEntityWithPartiallyHeritableConfig.ALWAYS_HERITABLE, MyEntityWithPartiallyHeritableConfig.HERITABLE_BY_DEFAULT));
}
public static class MyEntityWithPartiallyHeritableConfig extends AbstractEntity {
public static final ConfigKey<String> HERITABLE_BY_DEFAULT = ConfigKeys.builder(String.class, "herit.default").build();
public static final ConfigKey<String> NEVER_INHERIT = ConfigKeys.builder(String.class, "herit.never").runtimeInheritance(BasicConfigInheritance.NEVER_INHERITED).build();
public static final ConfigKey<String> NOT_REINHERITABLE = ConfigKeys.builder(String.class, "herit.not_re").runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED).build();
// i find a strange joy in words where the prefix "in-" does not mean not, like inflammable
public static final ConfigKey<String> ALWAYS_HERITABLE = ConfigKeys.builder(String.class, "herit.always").runtimeInheritance(BasicConfigInheritance.OVERWRITE).build();
}
public static class WeirdInheritanceCase {
public interface Y {
public static final ConfigKey<String> WHERE = ConfigKeys.newStringConfigKey("where", null, "y");
}
public interface X extends Y {}
public interface S {
public static final ConfigKey<String> WHERE = ConfigKeys.newStringConfigKey("where", null, "s");
}
public static class SI extends AbstractEntity implements S {}
public static class XI extends SI implements X {}
}
/** There is a special case reported (in the main javadoc of this class, before this commit) where:
* class XI extends SI implements X
* class SI implements S
* interface X extends Y
* config C is declared on S and overwritten at Y.
* <p>
* This was described as a bug. This case confirms it correctly get S.
* The description should read that C (aka WHERE) is declared at both S and Y.
* Its value should be read from the most proximal interface ie S.
* (At runtime it will generate warnings.)
*/
@Test
public void testWeirdInheritanceCase() {
Entity child = app.addChild(EntitySpec.create(Entity.class, WeirdInheritanceCase.XI.class));
Assert.assertEquals(child.getConfig(ConfigKeys.newStringConfigKey("where", null, "local")), "s");
}
}