| /* |
| * 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 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.testng.annotations.AfterClass; |
| import org.testng.annotations.BeforeClass; |
| import org.testng.annotations.Test; |
| |
| import brooklyn.config.render.RendererHints; |
| import brooklyn.config.render.TestRendererHints; |
| import brooklyn.entity.basic.EntityInternal; |
| import brooklyn.entity.basic.EntityPredicates; |
| import brooklyn.event.AttributeSensor; |
| import brooklyn.event.basic.Sensors; |
| import brooklyn.rest.api.SensorApi; |
| import brooklyn.rest.domain.ApplicationSpec; |
| import brooklyn.rest.domain.EntitySpec; |
| import brooklyn.rest.testing.BrooklynRestResourceTest; |
| import brooklyn.rest.testing.mocks.RestMockSimpleEntity; |
| import brooklyn.test.HttpTestUtils; |
| import brooklyn.util.stream.Streams; |
| import brooklyn.util.text.StringFunctions; |
| |
| 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.setAttribute(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) { |
| HttpTestUtils.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()); |
| |
| String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class); |
| assertEquals(value, "67890 frogs"); |
| |
| } 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); } |
| } |
| |
| } |