You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2016/07/04 13:04:34 UTC

[1/3] brooklyn-server git commit: TestSshCommand: fail fast if no command

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 706fdb7b6 -> c7731a4fa


TestSshCommand: fail fast if no command


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/d2fe5918
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/d2fe5918
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/d2fe5918

Branch: refs/heads/master
Commit: d2fe59181107a13afb9a169daffe6594356e0d4b
Parents: de12a4e
Author: Aled Sage <al...@gmail.com>
Authored: Thu Jun 30 13:28:23 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Jul 4 12:27:53 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/test/framework/BaseTest.java       |  9 +--
 .../framework/TargetableTestComponentImpl.java  |  7 ++
 .../test/framework/TestSshCommandImpl.java      | 29 ++++---
 .../framework/TargetableTestComponentTest.java  | 84 ++++++++++++++++----
 .../test/framework/TestSshCommandTest.java      | 41 +++++++---
 5 files changed, 127 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d2fe5918/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
index f5eb30b..5c313cd 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
@@ -19,15 +19,14 @@
 package org.apache.brooklyn.test.framework;
 
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import com.google.common.collect.ImmutableList;
 
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.util.time.Duration;
 
+import com.google.common.collect.ImmutableList;
+
 /**
  * A base interface for all tests.
  */
@@ -40,10 +39,10 @@ public interface BaseTest extends TargetableTestComponent, Startable {
         ImmutableList.<Map<String, Object>>of());
 
     /**
-     * THe duration to wait for an assertion to succeed or fail before throwing an exception.
+     * The duration to wait for an assertion to succeed or fail before throwing an exception.
      */
     ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "Time to wait on result",
-        new Duration(1L, TimeUnit.SECONDS));
+        Duration.seconds(1));
 
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d2fe5918/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
index 326e3ea..4750b4d 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -25,6 +27,7 @@ import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
+import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -99,4 +102,8 @@ public abstract class TargetableTestComponentImpl extends AbstractEntity impleme
             throw Exceptions.propagate(e);
         }
     }
+    
+    protected <T> T getRequiredConfig(ConfigKey<T> key) {
+        return checkNotNull(config().get(key), "config %s must not be null", key);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d2fe5918/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java
index c65f4f9..33321e9 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java
@@ -117,6 +117,7 @@ public class TestSshCommandImpl extends TargetableTestComponentImpl implements T
         return Strings.maxlenWithEllipsis(text, A_LINE);
     }
 
