blob: bb80035bfe94e701f57f884c2dd151629ed37833 [file] [log] [blame]
/*
* 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); }
}
}