/*
* 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 static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.test.EntityTestUtils;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.BasicTask;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.text.StringPredicates;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Callables;
/** Tests the standalone routines in dependent configuration.
* See e.g. LocalEntitiesTest for tests of attributeWhenReady etc.
*/
public class DependentConfigurationTest extends BrooklynAppUnitTestSupport {
private static final Logger log = LoggerFactory.getLogger(DependentConfigurationTest.class);
public static final int SHORT_WAIT_MS = 100;
public static final int TIMEOUT_MS = 30*1000;
private TestEntity entity;
private TestEntity entity2;
@BeforeMethod(alwaysRun=true)
@Override
public void setUp() throws Exception {
super.setUp();
entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
}
@Test
public void testTransform() throws Exception {
Task<Integer> t = DependentConfiguration.transform(
new BasicTask<Integer>(Callables.returning(2)),
incrementerFunction());
submit(t);
assertEquals(t.get(TIMEOUT_MS, TimeUnit.MILLISECONDS), Integer.valueOf(3));
}
private Function<Integer, Integer> incrementerFunction() {
return new Function<Integer, Integer>() {
@Override public Integer apply(Integer val) {
return val + 1;
}};
}
@Test
public void testFormatString() throws Exception {
Task<String> t = DependentConfiguration.formatString("%s://%s:%d/",
"http",
new BasicTask<String>(Callables.returning("localhost")),
DependentConfiguration.transform(new BasicTask<Integer>(Callables.returning(8080)), incrementerFunction()));
submit(t);
Assert.assertEquals(t.get(TIMEOUT_MS, TimeUnit.MILLISECONDS), "http://localhost:8081/");
}
@Test
public void testRegexReplacementFunctionWithStrings() throws Exception {
Task<Function<String, String>> task = DependentConfiguration.regexReplacement("foo", "bar");
submit(task);
Function<String, String> regexReplacer = task.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(regexReplacer.apply("somefootext"), "somebartext");
}
@Test
public void testRegexReplacementFunctionWithAttributeWhenReady() throws Exception {
AttributeSensor<Object> replacementSensor = Sensors.newSensor(Object.class, "test.replacement");
Task<String> pattern = DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME);
Task<Object> replacement = DependentConfiguration.attributeWhenReady(entity, replacementSensor);
Task<Function<String, String>> task = DependentConfiguration.regexReplacement(pattern, replacement);
submit(task);
entity.sensors().set(TestEntity.NAME, "foo");
entity.sensors().set(replacementSensor, "bar");
Function<String, String> regexReplacer = task.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(regexReplacer.apply("somefootext"), "somebartext");
}
@Test
public void testRegexReplacementWithStrings() throws Exception {
Task<String> task = DependentConfiguration.regexReplacement("somefootext", "foo", "bar");
submit(task);
String result = task.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(result, "somebartext");
}
@Test
public void testRegexReplacementWithAttributeWhenReady() throws Exception {
AttributeSensor<String> sourceSensor = Sensors.newStringSensor("test.source");
AttributeSensor<String> replacementSensor = Sensors.newSensor(String.class, "test.replacement");
Task<String> source = DependentConfiguration.attributeWhenReady(entity, sourceSensor);
Task<String> pattern = DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME);
Task<String> replacement = DependentConfiguration.attributeWhenReady(entity, replacementSensor);
Task<String> task = DependentConfiguration.regexReplacement(source, pattern, replacement);
submit(task);
entity.sensors().set(sourceSensor, "somefootext");
entity.sensors().set(TestEntity.NAME, "foo");
entity.sensors().set(replacementSensor, "bar");
String result = task.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(result, "somebartext");
}
@Test
public void testAttributeWhenReady() throws Exception {
final Task<String> t = submit(DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME));
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.NAME, "myval");
assertEquals(assertDoneEventually(t), "myval");
}
@Test
public void testAttributeWhenReadyWithPredicate() throws Exception {
final Task<String> t = submit(DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME, Predicates.equalTo("myval2")));
entity.sensors().set(TestEntity.NAME, "myval");
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.NAME, "myval2");
assertEquals(assertDoneEventually(t), "myval2");
}
@Test
public void testAttributeWhenReadyWithPostProcessing() throws Exception {
final Task<String> t = submit(DependentConfiguration.valueWhenAttributeReady(entity, TestEntity.SEQUENCE, Functions.toStringFunction()));
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.SEQUENCE, 1);
assertEquals(assertDoneEventually(t), "1");
}
@Test
public void testAttributeWhenReadyWithPostProcessingWithBuilder() throws Exception {
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.SEQUENCE)
.postProcess(Functions.toStringFunction())
.build());
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.SEQUENCE, 1);
assertEquals(assertDoneEventually(t), "1");
}
@Test
public void testAttributeWhenReadyWithPostProcessingWithBuilderWaitingNow() throws Exception {
final Task<String> t = submit(new Callable<String>() {
public String call() {
return DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.SEQUENCE)
.postProcess(Functions.toStringFunction())
.runNow();
}});
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.SEQUENCE, 1);
assertEquals(assertDoneEventually(t), "1");
}
@Test
public void testAttributeWhenReadyWithAbortHappyPath() throws Exception {
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.abortIf(entity2, TestEntity.SEQUENCE, Predicates.equalTo(1))
.build());
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.NAME, "myval");
assertEquals(assertDoneEventually(t), "myval");
}
@Test
public void testAttributeWhenReadyWithAbort() throws Exception {
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.abortIf(entity2, TestEntity.SEQUENCE, Predicates.equalTo(1))
.build());
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.SEQUENCE, 321);
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.SEQUENCE, 1);
try {
assertDoneEventually(t);
fail();
} catch (Exception e) {
if (!e.toString().contains("Aborted waiting for ready")) throw e;
}
}
@Test
public void testAttributeWhenReadyWithAbortWaitingNow() throws Exception {
final Task<String> t = submit(new Callable<String>() {
public String call() {
return DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.abortIf(entity2, TestEntity.SEQUENCE, Predicates.equalTo(1))
.runNow();
}});
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.SEQUENCE, 321);
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.SEQUENCE, 1);
try {
assertDoneEventually(t);
fail();
} catch (Exception e) {
if (!e.toString().contains("Aborted waiting for ready")) throw e;
}
}
@Test
public void testAttributeWhenReadyWithAbortFailsWhenAbortConditionAlreadyHolds() throws Exception {
entity2.sensors().set(TestEntity.SEQUENCE, 1);
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.abortIf(entity2, TestEntity.SEQUENCE, Predicates.equalTo(1))
.build());
try {
assertDoneEventually(t);
fail();
} catch (Exception e) {
if (!e.toString().contains("Aborted waiting for ready")) throw e;
}
}
@Test
public void testAttributeWhenReadyWithAbortFailsWhenAbortConditionAlreadyHoldsWaitingNow() throws Exception {
entity2.sensors().set(TestEntity.SEQUENCE, 1);
final Task<String> t = submit(new Callable<String>() {
public String call() {
return DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.abortIf(entity2, TestEntity.SEQUENCE, Predicates.equalTo(1))
.runNow();
}});
try {
assertDoneEventually(t);
fail();
} catch (Exception e) {
if (!e.toString().contains("Aborted waiting for ready")) throw e;
}
}
@Test
public void testAttributeWhenReadyRunNowWithoutPostProcess() throws Exception {
Task<String> t = submit(new Callable<String>() {
@Override
public String call() throws Exception {
return DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.runNow();
}
});
entity.sensors().set(TestEntity.NAME, "myentity");
assertDoneEventually(t);
assertEquals(t.get(), "myentity");
}
@Test
public void testAttributeWhenReadyAbortsWhenOnFireByDefault() {
log.info("starting test "+JavaClassNames.niceClassAndMethod());
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.build());
ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
try {
assertDoneEventually(t);
fail("Should have failed already!");
} catch (Throwable e) {
if (e.toString().contains("Aborted waiting for ready"))
return;
log.warn("Did not abort as expected: "+e, e);
Entities.dumpInfo(entity);
throw Exceptions.propagate(e);
}
}
@Test(invocationCount=100, groups = "Integration")
public void testAttributeWhenReadyAbortsWhenOnfireByDefaultManyTimes() {
testAttributeWhenReadyAbortsWhenOnFireByDefault();
}
@Test
public void testAttributeWhenReadyAbortsWhenAlreadyOnFireByDefault() throws Exception {
ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReady(entity, TestEntity.NAME)
.build());
try {
assertDoneEventually(t);
fail();
} catch (Exception e) {
if (!e.toString().contains("Aborted waiting for ready")) throw e;
}
}
@Test
public void testListAttributeWhenReadyFromMultipleEntities() throws Exception {
final Task<List<String>> t = submit(DependentConfiguration.builder()
.attributeWhenReadyFromMultiple(ImmutableList.of(entity, entity2), TestEntity.NAME)
.build());
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.NAME, "myval");
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.NAME, "myval2");
assertEquals(ImmutableSet.copyOf(assertDoneEventually(t)), ImmutableSet.of("myval", "myval2"));
}
@Test
public void testListAttributeWhenReadyFromMultipleEntitiesWithLocalReadinessPredicate() throws Exception {
final Task<List<String>> t = submit(DependentConfiguration.builder()
.attributeWhenReadyFromMultiple(ImmutableList.of(entity, entity2), TestEntity.NAME, StringPredicates.startsWith("myval"))
.build());
entity.sensors().set(TestEntity.NAME, "wrongval");
entity2.sensors().set(TestEntity.NAME, "wrongval2");
assertNotDoneContinually(t);
entity.sensors().set(TestEntity.NAME, "myval");
assertNotDoneContinually(t);
entity2.sensors().set(TestEntity.NAME, "myval2");
assertEquals(ImmutableSet.copyOf(assertDoneEventually(t)), ImmutableSet.of("myval", "myval2"));
}
@Test
public void testListAttributeWhenReadyFromMultipleEntitiesWithGlobalPostProcessor() throws Exception {
final Task<String> t = submit(DependentConfiguration.builder()
.attributeWhenReadyFromMultiple(ImmutableList.of(entity, entity2), TestEntity.SEQUENCE)
.postProcessFromMultiple(new Function<List<Integer>, String>() {
@Override public String apply(List<Integer> input) {
if (input == null) {
return null;
} else {
MutableList<Integer> inputCopy = MutableList.copyOf(input);
Collections.sort(inputCopy);
return Joiner.on(",").join(inputCopy);
}
}})
.build());
entity.sensors().set(TestEntity.SEQUENCE, 1);
entity2.sensors().set(TestEntity.SEQUENCE, 2);
assertEquals(assertDoneEventually(t), "1,2");
}
private void assertNotDoneContinually(final Task<?> t) {
Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), new Callable<Void>() {
@Override public Void call() throws Exception {
if (t.isDone()) {
fail("task unexpectedly done: t="+t+"; result="+t.get());
}
return null;
}
});
}
private <T> T assertDoneEventually(final Task<T> t) throws Exception {
final AtomicReference<ExecutionException> exception = new AtomicReference<ExecutionException>();
T result = Asserts.succeedsEventually(MutableMap.of("timeout", Duration.FIVE_SECONDS), new Callable<T>() {
@Override public T call() throws InterruptedException, TimeoutException {
try {
return t.get(Duration.ONE_SECOND);
} catch (ExecutionException e) {
exception.set(e);
return null;
} catch (InterruptedException e) {
throw e;
} catch (TimeoutException e) {
throw e;
}
}
});
if (exception.get() != null) {
throw exception.get();
}
return result;
}
private <T> Task<T> submit(Task<T> task) {
return app.getExecutionContext().submit(task);
}
private <T> Task<T> submit(Callable<T> job) {
return app.getExecutionContext().submit(new BasicTask<T>(job));
}
}