brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [23/34] brooklyn-server git commit: REST API optional Jersey compatibility
Date Thu, 18 Feb 2016 15:47:41 GMT
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
new file mode 100644
index 0000000..014053f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+@Test(singleThreaded = true)
+public class EntityConfigResourceTest extends BrooklynRestResourceTest {
+    
+    private final static Logger log = LoggerFactory.getLogger(EntityConfigResourceTest.class);
+    private URI applicationUri;
+    private EntityInternal entity;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp(); // We require that the superclass setup is done first, as we will be calling out to Jersey
+
+        // Deploy an application that we'll use to read the configuration of
+        final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+                  entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName(), ImmutableMap.of("install.version", "1.0.0")))).
+                  locations(ImmutableSet.of("localhost")).
+                  build();
+        
+        ClientResponse response = clientDeploy(simpleSpec);
+        int status = response.getStatus();
+        assertTrue(status >= 200 && status <= 299, "expected HTTP Response of 2xx but got " + status);
+        applicationUri = response.getLocation();
+        log.debug("Built app: application");
+        waitForApplicationToBeRunning(applicationUri);
+        
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+    }
+
+    @Test
+    public void testList() throws Exception {
+        List<EntityConfigSummary> entityConfigSummaries = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config"))
+                .get(new GenericType<List<EntityConfigSummary>>() {
+                });
+        
+        // Default entities have over a dozen config entries, but it's unnecessary to test them all; just pick one
+        // representative config key
+        Optional<EntityConfigSummary> configKeyOptional = Iterables.tryFind(entityConfigSummaries, new Predicate<EntityConfigSummary>() {
+            @Override
+            public boolean apply(@Nullable EntityConfigSummary input) {
+                return input != null && "install.version".equals(input.getName());
+            }
+        });
+        assertTrue(configKeyOptional.isPresent());
+        
+        assertEquals(configKeyOptional.get().getType(), "java.lang.String");
+        assertEquals(configKeyOptional.get().getDescription(), "Suggested version");
+        assertFalse(configKeyOptional.get().isReconfigurable());
+        assertNull(configKeyOptional.get().getDefaultValue());
+        assertNull(configKeyOptional.get().getLabel());
+        assertNull(configKeyOptional.get().getPriority());
+    }
+
+    @Test
+    public void testBatchConfigRead() throws Exception {
+        Map<String, Object> currentState = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/current-state"))
+                .get(new GenericType<Map<String, Object>>() {
+                });
+        assertTrue(currentState.containsKey("install.version"));
+        assertEquals(currentState.get("install.version"), "1.0.0");
+    }
+
+    @Test
+    public void testGetJson() throws Exception {
+        String configValue = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "\"1.0.0\"");
+    }
+
+    @Test
+    public void testGetPlain() throws Exception {
+        String configValue = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.TEXT_PLAIN_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "1.0.0");
+    }
+
+    @Test
+    public void testSet() throws Exception {
+        try {
+            String uri = "/v1/applications/simple-app/entities/simple-ent/config/"+
+                RestMockSimpleEntity.SAMPLE_CONFIG.getName();
+            ClientResponse response = client().resource(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, "\"hello world\"");
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().resource(uri).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            String uri = "/v1/applications/simple-app/entities/simple-ent/config";
+            ClientResponse response = client().resource(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, MutableMap.of(
+                    RestMockSimpleEntity.SAMPLE_CONFIG.getName(), "hello world"));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().resource(uri+"/"+RestMockSimpleEntity.SAMPLE_CONFIG.getName()).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
new file mode 100644
index 0000000..1c091da
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.rest.resources;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+
+@Test(singleThreaded = true)
+public class EntityResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityResourceTest.class);
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    private EntityInternal entity;
+
+    private static final String entityEndpoint = "/v1/applications/simple-app/entities/simple-ent";
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Deploy application
+        ClientResponse deploy = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        // Add tag
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), new Predicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return "RestMockSimpleEntity".equals(input.getEntityType().getSimpleName());
+            }
+        });
+    }
+
+    @Test
+    public void testTagsSanity() throws Exception {
+        entity.tags().addTag("foo");
+        
+        ClientResponse response = client().resource(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(ClientResponse.class);
+        String data = response.getEntity(String.class);
+        
+        try {
+            List<Object> tags = new ObjectMapper().readValue(data, new TypeReference<List<Object>>() {});
+            Assert.assertTrue(tags.contains("foo"));
+            Assert.assertFalse(tags.contains("bar"));
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Error with deserialization of tags list: "+e+"\n"+data, e);
+        }
+    }
+    
+    @Test
+    public void testRename() throws Exception {
+        try {
+            ClientResponse response = client().resource(entityEndpoint + "/name")
+                .queryParam("name", "New Name")
+                .post(ClientResponse.class);
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertTrue(entity.getDisplayName().equals("New Name"));
+        } finally {
+            // restore it for other tests!
+            entity.setDisplayName("simple-ent");
+        }
+    }
+    
+    @Test
+    public void testAddChild() throws Exception {
+        try {
+            // to test in GUI: 
+            // services: [ { type: org.apache.brooklyn.entity.stock.BasicEntity }]
+            ClientResponse response = client().resource(entityEndpoint + "/children?timeout=10s")
+                .entity("services: [ { type: "+TestEntity.class.getName()+" }]", "application/yaml")
+                .post(ClientResponse.class);
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertEquals(entity.getChildren().size(), 1);
+            Entity child = Iterables.getOnlyElement(entity.getChildren());
+            Assert.assertTrue(Entities.isManaged(child));
+            
+            TaskSummary task = response.getEntity(TaskSummary.class);
+            Assert.assertEquals(task.getResult(), MutableList.of(child.getId()));
+            
+        } finally {
+            // restore it for other tests
+            Collection<Entity> children = entity.getChildren();
+            if (!children.isEmpty()) Entities.unmanage(Iterables.getOnlyElement(children));
+        }
+    }
+    
+    @Test
+    public void testTagsDoNotSerializeTooMuch() throws Exception {
+        entity.tags().addTag("foo");
+        entity.tags().addTag(entity.getParent());
+
+        ClientResponse response = client().resource(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        String raw = response.getEntity(String.class);
+        log.info("TAGS raw: "+raw);
+        HttpAsserts.assertHealthyStatusCode(response.getStatus());
+        
+        Assert.assertTrue(raw.contains(entity.getParent().getId()), "unexpected app tag, does not include ID: "+raw);
+        
+        Assert.assertTrue(raw.length() < 1000, "unexpected app tag, includes too much mgmt info (len "+raw.length()+"): "+raw);
+        
+        Assert.assertFalse(raw.contains(entity.getManagementContext().getManagementNodeId()), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("managementContext"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("localhost"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("catalog"), "unexpected app tag, includes too much mgmt info: "+raw);
+
+        @SuppressWarnings("unchecked")
+        List<Object> tags = mapper().readValue(raw, List.class);
+        log.info("TAGS are: "+tags);
+        
+        Assert.assertEquals(tags.size(), 2, "tags are: "+tags);
+
+        Assert.assertTrue(tags.contains("foo"));
+        Assert.assertFalse(tags.contains("bar"));
+        
+        MutableList<Object> appTags = MutableList.copyOf(tags);
+        appTags.remove("foo");
+        Object appTag = Iterables.getOnlyElement( appTags );
+        
+        // it's a map at this point, because there was no way to make it something stronger than Object
+        Assert.assertTrue(appTag instanceof Map, "Should have deserialized an entity: "+appTag);
+        // let's re-serialize it as an entity
+        appTag = mapper().readValue(mapper().writeValueAsString(appTag), Entity.class);
+        
+        Assert.assertTrue(appTag instanceof Entity, "Should have deserialized an entity: "+appTag);
+        Assert.assertEquals( ((Entity)appTag).getId(), entity.getApplicationId(), "Wrong ID: "+appTag);
+        Assert.assertTrue(appTag instanceof BasicApplication, "Should have deserialized BasicApplication: "+appTag);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
new file mode 100644
index 0000000..0979875
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.sun.jersey.api.client.ClientResponse;
+
+public class ErrorResponseTest extends BrooklynRestResourceTest {
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+    private String policyId;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        ClientResponse aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
+
+        ClientResponse pResponse = client().resource(policiesEndpoint)
+                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, Maps.newHashMap());
+        PolicySummary response = pResponse.getEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+    }
+
+    @Test
+    public void testResponseToBadRequest() {
+        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        ClientResponse response = client().resource(resource)
+                .queryParam("value", "notanumber")
+                .post(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Status.BAD_REQUEST.getStatusCode());
+        assertEquals(response.getHeaders().getFirst("Content-Type"), MediaType.APPLICATION_JSON);
+
+        ApiError error = response.getEntity(ApiError.class);
+        assertTrue(error.getMessage().toLowerCase().contains("cannot coerce"));
+    }
+
+    @Test
+    public void testResponseToWrongMethod() {
+        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        // Should be POST, not GET
+        ClientResponse response = client().resource(resource)
+                .queryParam("value", "4")
+                .get(ClientResponse.class);
+
+        assertEquals(response.getStatus(), 405);
+        // Can we assert anything about the content type?
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
new file mode 100644
index 0000000..fe78b83
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.location.SimulatedLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+
+@Test(singleThreaded = true)
+public class LocationResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class);
+    private String legacyLocationName = "my-jungle-legacy";
+    private String legacyLocationVersion = "0.0.0.SNAPSHOT";
+    
+    private String locationName = "my-jungle";
+    private String locationVersion = "0.1.2";
+    
+    @Test
+    @Deprecated
+    public void testAddLegacyLocationDefinition() {
+        Map<String, String> expectedConfig = ImmutableMap.of(
+                "identity", "bob",
+                "credential", "CR3dential");
+        ClientResponse response = client().resource("/v1/locations")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, new org.apache.brooklyn.rest.domain.LocationSpec(legacyLocationName, "aws-ec2:us-east-1", expectedConfig));
+
+        URI addedLegacyLocationUri = response.getLocation();
+        log.info("added legacy, at: " + addedLegacyLocationUri);
+        LocationSummary location = client().resource(response.getLocation()).get(LocationSummary.class);
+        log.info(" contents: " + location);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion);
+        assertTrue(addedLegacyLocationUri.getPath().startsWith("/v1/locations/"));
+
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(legacyLocationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testAddNewLocationDefinition() {
+        String yaml = Joiner.on("\n").join(ImmutableList.of(
+                "brooklyn.catalog:",
+                "  symbolicName: "+locationName,
+                "  version: " + locationVersion,
+                "",
+                "brooklyn.locations:",
+                "- type: "+"aws-ec2:us-east-1",
+                "  brooklyn.config:",
+                "    identity: bob",
+                "    credential: CR3dential"));
+
+        
+        ClientResponse response = client().resource("/v1/catalog")
+                .post(ClientResponse.class, yaml);
+
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+        
+
+        URI addedCatalogItemUri = response.getLocation();
+        log.info("added, at: " + addedCatalogItemUri);
+        
+        // Ensure location definition exists
+        CatalogLocationSummary locationItem = client().resource("/v1/catalog/locations/"+locationName + "/" + locationVersion)
+                .get(CatalogLocationSummary.class);
+        log.info(" item: " + locationItem);
+        LocationSummary locationSummary = client().resource(URI.create("/v1/locations/"+locationName+"/")).get(LocationSummary.class);
+        log.info(" summary: " + locationSummary);
+        Assert.assertEquals(locationSummary.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+
+        // Ensure location is usable - can instantiate, and has right config
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(locationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testAddNewLocationDefinition" })
+    public void testListAllLocationDefinitions() {
+        Set<LocationSummary> locations = client().resource("/v1/locations")
+                .get(new GenericType<Set<LocationSummary>>() {});
+        Iterable<LocationSummary> matching = Iterables.filter(locations, new Predicate<LocationSummary>() {
+            @Override
+            public boolean apply(@Nullable LocationSummary l) {
+                return locationName.equals(l.getName());
+            }
+        });
+        LocationSummary location = Iterables.getOnlyElement(matching);
+        
+        Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+        Assert.assertEquals(location.getLinks().get("self").getPath(), "/v1/locations/"+locationName);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testListAllLocationDefinitions" })
+    public void testGetSpecificLocation() {
+        URI expectedLocationUri = URI.create("/v1/locations/"+locationName);
+        LocationSummary location = client().resource(expectedLocationUri).get(LocationSummary.class);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetLocationConfig() {
+        SimulatedLocation parentLoc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .configure("myParentKey", "myParentVal"));
+        SimulatedLocation loc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .parent(parentLoc)
+                .configure("mykey", "myval")
+                .configure("password", "mypassword"));
+    
+        // "full" means including-inherited, filtered to exclude secrets
+        URI uriFull = URI.create("/v1/locations/"+loc.getId()+"?full=true");
+        LocationSummary summaryFull = client().resource(uriFull).get(LocationSummary.class);
+        assertEquals(summaryFull.getConfig(), ImmutableMap.of("mykey", "myval", "myParentKey", "myParentVal"), "conf="+summaryFull.getConfig());
+        
+        // Default is local-only, filtered to exclude secrets
+        URI uriDefault = URI.create("/v1/locations/"+loc.getId());
+        LocationSummary summaryDefault = client().resource(uriDefault).get(LocationSummary.class);
+        assertEquals(summaryDefault.getConfig(), ImmutableMap.of("mykey", "myval"), "conf="+summaryDefault.getConfig());
+    }
+
+    @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" })
+    @Deprecated
+    public void testDeleteLocation() {
+        final int size = getLocationRegistry().getDefinedLocations().size();
+        URI expectedLocationUri = URI.create("/v1/locations/"+legacyLocationName);
+
+        ClientResponse response = client().resource(expectedLocationUri).delete(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
new file mode 100644
index 0000000..22dc86d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+@Test(singleThreaded = true)
+public class PolicyResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(PolicyResourceTest.class);
+
+    private static final String ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/policies/";
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+
+    private String policyId;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        ClientResponse aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        ClientResponse pResponse = client().resource(ENDPOINT)
+                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, Maps.newHashMap());
+
+        PolicySummary response = pResponse.getEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+
+    }
+
+    @Test
+    public void testListConfig() throws Exception {
+        Set<PolicyConfigSummary> config = client().resource(ENDPOINT + policyId + "/config")
+                .get(new GenericType<Set<PolicyConfigSummary>>() {});
+        
+        Set<String> configNames = Sets.newLinkedHashSet();
+        for (PolicyConfigSummary conf : config) {
+            configNames.add(conf.getName());
+        }
+
+        assertEquals(configNames, ImmutableSet.of(
+                RestMockSimplePolicy.SAMPLE_CONFIG.getName(),
+                RestMockSimplePolicy.INTEGER_CONFIG.getName()));
+    }
+
+    @Test
+    public void testGetNonExistantConfigReturns404() throws Exception {
+        String invalidConfigName = "doesnotexist";
+        try {
+            PolicyConfigSummary summary = client().resource(ENDPOINT + policyId + "/config/" + invalidConfigName)
+                    .get(PolicyConfigSummary.class);
+            fail("Should have thrown 404, but got "+summary);
+        } catch (Exception e) {
+            if (!e.toString().contains("404")) throw e;
+        }
+    }
+
+    @Test
+    public void testGetDefaultValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = RestMockSimplePolicy.SAMPLE_CONFIG.getDefaultValue();
+        
+        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+    
+    @Test(dependsOnMethods = "testGetDefaultValue")
+    public void testReconfigureConfig() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        
+        ClientResponse response = client().resource(ENDPOINT + policyId + "/config/" + configName + "/set")
+                .queryParam("value", "newval")
+                .post(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+    }
+    
+    @Test(dependsOnMethods = "testReconfigureConfig")
+    public void testGetConfigValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = "newval";
+        
+        Map<String, Object> allState = client().resource(ENDPOINT + policyId + "/config/current-state")
+                .get(new GenericType<Map<String, Object>>() {});
+        assertEquals(allState, ImmutableMap.of(configName, expectedVal));
+        
+        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
new file mode 100644
index 0000000..08b9aa4
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.rest.resources;
+
+import java.util.Collections;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
+import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ScriptResourceTest {
+
+    @Test
+    public void testGroovy() {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Application app = mgmt.getEntityManager().createEntity( EntitySpec.create(Application.class, RestMockApp.class) );
+        try {
+        
+            Entities.start(app, Collections.<Location>emptyList());
+
+            ScriptResource s = new ScriptResource();
+            s.setManagementContext(mgmt);
+
+            ScriptExecutionSummary result = s.groovy(null, "def apps = []; mgmt.applications.each { println 'app:'+it; apps << it.id }; apps");
+            Assert.assertEquals(Collections.singletonList(app.getId()).toString(), result.getResult());
+            Assert.assertTrue(result.getStdout().contains("app:RestMockApp"));
+        
+        } finally { Entities.destroyAll(mgmt); }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
new file mode 100644
index 0000000..f90b677
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.rest.resources;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class SensorResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
+
+    private Server server;
+    private ManagementContext mgmt;
+    private BasicApplication app;
+
+    @BeforeClass(alwaysRun = true)
+    protected void setUp() {
+        mgmt = LocalManagementContextForTests.newInstance();
+        server = useServerForTest(BrooklynRestApiLauncher.launcher()
+            .managementContext(mgmt)
+            .withoutJsgui()
+            .start());
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class).displayName("simple-app")
+            .child(EntitySpec.create(Entity.class, RestMockSimpleEntity.class).displayName("simple-ent")));
+        mgmt.getEntityManager().manage(app);
+        app.start(MutableList.of(mgmt.getLocationRegistry().resolve("localhost")));
+    }
+    
+    // marked integration because of time
+    @Test(groups = "Integration")
+    public void testSensorBytes() throws Exception {
+        EntityInternal entity = (EntityInternal) Iterables.find(mgmt.getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        SensorResourceTest.addAmphibianSensor(entity);
+        
+        String baseUri = getBaseUri(server);
+        URI url = URI.create(Urls.mergePaths(baseUri, SensorResourceTest.SENSORS_ENDPOINT, SensorResourceTest.SENSOR_NAME));
+        
+        // Uses explicit "application/json" because failed on jenkins as though "text/plain" was the default on Ubuntu jenkins! 
+        HttpClient client = HttpTool.httpClientBuilder().uri(baseUri).build();
+        HttpToolResponse response = HttpTool.httpGet(client, url, ImmutableMap.<String, String>of("Accept", "application/json"));
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        Assert.assertEquals(response.getContentAsString(), "\"12345 frogs\"");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
new file mode 100644
index 0000000..4d2f781
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.test.config.render.TestRendererHints;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.WebResource.Builder;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class SensorResourceTest extends BrooklynRestResourceTest {
+
+    final static ApplicationSpec SIMPLE_SPEC = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    static final String SENSORS_ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/sensors";
+    static final String SENSOR_NAME = "amphibian.count";
+    static final AttributeSensor<Integer> SENSOR = Sensors.newIntegerSensor(SENSOR_NAME);
+
+    EntityInternal entity;
+
+    /**
+     * Sets up the application and entity.
+     * <p>
+     * Adds a sensor and sets its value to {@code 12345}. Configures a display value
+     * hint that appends {@code frogs} to the value of the sensor.
+     */
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Deploy application
+        ClientResponse deploy = clientDeploy(SIMPLE_SPEC);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        addAmphibianSensor(entity);
+    }
+
+    static void addAmphibianSensor(EntityInternal entity) {
+        // Add new sensor
+        entity.getMutableEntityType().addSensor(SENSOR);
+        entity.sensors().set(SENSOR, 12345);
+
+        // Register display value hint
+        RendererHints.register(SENSOR, RendererHints.displayValue(Functions.compose(StringFunctions.append(" frogs"), Functions.toStringFunction())));
+    }
+
+    @AfterClass(alwaysRun = true)
+    @Override
+    public void tearDown() throws Exception {
+        TestRendererHints.clearRegistry();
+        super.tearDown();
+    }
+
+    /** Check default is to use display value hint. */
+    @Test
+    public void testBatchSensorRead() throws Exception {
+        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), "12345 frogs");
+            }
+        }
+    }
+
+    /** Check setting {@code raw} to {@code true} ignores display value hint. */
+    @Test(dependsOnMethods = "testBatchSensorRead")
+    public void testBatchSensorReadRaw() throws Exception {
+        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
+                .queryParam("raw", "true")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), Integer.valueOf(12345));
+            }
+        }
+    }
+
+    protected ClientResponse doSensorTest(Boolean raw, MediaType acceptsType, Object expectedValue) {
+        return doSensorTestUntyped(
+            raw==null ? null : (""+raw).toLowerCase(), 
+            acceptsType==null ? null : new String[] { acceptsType.getType() }, 
+            expectedValue);
+    }
+    protected ClientResponse doSensorTestUntyped(String raw, String[] acceptsTypes, Object expectedValue) {
+        WebResource req = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME);
+        if (raw!=null) req = req.queryParam("raw", raw);
+        ClientResponse response;
+        if (acceptsTypes!=null) {
+            Builder rb = req.accept(acceptsTypes);
+            response = rb.get(ClientResponse.class);
+        } else {
+            response = req.get(ClientResponse.class);
+        }
+        if (expectedValue!=null) {
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Object value = response.getEntity(expectedValue.getClass());
+            assertEquals(value, expectedValue);
+        }
+        return response;
+    }
+    
+    /**
+     * Check we can get a sensor, explicitly requesting json; gives a string picking up the rendering hint.
+     * 
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetJson() throws Exception {
+        doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, "\"12345 frogs\"");
+    }
+    
+    @Test
+    public void testGetJsonBytes() throws Exception {
+        ClientResponse response = doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, null);
+        byte[] bytes = Streams.readFully(response.getEntityInputStream());
+        // assert we have one set of surrounding quotes
+        assertEquals(bytes.length, 13);
+    }
+
+    /** Check that plain returns a string without quotes, with the rendering hint */
+    @Test
+    public void testGetPlain() throws Exception {
+        doSensorTest(null, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** 
+     * Check that when we set {@code raw = true}, the result ignores the display value hint.
+     *
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetRawJson() throws Exception {
+        doSensorTest(true, MediaType.APPLICATION_JSON_TYPE, 12345);
+    }
+    
+    /** As {@link #testGetRaw()} but with plain set, returns the number */
+    @Test
+    public void testGetPlainRaw() throws Exception {
+        // have to pass a string because that's how PLAIN is deserialized
+        doSensorTest(true, MediaType.TEXT_PLAIN_TYPE, "12345");
+    }
+
+    /** Check explicitly setting {@code raw} to {@code false} is as before */
+    @Test
+    public void testGetPlainRawFalse() throws Exception {
+        doSensorTest(false, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** Check empty vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawEmpty() throws Exception {
+        doSensorTestUntyped("", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+
+    /** Check unparseable vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawError() throws Exception {
+        doSensorTestUntyped("biscuits", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+    
+    /** Check we can set a value */
+    @Test
+    public void testSet() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, 67890);
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+            
+            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "67890 frogs");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, MutableMap.of(SENSOR_NAME, 67890));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+            
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+
+        } finally { addAmphibianSensor(entity); }
+    }
+    
+    /** Check we can delete a value */
+    @Test
+    public void testDelete() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .delete(ClientResponse.class);
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
new file mode 100644
index 0000000..ce3fd37
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.rest.resources;
+
+import static org.apache.brooklyn.util.http.HttpTool.httpClientBuilder;
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.rest.security.provider.TestSecurityProvider;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
+
+    /**
+     * [sam] Other tests rely on brooklyn.properties not containing security properties so ..
+     * I think the best way to test this is to set a security provider, then reload properties
+     * and check no authentication is required.
+     * 
+     * [aled] Changing this test so doesn't rely on brooklyn.properties having no security
+     * provider (that can lead to failures locally when running just this test). Asserts 
+     */
+    @Test(groups = "Integration")
+    public void testSecurityProviderUpdatesWhenPropertiesReloaded() {
+        BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty();
+        brooklynProperties.put("brooklyn.webconsole.security.users", "admin");
+        brooklynProperties.put("brooklyn.webconsole.security.user.admin.password", "mypassword");
+        UsernamePasswordCredentials defaultCredential = new UsernamePasswordCredentials("admin", "mypassword");
+
+        ManagementContext mgmt = new LocalManagementContext(brooklynProperties);
+        
+        try {
+            Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                    .managementContext(mgmt)
+                    .withoutJsgui()
+                    .securityProvider(TestSecurityProvider.class)
+                    .start());
+            String baseUri = getBaseUri(server);
+    
+            HttpToolResponse response;
+            final URI uri = URI.create(getBaseUri() + "/v1/server/properties/reload");
+            final Map<String, String> args = Collections.emptyMap();
+    
+            // Unauthorised when no credentials, and when default credentials.
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).build(), uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+    
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(defaultCredential).build(), 
+                    uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+
+            // Accepts TestSecurityProvider credentials, and we reload.
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(),
+                    uri, args, args);
+            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+    
+            // Has no gone back to credentials from brooklynProperties; TestSecurityProvider credentials no longer work
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(defaultCredential).build(), 
+                    uri, args, args);
+            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+            
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(), 
+                    uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+    
+        } finally {
+            ((ManagementContextInternal)mgmt).terminate();
+        }
+    }
+
+    @Test(groups = "Integration")
+    public void testGetUser() throws Exception {
+        Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                .securityProvider(TestSecurityProvider.class)
+                .withoutJsgui()
+                .start());
+        assertEquals(getServerUser(server), TestSecurityProvider.USER);
+    }
+
+    private String getServerUser(Server server) throws Exception {
+        HttpClient client = httpClientBuilder()
+                .uri(getBaseUri(server))
+                .credentials(TestSecurityProvider.CREDENTIAL)
+                .build();
+        
+        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri(server) + "/v1/server/user"),
+                ImmutableMap.<String, String>of());
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        return response.getContentAsString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
new file mode 100644
index 0000000..f84cb80
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.brooklyn.rest.domain.VersionSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+@Test(singleThreaded = true)
+public class ServerResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+    
+    @Test
+    public void testGetVersion() throws Exception {
+        VersionSummary version = client().resource("/v1/server/version").get(VersionSummary.class);
+        assertEquals(version.getVersion(), BrooklynVersion.get());
+    }
+
+    @Test
+    public void testGetStatus() throws Exception {
+        String status = client().resource("/v1/server/status").get(String.class);
+        assertEquals(status, "MASTER");
+    }
+
+    @Test
+    public void testGetHighAvailability() throws Exception {
+        // Note by default management context from super is started without HA enabled.
+        // Therefore can only assert a minimal amount of stuff.
+        HighAvailabilitySummary summary = client().resource("/v1/server/highAvailability").get(HighAvailabilitySummary.class);
+        log.info("HA summary is: "+summary);
+        
+        String ownNodeId = getManagementContext().getManagementNodeId();
+        assertEquals(summary.getOwnId(), ownNodeId);
+        assertEquals(summary.getMasterId(), ownNodeId);
+        assertEquals(summary.getNodes().keySet(), ImmutableSet.of(ownNodeId));
+        assertEquals(summary.getNodes().get(ownNodeId).getNodeId(), ownNodeId);
+        assertEquals(summary.getNodes().get(ownNodeId).getStatus(), "MASTER");
+        assertNotNull(summary.getNodes().get(ownNodeId).getLocalTimestamp());
+        // remote will also be non-null if there is no remote backend (local is re-used)
+        assertNotNull(summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+        assertEquals(summary.getNodes().get(ownNodeId).getLocalTimestamp(), summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testReloadsBrooklynProperties() throws Exception {
+        final AtomicInteger reloadCount = new AtomicInteger();
+        getManagementContext().addPropertiesReloadListener(new ManagementContext.PropertiesReloadListener() {
+            @Override public void reloaded() {
+                reloadCount.incrementAndGet();
+            }});
+        client().resource("/v1/server/properties/reload").post();
+        assertEquals(reloadCount.get(), 1);
+    }
+
+    @Test
+    void testGetConfig() throws Exception {
+        ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().put("foo.bar.baz", "quux");
+        try {
+            assertEquals(client().resource("/v1/server/config/foo.bar.baz").get(String.class), "quux");
+        } finally {
+            ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().remove("foo.bar.baz");
+        }
+    }
+
+    @Test
+    void testGetMissingConfigThrowsException() throws Exception {
+        final String key = "foo.bar.baz";
+        BrooklynProperties properties = ((ManagementContextInternal)getManagementContext()).getBrooklynProperties();
+        Object existingValue = null;
+        boolean keyAlreadyPresent = false;
+        String response = null;
+        if (properties.containsKey(key)) {
+            existingValue = properties.remove(key);
+            keyAlreadyPresent = true;
+        }
+        try {
+            response = client().resource("/v1/server/config/" + key).get(String.class);
+            Asserts.fail("Expected call to /v1/server/config/" + key + " to fail with status 404, instead server returned " + response);
+        } catch (UniformInterfaceException e) {
+            assertEquals(e.getResponse().getStatus(), 204);
+        } finally {
+            if (keyAlreadyPresent) {
+                properties.put(key, existingValue);
+            }
+        }
+    }
+
+    // Alternatively could reuse a blocking location, see org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.ReleaseLatchLocation
+    @ImplementedBy(StopLatchEntityImpl.class)
+    public interface StopLatchEntity extends EmptySoftwareProcess {
+        public void unblock();
+        public boolean isBlocked();
+    }
+
+    public static class StopLatchEntityImpl extends EmptySoftwareProcessImpl implements StopLatchEntity {
+        private CountDownLatch lock = new CountDownLatch(1);
+        private volatile boolean isBlocked;
+
+        @Override
+        public void unblock() {
+            lock.countDown();
+        }
+
+        @Override
+        protected void postStop() {
+            super.preStop();
+            try {
+                isBlocked = true;
+                lock.await();
+                isBlocked = false;
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+
+        @Override
+        public Class<?> getDriverInterface() {
+            return EmptySoftwareProcessDriver.class;
+        }
+
+        @Override
+        public boolean isBlocked() {
+            return isBlocked;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
new file mode 100644
index 0000000..dbe9afd
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
+import org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactory;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.rest.resources.ServerResourceTest.StopLatchEntity;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+public class ServerShutdownTest extends BrooklynRestResourceTest {
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+
+    // Need to initialise the ManagementContext before each test as it is destroyed.
+    @Override
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+    }
+
+    @Override
+    @AfterClass(alwaysRun = true)
+    public void tearDown() throws Exception {
+    }
+
+    @Override
+    @BeforeMethod(alwaysRun = true)
+    public void setUpMethod() {
+        setUpJersey();
+        super.setUpMethod();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDownMethod() {
+        tearDownJersey();
+        destroyManagementContext();
+    }
+
+    @Test
+    public void testShutdown() throws Exception {
+        assertTrue(getManagementContext().isRunning());
+        assertFalse(shutdownListener.isRequested());
+
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
+        formData.add("requestTimeout", "0");
+        formData.add("delayForHttpReturn", "0");
+        client().resource("/v1/server/shutdown").entity(formData).post();
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(shutdownListener.isRequested());
+            }
+        });
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertFalse(getManagementContext().isRunning());
+            }});
+    }
+
+    @Test
+    public void testStopAppThenShutdownAndStopAppsWaitsForFirstStop() throws InterruptedException {
+        ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)getManagementContext().getEntityDriverManager()).getReflectiveDriverFactory();
+        f.addClassFullNameMapping("org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver", "org.apache.brooklyn.rest.resources.ServerResourceTest$EmptySoftwareProcessTestDriver");
+
+        // Second stop on SoftwareProcess could return early, while the first stop is still in progress
+        // This causes the app to shutdown prematurely, leaking machines.
+        EntityManager emgr = getManagementContext().getEntityManager();
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
+        TestApplication app = emgr.createEntity(appSpec);
+        emgr.manage(app);
+        EntitySpec<StopLatchEntity> latchEntitySpec = EntitySpec.create(StopLatchEntity.class);
+        final StopLatchEntity entity = app.createAndManageChild(latchEntitySpec);
+        app.start(ImmutableSet.of(app.newLocalhostProvisioningLocation()));
+        EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        try {
+            final Task<Void> firstStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(entity.isBlocked());
+                }
+            });
+
+            final AtomicReference<Exception> shutdownError = new AtomicReference<>();
+            // Can't use ExecutionContext as it will be stopped on shutdown
+            Thread shutdownThread = new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
+                        formData.add("stopAppsFirst", "true");
+                        formData.add("shutdownTimeout", "0");
+                        formData.add("requestTimeout", "0");
+                        formData.add("delayForHttpReturn", "0");
+                        client().resource("/v1/server/shutdown").entity(formData).post();
+                    } catch (Exception e) {
+                        log.error("Shutdown request error", e);
+                        shutdownError.set(e);
+                        throw Exceptions.propagate(e);
+                    }
+                }
+            };
+            shutdownThread.start();
+
+            //shutdown must wait until the first stop completes (or time out)
+            Asserts.succeedsContinually(new Runnable() {
+                @Override
+                public void run() {
+                    assertFalse(firstStop.isDone());
+                    assertEquals(getManagementContext().getApplications().size(), 1);
+                    assertFalse(shutdownListener.isRequested());
+                }
+            });
+
+            // NOTE test is not fully deterministic. Depending on thread scheduling this will
+            // execute before or after ServerResource.shutdown does the app stop loop. This
+            // means that the shutdown code might not see the app at all. In any case though
+            // the test must succeed.
+            entity.unblock();
+
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(firstStop.isDone());
+                    assertTrue(shutdownListener.isRequested());
+                    assertFalse(getManagementContext().isRunning());
+                }
+            });
+
+            shutdownThread.join();
+            assertNull(shutdownError.get(), "Shutdown request error, logged above");
+        } finally {
+            // Be sure we always unblock entity stop even in the case of an exception.
+            // In the success path the entity is already unblocked above.
+            entity.unblock();
+        }
+    }
+
+}


Mime
View raw message