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(); + } + } + +}