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/05/19 15:40:18 UTC

[1/4] brooklyn-server git commit: YAML built-in types: include boolean etc

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 6944ac5e1 -> dee107447


YAML built-in types: include boolean etc

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

Branch: refs/heads/master
Commit: 0aba4ffd1789b3591d70b0975e0a8088fa5d7129
Parents: f4a6bb8
Author: Aled Sage <al...@gmail.com>
Authored: Tue May 17 10:53:11 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue May 17 10:53:11 2016 +0100

----------------------------------------------------------------------
 .../java/org/apache/brooklyn/core/objs/BasicSpecParameter.java   | 4 ++++
 1 file changed, 4 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0aba4ffd/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
index ea041b2..89b7cd0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
+++ b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
@@ -186,6 +186,10 @@ public class BasicSpecParameter<T> implements SpecParameter<T>{
         private static final String DEFAULT_TYPE = "string";
         private static final Map<String, Class<?>> BUILT_IN_TYPES = ImmutableMap.<String, Class<?>>builder()
                 .put(DEFAULT_TYPE, String.class)
+                .put("boolean", Boolean.class)
+                .put("byte", Byte.class)
+                .put("char", Character.class)
+                .put("short", Short.class)
                 .put("integer", Integer.class)
                 .put("long", Long.class)
                 .put("float", Float.class)


[4/4] brooklyn-server git commit: This closes #142

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


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

Branch: refs/heads/master
Commit: dee1074475d4837997504265478afca08f3e1dd8
Parents: 6944ac5 4f98035
Author: Aled Sage <al...@gmail.com>
Authored: Thu May 19 16:39:57 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Thu May 19 16:39:57 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/core/objs/BasicSpecParameter.java  |   4 +
 .../test/framework/TestEffectorImpl.java        |   2 +-
 .../test/framework/TestEndpointReachable.java   | 103 ++++++++
 .../framework/TestEndpointReachableImpl.java    | 223 ++++++++++++++++
 .../test/framework/TestHttpCallImpl.java        |   2 +-
 .../brooklyn/test/framework/TestSensorImpl.java |   2 +-
 .../framework/TestEndpointReachableTest.java    | 252 +++++++++++++++++++
 7 files changed, 585 insertions(+), 3 deletions(-)
----------------------------------------------------------------------



[3/4] brooklyn-server git commit: TestEndpointReachable: incorporate review comments PR #142

Posted by al...@apache.org.
TestEndpointReachable: incorporate review comments PR #142


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

Branch: refs/heads/master
Commit: 4f98035f21c2fba001b88317a4dca1209373505b
Parents: 9337ebf
Author: Aled Sage <al...@gmail.com>
Authored: Thu May 19 14:25:22 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Thu May 19 15:01:08 2016 +0100

----------------------------------------------------------------------
 .../test/framework/TestEffectorImpl.java        |  2 +-
 .../test/framework/TestEndpointReachable.java   | 31 ++++++++--
 .../framework/TestEndpointReachableImpl.java    | 53 ++++++++++++++--
 .../test/framework/TestHttpCallImpl.java        |  2 +-
 .../brooklyn/test/framework/TestSensorImpl.java |  2 +-
 .../framework/TestEndpointReachableTest.java    | 64 ++++++++++++++------
 6 files changed, 123 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
index c00bef2..bd94ad8 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
@@ -101,7 +101,7 @@ public class TestEffectorImpl extends TargetableTestComponentImpl implements Tes
      * {@inheritDoc}
      */
     public void stop() {
-        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         sensors().set(SERVICE_UP, false);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
index 8fdcfd7..425b348 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
@@ -51,12 +51,7 @@ import org.apache.brooklyn.core.config.ConfigKeys;
  * the target entity):
  * <pre>
  * {@code
- * services:
- * - type: MyEntityUnderTest
- *   id: entity-under-test
- * - type: org.apache.brooklyn.test.framework.TestCase
- *   name: Tests
- *   brooklyn.children:
+ *   ...
  *   - type: org.apache.brooklyn.test.framework.TestEndpointReachable
  *     name: Endpoint reachable
  *     brooklyn.config:
@@ -69,6 +64,24 @@ import org.apache.brooklyn.core.config.ConfigKeys;
  *         - $brooklyn:entity("entity-under-test").attributeWhenReady("https.port")
  * }
  * </pre>
+ * 
+ * One can also assert that the given endpoint is not reachable. Here the timeout means that at 
+ * some point within this timeout period, we expect the endpoint to become unreachable. As soon
+ * as it is unreachable, we return:
+ * 
+ * <pre>
+ * {@code
+ *   ...
+ *   - type: org.apache.brooklyn.test.framework.TestEndpointReachable
+ *     name: Endpoint reachable
+ *     brooklyn.config:
+ *       targetId: entity-under-test
+ *       timeout: 2m
+ *       endpointSensor: datastore.url
+ *       assertions:
+ *         reachable: false
+ * }
+ * </pre>
  */
 @ImplementedBy(value = TestEndpointReachableImpl.class)
 public interface TestEndpointReachable extends BaseTest {
@@ -81,4 +94,10 @@ public interface TestEndpointReachable extends BaseTest {
             Object.class,
             "endpointSensor", 
             "Sensor (or name of sensor) on target that advertises the endpoint (to test for tcp-reachability); mutually exclusive with 'endpoint'");
+    
+    /**
+     * A key within the assertions map, to say whether we should assert that the endpoint is reachable or not reachable.
+     * The value in the map should be a boolean. If absent, defaults to true.
+     */
+    public static final String REACHABLE_KEY = "reachable";
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
index d759507..913be27 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
@@ -25,6 +25,7 @@ import java.net.URL;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
@@ -34,19 +35,21 @@ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport;
-import org.apache.brooklyn.test.framework.TestHttpCall.HttpAssertionTarget;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.common.net.HostAndPort;
 
 /**
@@ -65,6 +68,8 @@ public class TestEndpointReachableImpl extends TargetableTestComponentImpl imple
         final String endpoint = getConfig(ENDPOINT);
         final Object endpointSensor = getConfig(ENDPOINT_SENSOR);
         final Duration timeout = getConfig(TIMEOUT);
+        final List<Map<String, Object>> assertions = getAssertions(this, ASSERTIONS);
+        
         final Entity target = resolveTarget();
 
         if (endpoint == null && endpointSensor == null) {
@@ -101,7 +106,7 @@ public class TestEndpointReachableImpl extends TargetableTestComponentImpl imple
                 public void run() {
                     HostAndPort val = supplier.get();
                     Asserts.assertNotNull(val);
-                    Asserts.assertTrue(Networking.isReachable(val), val+" not reachable");
+                    assertSucceeds(assertions, val);
                 }});
             sensors().set(Attributes.SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
@@ -114,6 +119,44 @@ public class TestEndpointReachableImpl extends TargetableTestComponentImpl imple
         }
     }
 
+    protected void assertSucceeds(List<Map<String, Object>> assertions, HostAndPort endpoint) {
+        Maybe<Object> checkReachableMaybe = getOnlyAssertionsValue(assertions, REACHABLE_KEY);
+        boolean checkReachable = checkReachableMaybe.isAbsentOrNull() || Boolean.TRUE.equals(TypeCoercions.coerce(checkReachableMaybe.get(), Boolean.class));
+        boolean reachable = Networking.isReachable(endpoint);
+        Asserts.assertEquals(reachable, checkReachable, endpoint+" "+(reachable ? "" : "not ")+"reachable");
+    }
+    
+    /**
+     * Finds the value for the given key in one of the maps (or {@link Maybe#absent()} if not found).
+     * 
+     * @throws IllegalArgumentException if multiple conflicts values for the key, or if there are other (unexpected) keys.
+     */
+    protected Maybe<Object> getOnlyAssertionsValue(List<Map<String, Object>> assertions, String key) {
+        Maybe<Object> result = Maybe.absent();
+        Set<String> keys = Sets.newLinkedHashSet();
+        boolean foundConflictingDuplicate = false;
+        if (assertions != null) {
+            for (Map<String, Object> assertionMap : assertions) {
+                if (assertionMap.containsKey(key)) {
+                    Object val = assertionMap.get(REACHABLE_KEY);
+                    if (result.isPresent() && !Objects.equal(result.get(), val)) {
+                        foundConflictingDuplicate = true;
+                    } else {
+                        result = Maybe.of(val);
+                    }
+                }
+                keys.addAll(assertionMap.keySet());
+            }
+        }
+        Set<String> unhandledKeys = Sets.difference(keys, ImmutableSet.of(key));
+        if (foundConflictingDuplicate) {
+            throw new IllegalArgumentException("Multiple conflicting values for assertion '"+key+"' in "+this);
+        } else if (unhandledKeys.size() > 0) {
+            throw new IllegalArgumentException("Unknown assertions "+unhandledKeys+" in "+this);
+        }
+        return result;
+    }
+    
     protected HostAndPort toHostAndPort(Object endpoint) {
         if (endpoint == null) {
             throw new IllegalArgumentException(String.format("The entity [%s] has no endpoint", getClass().getName()));
@@ -164,7 +207,7 @@ public class TestEndpointReachableImpl extends TargetableTestComponentImpl imple
      * {@inheritDoc}
      */
     public void stop() {
-        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         sensors().set(Attributes.SERVICE_UP, false);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
index e3e4456..45b04d0 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
@@ -107,7 +107,7 @@ public class TestHttpCallImpl extends TargetableTestComponentImpl implements Tes
      * {@inheritDoc}
      */
     public void stop() {
-        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         sensors().set(Attributes.SERVICE_UP, false);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
index 633e982..df50064 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
@@ -86,7 +86,7 @@ public class TestSensorImpl extends TargetableTestComponentImpl implements TestS
      * {@inheritDoc}
      */
     public void stop() {
-        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED);
         sensors().set(SERVICE_UP, false);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/4f98035f/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
index 20391df..efeb7eb 100644
--- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
@@ -34,7 +34,6 @@ import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
 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.slf4j.Logger;
@@ -45,6 +44,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.net.HostAndPort;
 
 public class TestEndpointReachableTest extends BrooklynAppUnitTestSupport {
@@ -138,6 +138,50 @@ public class TestEndpointReachableTest extends BrooklynAppUnitTestSupport {
         app.start(ImmutableList.of(loc));
     }
 
+    @Test
+    public void testHardcodedEndpointReachableWithExplicitAssertionForReachable() throws Exception {
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, serverSocketHostAndPort.toString())
+                .configure(TestEndpointReachable.ASSERTIONS, ImmutableMap.of(TestEndpointReachable.REACHABLE_KEY, "true")));
+
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testExplicitAssertionForNotReachableWhenReachable() throws Exception {
+        TestEndpointReachable test = app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, serverSocketHostAndPort.toString())
+                .configure(TestEndpointReachable.TIMEOUT, Duration.millis(100))
+                .configure(TestEndpointReachable.ASSERTIONS, ImmutableMap.of(TestEndpointReachable.REACHABLE_KEY, "false")));
+
+        try {
+            app.start(ImmutableList.of(loc));
+        } catch (Exception e) {
+            // It should fail to start
+            Exceptions.propagateIfFatal(e);
+            LOG.debug("As desired, failed to start app with TestEndpointReachable: "+e.toString());
+        }
+        EntityAsserts.assertAttributeEqualsEventually(test, TestEndpointReachable.SERVICE_UP, false);
+        EntityAsserts.assertAttributeEqualsEventually(test, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+    }
+
+    // Not a normal unit-test, because other processes on shared infrastructure can grab the port between
+    // us identifying it is free and us checking the reachability.
+    @Test(groups="Integration")
+    public void testExplicitAssertionForNotReachableWhenNotReachable() throws Exception {
+        HostAndPort unusedPort = findUnusedPort();
+        TestEndpointReachable test = app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, unusedPort.toString())
+                .configure(TestEndpointReachable.ASSERTIONS, ImmutableMap.of(TestEndpointReachable.REACHABLE_KEY, "false")));
+
+        app.start(ImmutableList.of(loc));
+        EntityAsserts.assertAttributeEqualsEventually(test, TestEndpointReachable.SERVICE_UP, true);
+        EntityAsserts.assertAttributeEqualsEventually(test, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
     // Not a normal unit-test, because other processes on shared infrastructure can grab the port between
     // us identifying it is free and us checking the reachability.
     @Test(groups="Integration")
@@ -182,23 +226,9 @@ public class TestEndpointReachableTest extends BrooklynAppUnitTestSupport {
         EntityAsserts.assertAttributeEqualsEventually(test, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
     }
 
-    protected ServerSocket openServerPort() {
-        int startPort = 58767;
-        int endPort = 60000;
-        int port = startPort;
+    protected ServerSocket openServerPort() throws IOException {
         InetAddress localAddress = Networking.getLocalHost();
-        do {
-            try {
-                ServerSocket ss = new ServerSocket(port, 1024, localAddress);
-                LOG.info("acquired server-socket on port "+port+" for test "+JavaClassNames.niceClassAndMethod());
-                return ss;
-            } catch (IOException e) {
-                // try next port
-                port++;
-            }
-        } while (port <= endPort);
-        Assert.fail("Could not open any local port in range "+startPort+"-"+endPort);
-        throw new IllegalStateException("Impossible code to reach");
+        return new ServerSocket(0, 1024, localAddress);
     }
 
     protected HostAndPort findUnusedPort() {


[2/4] brooklyn-server git commit: Adds TestEndpointReachable (for yaml test-cases)

Posted by al...@apache.org.
Adds TestEndpointReachable (for yaml test-cases)

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

Branch: refs/heads/master
Commit: 9337ebff6ce5c071a9b9585708010ed1f8b3bc2a
Parents: 0aba4ff
Author: Aled Sage <al...@gmail.com>
Authored: Tue May 17 11:14:36 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue May 17 17:27:06 2016 +0100

----------------------------------------------------------------------
 .../test/framework/TestEndpointReachable.java   |  84 +++++++
 .../framework/TestEndpointReachableImpl.java    | 180 +++++++++++++++
 .../framework/TestEndpointReachableTest.java    | 222 +++++++++++++++++++
 3 files changed, 486 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9337ebff/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
new file mode 100644
index 0000000..8fdcfd7
--- /dev/null
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachable.java
@@ -0,0 +1,84 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+
+/**
+ * Entity that checks if a TCP endpoint is reachable.
+ * 
+ * For example:
+ * <pre>
+ * {@code
+ * services:
+ * - type: com.acme.MyEntityUnderTest
+ *   id: entity-under-test
+ * - type: org.apache.brooklyn.test.framework.TestCase
+ *   name: Tests
+ *   brooklyn.children:
+ *   - type: org.apache.brooklyn.test.framework.TestEndpointReachable
+ *     name: Endpoint reachable
+ *     brooklyn.config:
+ *       targetId: entity-under-test
+ *       timeout: 2m
+ *       endpointSensor: datastore.url
+ * }
+ * </pre>
+ * 
+ * The sensor's value can be in a number of different formats: a string in the form of {@code ip:port}
+ * or URI format; or a {@link com.google.common.net.HostAndPort}; or a {@link java.net.URI}; or a 
+ * {@link java.net.URL} instance.
+ * 
+ * Alternatively an explicit endpoint can be used (e.g. constructed from other sensors of
+ * the target entity):
+ * <pre>
+ * {@code
+ * services:
+ * - type: MyEntityUnderTest
+ *   id: entity-under-test
+ * - type: org.apache.brooklyn.test.framework.TestCase
+ *   name: Tests
+ *   brooklyn.children:
+ *   - type: org.apache.brooklyn.test.framework.TestEndpointReachable
+ *     name: Endpoint reachable
+ *     brooklyn.config:
+ *       targetId: entity-under-test
+ *       timeout: 2m
+ *       endpoint:
+ *         $brooklyn:formatString:
+ *         - %s:%s"
+ *         - $brooklyn:entity("entity-under-test").attributeWhenReady("host.name")
+ *         - $brooklyn:entity("entity-under-test").attributeWhenReady("https.port")
+ * }
+ * </pre>
+ */
+@ImplementedBy(value = TestEndpointReachableImpl.class)
+public interface TestEndpointReachable extends BaseTest {
+
+    ConfigKey<String> ENDPOINT = ConfigKeys.newStringConfigKey(
+            "endpoint", 
+            "Endpoint (be it URL or host:port) to test, for tcp-reachability; mutually exclusive with 'endpointSensor'");
+
+    ConfigKey<Object> ENDPOINT_SENSOR = ConfigKeys.newConfigKey(
+            Object.class,
+            "endpointSensor", 
+            "Sensor (or name of sensor) on target that advertises the endpoint (to test for tcp-reachability); mutually exclusive with 'endpoint'");
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9337ebff/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
new file mode 100644
index 0000000..d759507
--- /dev/null
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEndpointReachableImpl.java
@@ -0,0 +1,180 @@
+/*
+ * 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 static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport;
+import org.apache.brooklyn.test.framework.TestHttpCall.HttpAssertionTarget;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.net.HostAndPort;
+
+/**
+ * {@inheritDoc}
+ */
+public class TestEndpointReachableImpl extends TargetableTestComponentImpl implements TestEndpointReachable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestEndpointReachableImpl.class);
+
+    @Override
+    public void start(Collection<? extends Location> locations) {
+        if (!getChildren().isEmpty()) {
+            throw new RuntimeException(String.format("The entity [%s] cannot have child entities", getClass().getName()));
+        }
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
+        final String endpoint = getConfig(ENDPOINT);
+        final Object endpointSensor = getConfig(ENDPOINT_SENSOR);
+        final Duration timeout = getConfig(TIMEOUT);
+        final Entity target = resolveTarget();
+
+        if (endpoint == null && endpointSensor == null) {
+            throw new RuntimeException(String.format("The entity [%s] must be configured with one of endpoint or endpointSensor", getClass().getName()));
+        } else if (endpoint != null && endpointSensor != null) {
+            throw new RuntimeException(String.format("The entity [%s] must be configured with only one of endpoint or endpointSensor", getClass().getName()));
+        }
+        
+        final Supplier<HostAndPort> supplier = new Supplier<HostAndPort>() {
+            @Override
+            public HostAndPort get() {
+                Object val;
+                if (endpoint != null) {
+                    val = endpoint;
+                } else if (endpointSensor instanceof AttributeSensor) {
+                    val = target.sensors().get((AttributeSensor<?>)endpointSensor);
+                } else if (endpointSensor instanceof CharSequence) {
+                    AttributeSensor<Object> sensor = Sensors.newSensor(Object.class, ((CharSequence)endpointSensor).toString());
+                    val = target.sensors().get(sensor);
+                } else {
+                    throw new IllegalArgumentException(String.format("The entity [%s] has endpointSensor of invalid type %s [%s]", getClass().getName(), endpointSensor.getClass().getName(), endpointSensor));
+                }
+                return (val == null) ? null : toHostAndPort(val);
+            }
+        };
+        if (endpoint != null) {
+            // fail-fast if have a static endpoint value
+            supplier.get();
+        }
+
+        try {
+            Asserts.succeedsEventually(ImmutableMap.of("timeout", timeout), new Runnable() {
+                @Override
+                public void run() {
+                    HostAndPort val = supplier.get();
+                    Asserts.assertNotNull(val);
+                    Asserts.assertTrue(Networking.isReachable(val), val+" not reachable");
+                }});
+            sensors().set(Attributes.SERVICE_UP, true);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+
+        } catch (Throwable t) {
+            LOG.info("{} [{}] test failed", this, endpoint != null ? endpoint : endpointSensor);
+            sensors().set(Attributes.SERVICE_UP, false);
+            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    protected HostAndPort toHostAndPort(Object endpoint) {
+        if (endpoint == null) {
+            throw new IllegalArgumentException(String.format("The entity [%s] has no endpoint", getClass().getName()));
+        } else if (endpoint instanceof String) {
+            return toHostAndPort((String)endpoint);
+        } else if (endpoint instanceof URI) {
+            return toHostAndPort(((URI)endpoint).toString());
+        } else if (endpoint instanceof URL) {
+            return toHostAndPort(((URL)endpoint).toString());
+        } else if (endpoint instanceof HostAndPort) {
+            return (HostAndPort)endpoint;
+        } else {
+            throw new IllegalArgumentException(String.format("The entity [%s] has endpoint of invalid type %s [%s]", getClass().getName(), endpoint.getClass().getName(), endpoint));
+        }
+    }
+    
+    protected HostAndPort toHostAndPort(String endpoint) {
+        if (Strings.isEmpty(endpoint)) {
+            throw new IllegalArgumentException(String.format("The entity [%s] has no endpoint", getClass().getName()));
+        }
+        try {
+            URI uri = URI.create(endpoint);
+            int port;
+            if (uri.getPort() != -1) {
+                port = uri.getPort();
+            } else {
+                if ("http".equalsIgnoreCase(uri.getScheme())) {
+                    port = 80;
+                } else if ("https".equalsIgnoreCase(uri.getScheme())) {
+                    port = 443;
+                } else {
+                    throw new IllegalArgumentException(String.format("The entity [%s] with endpoint [%s] has no port", getClass().getName(), endpoint));
+                }
+            }
+            return HostAndPort.fromParts(uri.getHost(), port);
+        } catch (IllegalArgumentException e) {
+            // Not a URL; fall-back to host-and-port
+        }
+        
+        HostAndPort result = HostAndPort.fromString(endpoint);
+        if (!result.hasPort()) {
+            throw new IllegalArgumentException(String.format("The entity [%s] with endpoint [%s] has no port", getClass().getName(), endpoint));
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stop() {
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+        sensors().set(Attributes.SERVICE_UP, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void restart() {
+        final Collection<Location> locations = Lists.newArrayList(getLocations());
+        stop();
+        start(locations);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9337ebff/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
new file mode 100644
index 0000000..20391df
--- /dev/null
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEndpointReachableTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URL;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+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.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.ImmutableList;
+import com.google.common.net.HostAndPort;
+
+public class TestEndpointReachableTest extends BrooklynAppUnitTestSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestEndpointReachableTest.class);
+
+    private LocalhostMachineProvisioningLocation loc;
+    private ServerSocket serverSocket;
+    private HostAndPort serverSocketHostAndPort;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.newLocalhostProvisioningLocation();
+        serverSocket = openServerPort();
+        serverSocketHostAndPort = toHostAndPort(serverSocket);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            if (serverSocket != null) serverSocket.close();
+        } catch (IOException e) {
+            // keep going; stop the other server sockets etc.
+            LOG.warn("Error closing server socket "+serverSocket+"; continuing", e);
+        }
+        super.tearDown();
+    }
+
+    @Test
+    public void testHardcodedEndpointReachable() throws Exception {
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, serverSocketHostAndPort.toString()));
+
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSensorHostAndPortStringReachable() throws Exception {
+        AttributeSensor<String> sensor = Sensors.newStringSensor("test.reachable.endpoint");
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT_SENSOR, sensor));
+        app.sensors().set(sensor, serverSocketHostAndPort.toString());
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSensorHostAndPortReachable() throws Exception {
+        AttributeSensor<HostAndPort> sensor = Sensors.newSensor(HostAndPort.class, "test.reachable.endpoint");
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT_SENSOR, sensor));
+        app.sensors().set(sensor, serverSocketHostAndPort);
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSensorUriStringReachable() throws Exception {
+        String sensorVal = "http://"+serverSocketHostAndPort.getHostText()+":"+serverSocketHostAndPort.getPort();
+        AttributeSensor<String> sensor = Sensors.newStringSensor("test.reachable.endpoint");
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT_SENSOR, sensor));
+        app.sensors().set(sensor, sensorVal);
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSensorUriReachable() throws Exception {
+        URI sensorVal = URI.create("http://"+serverSocketHostAndPort.getHostText()+":"+serverSocketHostAndPort.getPort());
+        AttributeSensor<URI> sensor = Sensors.newSensor(URI.class, "test.reachable.endpoint");
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT_SENSOR, sensor));
+        app.sensors().set(sensor, sensorVal);
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSensorUrlReachable() throws Exception {
+        URL sensorVal = new URL("http://"+serverSocketHostAndPort.getHostText()+":"+serverSocketHostAndPort.getPort());
+        AttributeSensor<URL> sensor = Sensors.newSensor(URL.class, "test.reachable.endpoint");
+        app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT_SENSOR, sensor));
+        app.sensors().set(sensor, sensorVal);
+        app.start(ImmutableList.of(loc));
+    }
+
+    // Not a normal unit-test, because other processes on shared infrastructure can grab the port between
+    // us identifying it is free and us checking the reachability.
+    @Test(groups="Integration")
+    public void testHardcodedEndpointNotReachable() throws Exception {
+        HostAndPort unusedPort = findUnusedPort();
+        TestEndpointReachable test = app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, unusedPort.toString())
+                .configure(TestEndpointReachable.TIMEOUT, Duration.millis(100)));
+
+        try {
+            app.start(ImmutableList.of(loc));
+        } catch (Exception e) {
+            // It should fail to start
+            Exceptions.propagateIfFatal(e);
+            LOG.debug("As desired, failed to start app with TestEndpointReachable: "+e.toString());
+        }
+        EntityAsserts.assertAttributeEqualsEventually(test, TestEndpointReachable.SERVICE_UP, false);
+        EntityAsserts.assertAttributeEqualsEventually(test, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+    }
+
+    // Not a normal unit-test, because other processes on shared infrastructure can grab the port between
+    // us identifying it is free and us checking the reachability.
+    @Test(groups="Integration")
+    public void testSensorEndpointNotReachable() throws Exception {
+        AttributeSensor<String> sensor = Sensors.newStringSensor("test.reachable.endpoint");
+        HostAndPort unusedPort = findUnusedPort();
+        TestEndpointReachable test = app.createAndManageChild(EntitySpec.create(TestEndpointReachable.class)
+                .configure(TestEndpointReachable.TARGET_ENTITY, app)
+                .configure(TestEndpointReachable.ENDPOINT, unusedPort.toString())
+                .configure(TestEndpointReachable.TIMEOUT, Duration.millis(100)));
+        app.sensors().set(sensor, unusedPort.toString());
+        
+        try {
+            app.start(ImmutableList.of(loc));
+        } catch (Exception e) {
+            // It should fail to start
+            Exceptions.propagateIfFatal(e);
+            LOG.debug("As desired, failed to start app with TestEndpointReachable: "+e.toString());
+        }
+        EntityAsserts.assertAttributeEqualsEventually(test, TestEndpointReachable.SERVICE_UP, false);
+        EntityAsserts.assertAttributeEqualsEventually(test, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+    }
+
+    protected ServerSocket openServerPort() {
+        int startPort = 58767;
+        int endPort = 60000;
+        int port = startPort;
+        InetAddress localAddress = Networking.getLocalHost();
+        do {
+            try {
+                ServerSocket ss = new ServerSocket(port, 1024, localAddress);
+                LOG.info("acquired server-socket on port "+port+" for test "+JavaClassNames.niceClassAndMethod());
+                return ss;
+            } catch (IOException e) {
+                // try next port
+                port++;
+            }
+        } while (port <= endPort);
+        Assert.fail("Could not open any local port in range "+startPort+"-"+endPort);
+        throw new IllegalStateException("Impossible code to reach");
+    }
+
+    protected HostAndPort findUnusedPort() {
+        int startPort = 58767;
+        int endPort = 60000;
+        int port = startPort;
+        InetAddress localAddress = Networking.getLocalHost();
+        do {
+            if (Networking.isPortAvailable(localAddress, port)) {
+                return HostAndPort.fromParts(localAddress.getHostAddress(), port);
+            }
+            port++;
+        } while (port <= endPort);
+        Assert.fail("Could not find available port in range "+startPort+"-"+endPort);
+        throw new IllegalStateException("Impossible code to reach");
+    }
+
+    protected HostAndPort toHostAndPort(ServerSocket ss) {
+        return HostAndPort.fromParts(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
+    }
+}