+    @SuppressWarnings("serial")
     private static class MarkerException extends Exception {
         public MarkerException(Throwable cause) {
             super(cause);
@@ -125,9 +126,10 @@ public class TestSshCommandImpl extends TargetableTestComponentImpl implements T
 
     public void execute() {
         try {
+            checkConfig();
             final SshMachineLocation machineLocation =
                     Machines.findUniqueMachineLocation(resolveTarget().getLocations(), SshMachineLocation.class).get();
-            final Duration timeout = config().get(TIMEOUT);
+            final Duration timeout = getRequiredConfig(TIMEOUT);
 
             ReferenceWithError<Boolean> result = Repeater.create("Running ssh-command tests")
                     .limitTimeTo(timeout)
@@ -170,28 +172,33 @@ public class TestSshCommandImpl extends TargetableTestComponentImpl implements T
         String downloadUrl = getConfig(DOWNLOAD_URL);
         String command = getConfig(COMMAND);
 
-        String downloadName = DOWNLOAD_URL.getName();
-        String commandName = COMMAND.getName();
-
         Map<String, Object> env = getConfig(SHELL_ENVIRONMENT);
         if (env == null) env = ImmutableMap.of();
         
-        if (!(isNonBlank(downloadUrl) ^ isNonBlank(command))) {
-            throw illegal("Must specify exactly one of", downloadName, "and", commandName);
-        }
-
         if (isNonBlank(downloadUrl)) {
             String scriptDir = getConfig(SCRIPT_DIR);
             String scriptPath = calculateDestPath(downloadUrl, scriptDir);
             result = executeDownloadedScript(machineLocation, downloadUrl, scriptPath, env);
-        }
-
-        if (isNonBlank(command)) {
+        } else if (isNonBlank(command)) {
             result = executeShellCommand(machineLocation, command, env);
+        } else {
+            // should have been caught by checkConfig() earlier; maybe someone reconfigured it on-the-fly?!
+            throw illegal("Must specify exactly one of", DOWNLOAD_URL.getName(), "and", COMMAND.getName());
         }
 
         return result;
     }
+    
+    protected void checkConfig() {
+        String downloadUrl = getConfig(DOWNLOAD_URL);
+        String command = getConfig(COMMAND);
+
+        if (!(isNonBlank(downloadUrl) ^ isNonBlank(command))) {
+            String downloadName = DOWNLOAD_URL.getName();
+            String commandName = COMMAND.getName();
+            throw illegal("Must specify exactly one of", downloadName, "and", commandName);
+        }
+    }
 
     protected void handle(Result result) {
         LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d2fe5918/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
index dec0be7..28149ad 100644
--- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.NoSuchElementException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -28,8 +29,12 @@ import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
 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.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -39,6 +44,21 @@ public class TargetableTestComponentTest extends BrooklynAppUnitTestSupport {
 
     private static final AttributeSensor<String> STRING_SENSOR = Sensors.newStringSensor("string-sensor");
 
+    private ExecutorService executor;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        if (executor != null) executor.shutdownNow();
+        super.tearDown();
+    }
+    
     @Test
     public void testTargetEntity() {
         app.sensors().set(STRING_SENSOR, "myval");
@@ -69,28 +89,58 @@ public class TargetableTestComponentTest extends BrooklynAppUnitTestSupport {
     public void testTargetEntityByIdWithDelayedEntityCreation() {
         final Duration entityCreationDelay = Duration.millis(250);
         final Duration overheadDuration = Duration.seconds(10);
-        ExecutorService executor = Executors.newCachedThreadPool();
+        executor =  Executors.newCachedThreadPool();
+
+        executor.submit(new Runnable() {
+            @Override public void run() {
+                Time.sleep(entityCreationDelay);
+                TestEntity target = app.addChild(EntitySpec.create(TestEntity.class)
+                        .configure(BrooklynCampConstants.PLAN_ID, "myTargetId"));
+                target.sensors().set(STRING_SENSOR, "myval");
+            }});
+
+        app.addChild(EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.TARGET_ID, "myTargetId")
+                .configure(TestSensor.TARGET_RESOLUTION_TIMEOUT, Duration.of(entityCreationDelay).add(overheadDuration))
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+
         
+        app.start(ImmutableList.<Location>of());
+    }
+    
+    @Test
+    public void testTargetEntityByIdNotFound() {
+        app.addChild(EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.TARGET_ID, "myTargetId")
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+
         try {
-            executor.submit(new Runnable() {
-                @Override public void run() {
-                    Time.sleep(entityCreationDelay);
-                    TestEntity target = app.addChild(EntitySpec.create(TestEntity.class)
-                            .configure(BrooklynCampConstants.PLAN_ID, "myTargetId"));
-                    target.sensors().set(STRING_SENSOR, "myval");
-                }});
+            app.start(ImmutableList.<Location>of());
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            NoSuchElementException e2 = Exceptions.getFirstThrowableOfType(e, NoSuchElementException.class);
+            if (e2 == null) throw e;
+            Asserts.expectedFailureContains(e2, "No entity matching id myTargetId");
+        }
+    }
     
-            app.addChild(EntitySpec.create(TestSensor.class)
-                    .configure(TestSensor.TARGET_ID, "myTargetId")
-                    .configure(TestSensor.TARGET_RESOLUTION_TIMEOUT, Duration.of(entityCreationDelay).add(overheadDuration))
-                    .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
-                    .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+    @Test
+    public void testTargetEntityByIdNotFoundWithResolutionTimeout() {
+        app.addChild(EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.TARGET_ID, "myTargetId")
+                .configure(TestSensor.TARGET_RESOLUTION_TIMEOUT, Duration.millis(10))
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
 
-            
+        try {
             app.start(ImmutableList.<Location>of());
-            
-        } finally {
-            executor.shutdownNow();
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            NoSuchElementException e2 = Exceptions.getFirstThrowableOfType(e, NoSuchElementException.class);
+            if (e2 == null) throw e;
+            Asserts.expectedFailureContains(e2, "No entity matching id myTargetId");
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d2fe5918/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandTest.java
index 7263dbe..37e79c9 100644
--- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandTest.java
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandTest.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.test.framework;
 
 import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.framework.BaseTest.TIMEOUT;
 import static org.apache.brooklyn.test.framework.TargetableTestComponent.TARGET_ENTITY;
 import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.CONTAINS;
 import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.EQUALS;
@@ -45,16 +46,17 @@ import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool;
 import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmd;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.time.Duration;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
@@ -211,11 +213,9 @@ public class TestSshCommandTest extends BrooklynAppUnitTestSupport {
 
     @Test
     public void shouldNotBeUpIfAssertionsFail() {
-        Map<String, Object> equalsOne = MutableMap.of();
-        equalsOne.put(EQUALS, 1);
+        Map<String, ?> equalsOne = ImmutableMap.of(EQUALS, 1);
 
-        Map<String, Object> equals255 = MutableMap.of();
-        equals255.put(EQUALS, 255);
+        Map<String, ?> equals255 = ImmutableMap.of(EQUALS, 255);
 
         TestSshCommand test = app.createAndManageChild(EntitySpec.create(TestSshCommand.class)
             .configure(TARGET_ENTITY, testEntity)
@@ -238,11 +238,7 @@ public class TestSshCommandTest extends BrooklynAppUnitTestSupport {
         Path testScript = createTempScript("script", "echo " + text);
 
         try {
-            Map<String, Object> equalsZero = MutableMap.of();
-            equalsZero.put(EQUALS, 0);
-
-            Map<String, Object> containsText = MutableMap.of();
-            containsText.put(CONTAINS, text);
+            Map<String, ?> equalsZero = ImmutableMap.of(EQUALS, 0);
 
             TestSshCommand test = app.createAndManageChild(EntitySpec.create(TestSshCommand.class)
                 .configure(TARGET_ENTITY, testEntity)
@@ -278,6 +274,31 @@ public class TestSshCommandTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
+    public void shouldFailFastIfNoCommand() throws Exception {
+        Duration longTimeout = Asserts.DEFAULT_LONG_TIMEOUT;
+        
+        Map<String, ?> equalsZero = ImmutableMap.of(EQUALS, 0);
+        
+        TestSshCommand test = app.createAndManageChild(EntitySpec.create(TestSshCommand.class)
+                .configure(TIMEOUT, longTimeout.multiply(2))
+                .configure(TARGET_ENTITY, testEntity)
+                .configure(ASSERT_STATUS, makeAssertions(equalsZero)));
+
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        try {
+            app.start(ImmutableList.<Location>of());
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            // note: sleep(1000) can take a few millis less than 1000ms, according to a stopwatch.
+            Asserts.expectedFailureContains(e, "Must specify exactly one of download.url and command");
+            Duration elapsed = Duration.of(stopwatch);
+            Asserts.assertTrue(elapsed.isShorterThan(longTimeout.subtract(Duration.millis(20))), "elapsed="+elapsed);
+        }
+
+        assertServiceFailed(test);
+    }
+    
+    @Test
     public void shouldIncludeEnv() throws Exception {
         Map<String, Object> env = ImmutableMap.<String, Object>of("ENV1", "val1", "ENV2", "val2");
         


[3/3] brooklyn-server git commit: This closes #225

Posted by al...@apache.org.
This closes #225


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/c7731a4f
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/c7731a4f
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/c7731a4f

Branch: refs/heads/master
Commit: c7731a4faf8065930b7921bb3978ce80122b285d
Parents: 706fdb7 d2fe591
Author: Aled Sage <al...@gmail.com>
Authored: Mon Jul 4 14:04:10 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Jul 4 14:04:10 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/test/framework/BaseTest.java       |   9 +-
 .../test/framework/TargetableTestComponent.java |  11 ++
 .../framework/TargetableTestComponentImpl.java  |  52 +++++--
 .../test/framework/TestSshCommandImpl.java      |  29 ++--
 .../framework/TargetableTestComponentTest.java  | 146 +++++++++++++++++++
 .../test/framework/TestSshCommandTest.java      |  41 ++++--
 6 files changed, 249 insertions(+), 39 deletions(-)
----------------------------------------------------------------------



[2/3] brooklyn-server git commit: Adds TargetableTestComponent.targetResolutionTimeout

Posted by al...@apache.org.
Adds TargetableTestComponent.targetResolutionTimeout

If no entity with the given id yet exists, then wait for this timeout
for it to exist (defaults to zero, in which case fail immediately
like it did before)

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/de12a4e8
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/de12a4e8
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/de12a4e8

Branch: refs/heads/master
Commit: de12a4e8a77f7d2bfe1027ed08d6e1e0efb2ce39
Parents: ce1192f
Author: Aled Sage <al...@gmail.com>
Authored: Thu Jun 30 13:16:57 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Jul 4 12:27:53 2016 +0100

----------------------------------------------------------------------
 .../test/framework/TargetableTestComponent.java | 11 +++
 .../framework/TargetableTestComponentImpl.java  | 47 +++++++---
 .../framework/TargetableTestComponentTest.java  | 96 ++++++++++++++++++++
 3 files changed, 140 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/de12a4e8/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
index 8ae44ad..67daff6 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
@@ -20,9 +20,11 @@ package org.apache.brooklyn.test.framework;
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.util.time.Duration;
 
 /**
  * Entity that can target another entity for the purpouse of testing
@@ -43,6 +45,15 @@ public interface TargetableTestComponent extends Entity, Startable {
     AttributeSensorAndConfigKey<String, String> TARGET_ID = ConfigKeys.newStringSensorAndConfigKey("targetId", "Id of the entity under test");
 
     /**
+     * The duration to wait for an entity with the given targetId to exist, before throwing an exception.
+     */
+    ConfigKey<Duration> TARGET_RESOLUTION_TIMEOUT = ConfigKeys.newConfigKey(
+            Duration.class, 
+            "targetResolutionTimeout", 
+            "Time to wait for targetId to exist (defaults to zero, i.e. must exist immediately)",
+            Duration.ZERO);
+
+    /**
      * Get the target of the test.
      *
      * @return The target.

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/de12a4e8/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
index 5b133bd..326e3ea 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
@@ -18,10 +18,8 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import java.util.concurrent.ExecutionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
@@ -30,6 +28,10 @@ import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Class that can resolve the target for a test component
@@ -62,22 +64,39 @@ public abstract class TargetableTestComponentImpl extends AbstractEntity impleme
         return target;
     }
 
-    private static Entity getTargetById(ExecutionContext executionContext, Entity entity) {
-        String targetId = entity.getConfig(TARGET_ID);
-
+    private static Entity getTargetById(final ExecutionContext executionContext, final Entity entity) {
+        final String targetId = entity.getConfig(TARGET_ID);
+        Duration resolutionTimeout = entity.getConfig(TARGET_RESOLUTION_TIMEOUT);
+        
         if(targetId == null){
             return null;
         }
 
-        final Task<Entity> targetLookup = new DslComponent(targetId).newTask();
-        Entity target = null;
+        final AtomicReference<Entity> result = new AtomicReference<>();
+        final DslComponent dslComponent = new DslComponent(targetId);
+        Callable<Boolean> resolver = new Callable<Boolean>() {
+            @Override public Boolean call() throws Exception {
+                Task<Entity> task = dslComponent.newTask();
+                result.set(Tasks.resolveValue(task, Entity.class, executionContext, "Finding entity " + targetId));
+                return true;
+            }
+        };
         try {
-            target = Tasks.resolveValue(targetLookup, Entity.class, executionContext, "Finding entity " + targetId);
-            LOG.debug("Found target by id {}", targetId);
-        } catch (final ExecutionException | InterruptedException e) {
+            if (resolutionTimeout == null || resolutionTimeout.toMilliseconds() <= 0) {
+                resolver.call();
+            } else {
+                Repeater.create("find entity "+targetId)
+                        .backoffTo(resolutionTimeout.multiply(0.1))
+                        .limitTimeTo(resolutionTimeout)
+                        .rethrowException()
+                        .until(resolver)
+                        .runRequiringTrue();
+            }
+            LOG.debug("Found target {} by id {}", result.get(), targetId);
+            return result.get();
+        } catch (Exception e) {
             LOG.error("Error finding target {}", targetId);
-            Exceptions.propagate(e);
+            throw Exceptions.propagate(e);
         }
-        return target;
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/de12a4e8/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
new file mode 100644
index 0000000..dec0be7
--- /dev/null
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TargetableTestComponentTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.test.framework;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
+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.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class TargetableTestComponentTest extends BrooklynAppUnitTestSupport {
+
+    private static final AttributeSensor<String> STRING_SENSOR = Sensors.newStringSensor("string-sensor");
+
+    @Test
+    public void testTargetEntity() {
+        app.sensors().set(STRING_SENSOR, "myval");
+
+        app.addChild(EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.TARGET_ENTITY, app)
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+
+        app.start(ImmutableList.<Location>of());
+    }
+    
+    @Test
+    public void testTargetEntityById() {
+        TestEntity target = app.addChild(EntitySpec.create(TestEntity.class)
+                .configure(BrooklynCampConstants.PLAN_ID, "myTargetId"));
+        target.sensors().set(STRING_SENSOR, "myval");
+
+        app.addChild(EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.TARGET_ID, "myTargetId")
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+
+        app.start(ImmutableList.<Location>of());
+    }
+    
+    @Test
+    public void testTargetEntityByIdWithDelayedEntityCreation() {
+        final Duration entityCreationDelay = Duration.millis(250);
+        final Duration overheadDuration = Duration.seconds(10);
+        ExecutorService executor = Executors.newCachedThreadPool();
+        
+        try {
+            executor.submit(new Runnable() {
+                @Override public void run() {
+                    Time.sleep(entityCreationDelay);
+                    TestEntity target = app.addChild(EntitySpec.create(TestEntity.class)
+                            .configure(BrooklynCampConstants.PLAN_ID, "myTargetId"));
+                    target.sensors().set(STRING_SENSOR, "myval");
+                }});
+    
+            app.addChild(EntitySpec.create(TestSensor.class)
+                    .configure(TestSensor.TARGET_ID, "myTargetId")
+                    .configure(TestSensor.TARGET_RESOLUTION_TIMEOUT, Duration.of(entityCreationDelay).add(overheadDuration))
+                    .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                    .configure(TestSensor.ASSERTIONS, ImmutableList.of(ImmutableMap.of("equals", "myval"))));
+
+            
+            app.start(ImmutableList.<Location>of());
+            
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+}