blob: 1417f71d2b8d81e98d35b709d328d440306a323d [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.camp.brooklyn;
import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.entity.group.DynamicCluster;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
@Test
public class DslAndRebindYamlTest extends AbstractYamlTest {
private static final Logger log = LoggerFactory.getLogger(DslAndRebindYamlTest.class);
protected ClassLoader classLoader = getClass().getClassLoader();
protected File mementoDir;
protected Set<ManagementContext> mgmtContexts = MutableSet.of();
protected ExecutorService executor;
@Override
protected LocalManagementContext newTestManagementContext() {
if (mementoDir != null) throw new IllegalStateException("already created mgmt context");
mementoDir = Files.createTempDir();
mementoDir.deleteOnExit();
LocalManagementContext mgmt = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
mgmtContexts.add(mgmt);
return mgmt;
}
@BeforeMethod(alwaysRun = true)
@Override
public void setUp() {
super.setUp();
executor = Executors.newSingleThreadExecutor();
}
@AfterMethod(alwaysRun = true)
@Override
public void tearDown() {
if (executor != null) executor.shutdownNow();
for (ManagementContext mgmt : mgmtContexts) Entities.destroyAll(mgmt);
super.tearDown();
mementoDir = null;
mgmtContexts.clear();
}
@Override
protected Logger getLogger() {
return log;
}
public Application rebind(Application app) throws Exception {
RebindTestUtils.waitForPersisted(app);
// Removed because of issues in some tests: // RebindTestUtils.checkCurrentMementoSerializable(app);
Application result = RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
mgmtContexts.add(result.getManagementContext());
return result;
}
protected Entity setupAndCheckTestEntityInBasicYamlWith(String... extras) throws Exception {
Entity app = createAndStartApplication(loadYaml("test-entity-basic-template.yaml", extras));
waitForApplicationTasks(app);
Assert.assertEquals(app.getDisplayName(), "test-entity-basic-template");
log.info("App started:");
Entities.dumpInfo(app);
Assert.assertTrue(app.getChildren().iterator().hasNext(), "Expected app to have child entity");
Entity entity = app.getChildren().iterator().next();
Assert.assertTrue(entity instanceof TestEntity, "Expected TestEntity, found " + entity.getClass());
return entity;
}
public static <T> T getConfigInTask(final Entity entity, final ConfigKey<T> key) {
return Entities.submit(entity, Tasks.<T>builder().body(new Callable<T>() {
@Override
public T call() throws Exception {
return entity.getConfig(key);
}
}).build()).getUnchecked();
}
protected <T> Future<T> getConfigInTaskAsync(final Entity entity, final ConfigKey<T> key) {
// Wait for the attribute to be ready in a new Task
Callable<T> configGetter = new Callable<T>() {
@Override
public T call() throws Exception {
T s = getConfigInTask(entity, key);
getLogger().info("getConfig {}={}", key, s);
return s;
}
};
return executor.submit(configGetter);
}
@Test
public void testDslAttributeWhenReady() throws Exception {
Entity testEntity = entityWithAttributeWhenReady();
((EntityInternal) testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "bar");
}
@Test
public void testDslAttributeWhenReadyRebindWhenResolved() throws Exception {
Entity testEntity = entityWithAttributeWhenReady();
((EntityInternal) testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
Application app2 = rebind(testEntity.getApplication());
Entity e2 = Iterables.getOnlyElement(app2.getChildren());
Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "bar");
}
@Test
public void testDslAttributeWhenReadyWhenNotYetResolved() throws Exception {
Entity testEntity = entityWithAttributeWhenReady();
Application app2 = rebind(testEntity.getApplication());
Entity e2 = Iterables.getOnlyElement(app2.getChildren());
// Wait for the attribute to be ready in a new Task
Future<String> stringFuture = getConfigInTaskAsync(e2, TestEntity.CONF_NAME);
// Check that the Task is still waiting for attribute to be ready
Assert.assertFalse(stringFuture.isDone());
// Set the sensor; expect that to complete
e2.sensors().set(Sensors.newStringSensor("foo"), "bar");
String s = stringFuture.get(10, TimeUnit.SECONDS); // Timeout just for sanity
Assert.assertEquals(s, "bar");
}
@Test
public void testDslAttributeWhenReadyPersistedAsDeferredSupplier() throws Exception {
doDslAttributeWhenReadyPersistedAsDeferredSupplier(false);
}
@Test
public void testDslAttributeWhenReadyPersistedWithoutLeakingResolvedValue() throws Exception {
doDslAttributeWhenReadyPersistedAsDeferredSupplier(true);
}
protected void doDslAttributeWhenReadyPersistedAsDeferredSupplier(boolean resolvedBeforeRebind) throws Exception {
Entity testEntity = entityWithAttributeWhenReady();
if (resolvedBeforeRebind) {
testEntity.sensors().set(Sensors.newStringSensor("foo"), "bar");
Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "bar");
}
// Persist and rebind
Application app2 = rebind(testEntity.getApplication());
Entity e2 = Iterables.getOnlyElement(app2.getChildren());
Maybe<Object> maybe = ((EntityInternal) e2).config().getLocalRaw(TestEntity.CONF_NAME);
Assert.assertTrue(maybe.isPresentAndNonNull());
Assert.assertTrue(BrooklynDslDeferredSupplier.class.isInstance(maybe.get()));
BrooklynDslDeferredSupplier<?> deferredSupplier = (BrooklynDslDeferredSupplier<?>) maybe.get();
Assert.assertEquals(deferredSupplier.toString(), "$brooklyn:entity(\"x\").attributeWhenReady(\"foo\")");
// Assert the persisted state itself is as expected, and not too big
BrooklynMementoRawData raw = BrooklynPersistenceUtils.newStateMemento(app2.getManagementContext(), MementoCopyMode.LOCAL);
String persistedStateForE2 = raw.getEntities().get(e2.getId());
Matcher matcher = Pattern.compile(".*\\<test.confName\\>(.*)\\<\\/test.confName\\>.*", Pattern.DOTALL)
.matcher(persistedStateForE2);
Assert.assertTrue(matcher.find());
String testConfNamePersistedState = matcher.group(1);
Assert.assertNotNull(testConfNamePersistedState);
// should be about 200 chars long, something like:
//
// <test.confName>
// <org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady>
// <component>
// <componentId>x</componentId>
// <scope>GLOBAL</scope>
// </component>
// <sensorName>foo</sensorName>
// </org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady>
// </test.confName>
Assert.assertTrue(testConfNamePersistedState.length() < 400, "persisted state too long: " + testConfNamePersistedState);
Assert.assertFalse(testConfNamePersistedState.contains("bar"), "value 'bar' leaked in persisted state");
}
@Test
public void testDslAttributeWhenReadyInEntitySpecWhenNotYetResolved() throws Exception {
doDslAttributeWhenReadyInEntitySpec(false);
}
@Test
public void testDslAttributeWhenReadyInEntitySpecWhenAlreadyResolved() throws Exception {
doDslAttributeWhenReadyInEntitySpec(true);
}
protected void doDslAttributeWhenReadyInEntitySpec(boolean resolvedBeforeRebind) throws Exception {
String yaml = "location: localhost\n" +
"name: Test Cluster\n" +
"services:\n" +
"- type: org.apache.brooklyn.entity.group.DynamicCluster\n" +
" id: test-cluster\n" +
" initialSize: 0\n" +
" memberSpec:\n" +
" $brooklyn:entitySpec:\n" +
" type: org.apache.brooklyn.core.test.entity.TestEntity\n" +
" brooklyn.config:\n" +
" test.confName: $brooklyn:component(\"test-cluster\").attributeWhenReady(\"sensor\")";
final Entity testEntity = createAndStartApplication(yaml);
DynamicCluster cluster = (DynamicCluster) Iterables.getOnlyElement(testEntity.getApplication().getChildren());
cluster.resize(1);
Assert.assertEquals(cluster.getMembers().size(), 1);
if (resolvedBeforeRebind) {
cluster.sensors().set(Sensors.newStringSensor("sensor"), "bar");
}
// Persist and rebind
Application app2 = rebind(cluster.getApplication());
DynamicCluster cluster2 = (DynamicCluster) Iterables.getOnlyElement(app2.getApplication().getChildren());
// Assert the persisted state itself is as expected, and not too big
BrooklynMementoRawData raw = BrooklynPersistenceUtils.newStateMemento(app2.getManagementContext(), MementoCopyMode.LOCAL);
String persistedStateForE2 = raw.getEntities().get(cluster2.getId());
String expectedTag = "org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady";
Matcher matcher = Pattern.compile(".*\\<"+expectedTag+"\\>(.*)\\<\\/"+expectedTag+"\\>.*", Pattern.DOTALL)
.matcher(persistedStateForE2);
Assert.assertTrue(matcher.find(), persistedStateForE2);
String testConfNamePersistedState = matcher.group(1);
Assert.assertNotNull(testConfNamePersistedState);
// Can re-size to create a new member entity
cluster2.resize(2);
Assert.assertEquals(cluster2.getMembers().size(), 2);
// Both the existing and the new member should have the DeferredSupplier config
for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
Maybe<Object> maybe = ((EntityInternal)member).config().getLocalRaw(TestEntity.CONF_NAME);
Assert.assertTrue(maybe.isPresentAndNonNull());
BrooklynDslDeferredSupplier<?> deferredSupplier = (BrooklynDslDeferredSupplier<?>) maybe.get();
Assert.assertEquals(deferredSupplier.toString(), "$brooklyn:entity(\"test-cluster\").attributeWhenReady(\"sensor\")");
}
if (resolvedBeforeRebind) {
// All members should resolve their config
for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
String val = getConfigInTask(member, TestEntity.CONF_NAME);
Assert.assertEquals(val, "bar");
}
} else {
List<Future<String>> futures = Lists.newArrayList();
// All members should have unresolved values
for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
// Wait for the attribute to be ready in a new Task
Future<String> stringFuture = getConfigInTaskAsync(member, TestEntity.CONF_NAME);
futures.add(stringFuture);
// Check that the Task is still waiting for attribute to be ready
Thread.sleep(100);
Assert.assertFalse(stringFuture.isDone());
}
// After setting the sensor, all those values should now resolve
cluster2.sensors().set(Sensors.newStringSensor("sensor"), "bar");
for (Future<String> future : futures) {
String s = future.get(10, TimeUnit.SECONDS); // Timeout just for sanity
Assert.assertEquals(s, "bar");
}
}
}
private Entity entityWithAttributeWhenReady() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.confName: $brooklyn:component(\"x\").attributeWhenReady(\"foo\")");
}
private void doTestOnEntityWithSensor(Entity testEntity, Sensor<?> expectedSensor) throws Exception {
doTestOnEntityWithSensor(testEntity, expectedSensor, true);
}
private void doTestOnEntityWithSensor(Entity testEntity, Sensor<?> expectedSensor, boolean inTask) throws Exception {
@SuppressWarnings("rawtypes")
ConfigKey<Sensor> configKey = ConfigKeys.newConfigKey(Sensor.class, "test.sensor");
Sensor<?> s;
s = inTask ? getConfigInTask(testEntity, configKey) : testEntity.getConfig(configKey);
Assert.assertEquals(s, expectedSensor);
Application app2 = rebind(testEntity.getApplication());
Entity te2 = Iterables.getOnlyElement(app2.getChildren());
s = inTask ? getConfigInTask(te2, configKey) : te2.getConfig(configKey);
Assert.assertEquals(s, expectedSensor);
}
@Test
public void testDslSensorFromClass() throws Exception {
doTestOnEntityWithSensor(entityWithSensorFromClass(), Attributes.SERVICE_UP);
// without context it can still find it
doTestOnEntityWithSensor(entityWithSensorFromClass(), Attributes.SERVICE_UP, false);
}
@Test
public void testDslSensorLocal() throws Exception {
doTestOnEntityWithSensor(entityWithSensorLocal(), TestEntity.SEQUENCE);
// here without context it makes one up, so type info (and description etc) not present;
// but context is needed to submit the DslDeferredSupplier object, so this would fail
// doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, TestEntity.SEQUENCE.getName()), false);
}
@Test
public void testDslSensorAdHoc() throws Exception {
doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, "sensor.foo"));
// here context has no impact, but it is needed to submit the DslDeferredSupplier object so this would fail
// doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, "sensor.foo"), false);
}
private Entity entityWithSensorFromClass() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.sensor: $brooklyn:sensor(\"" + Attributes.class.getName() + "\", \"" + Attributes.SERVICE_UP.getName() + "\")");
}
private Entity entityWithSensorLocal() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.sensor: $brooklyn:sensor(\"" + TestEntity.SEQUENCE.getName() + "\")");
}
private Entity entityWithSensorAdHoc() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.sensor: $brooklyn:sensor(\"sensor.foo\")");
}
@Test
public void testDslConfigFromRoot() throws Exception {
Entity testEntity = entityWithConfigFromRoot();
Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "bar");
}
@Test
public void testDslConfigFromRootRebind() throws Exception {
Entity testEntity = entityWithConfigFromRoot();
Application app2 = rebind(testEntity.getApplication());
Entity e2 = Iterables.getOnlyElement(app2.getChildren());
Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "bar");
}
private Entity entityWithConfigFromRoot() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.confName: $brooklyn:component(\"x\").config(\"foo\")",
"brooklyn.config:",
" foo: bar");
}
@Test
public void testDslFormatString() throws Exception {
Entity testEntity = entityWithFormatString();
Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "hello world");
}
@Test
public void testDslFormatStringRebind() throws Exception {
Entity testEntity = entityWithFormatString();
Application app2 = rebind(testEntity.getApplication());
Entity e2 = Iterables.getOnlyElement(app2.getChildren());
Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "hello world");
}
private Entity entityWithFormatString() throws Exception {
return setupAndCheckTestEntityInBasicYamlWith(
" id: x",
" brooklyn.config:",
" test.confName: $brooklyn:formatString(\"hello %s\", \"world\")");
}
/*
- type: org.apache.brooklyn.enricher.stock.Transformer
brooklyn.config:
enricher.sourceSensor: $brooklyn:sensor("mongodb.server.replicaSet.primary.endpoint")
enricher.targetSensor: $brooklyn:sensor("justtheport")
enricher.transformation: $brooklyn:function.regexReplacement("^.*:", "")
- type: org.apache.brooklyn.enricher.stock.Transformer
brooklyn.config:
enricher.sourceSensor: $brooklyn:sensor("mongodb.server.replicaSet.primary.endpoint")
enricher.targetSensor: $brooklyn:sensor("directport")
enricher.targetValue: $brooklyn:regexReplacement($brooklyn:attributeWhenReady("mongodb.server.replicaSet.primary.endpoint"), "^.*:", "foo")
*/
@Test
public void testRegexReplacementWithStrings() throws Exception {
Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
" brooklyn.config:",
" test.regex.config: $brooklyn:regexReplacement(\"somefooname\", \"foo\", \"bar\")"
);
Assert.assertEquals("somebarname", testEntity.getConfig(ConfigKeys.newStringConfigKey("test.regex.config")));
}
@Test
public void testRegexReplacementWithAttributeWhenReady() throws Exception {
Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
" brooklyn.config:",
" test.regex.config: $brooklyn:regexReplacement($brooklyn:attributeWhenReady(\"test.regex.source\"), $brooklyn:attributeWhenReady(\"test.regex.pattern\"), $brooklyn:attributeWhenReady(\"test.regex.replacement\"))"
);
testEntity.sensors().set(Sensors.newStringSensor("test.regex.source"), "somefooname");
testEntity.sensors().set(Sensors.newStringSensor("test.regex.pattern"), "foo");
testEntity.sensors().set(Sensors.newStringSensor("test.regex.replacement"), "bar");
Assert.assertEquals("somebarname", testEntity.getConfig(ConfigKeys.newStringConfigKey("test.regex.config")));
}
@Test
public void testRegexReplacementFunctionWithStrings() throws Exception {
Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
" brooklyn.enrichers:",
" - type: org.apache.brooklyn.enricher.stock.Transformer",
" brooklyn.config:",
" enricher.sourceSensor: $brooklyn:sensor(\"test.name\")",
" enricher.targetSensor: $brooklyn:sensor(\"test.name.transformed\")",
" enricher.transformation: $brooklyn:function.regexReplacement(\"foo\", \"bar\")"
);
testEntity.sensors().set(TestEntity.NAME, "somefooname");
AttributeSensor<String> transformedSensor = Sensors.newStringSensor("test.name.transformed");
EntityAsserts.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
}
@Test
public void testRegexReplacementFunctionWithAttributeWhenReady() throws Exception {
Entity testEntity = setupAndCheckTestEntityInBasicYamlWith(
" brooklyn.enrichers:",
" - type: org.apache.brooklyn.enricher.stock.Transformer",
" brooklyn.config:",
" enricher.sourceSensor: $brooklyn:sensor(\"test.name\")",
" enricher.targetSensor: $brooklyn:sensor(\"test.name.transformed\")",
" enricher.transformation: $brooklyn:function.regexReplacement($brooklyn:attributeWhenReady(\"test.pattern\"), $brooklyn:attributeWhenReady(\"test.replacement\"))"
);
testEntity.sensors().set(Sensors.newStringSensor("test.pattern"), "foo");
testEntity.sensors().set(Sensors.newStringSensor("test.replacement"), "bar");
testEntity.sensors().set(TestEntity.NAME, "somefooname");
AttributeSensor<String> transformedSensor = Sensors.newStringSensor("test.name.transformed");
EntityAsserts.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
}
}