blob: a8d3cdf68030ac51e58188fd987455213ac0a851 [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 org.apache.brooklyn.rest.resources;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.rest.api.EffectorApi;
import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.test.support.FlakyRetryAnalyser;
import org.apache.brooklyn.util.time.Duration;
import org.testng.annotations.Test;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
/**
* Test the {@link EffectorApi} implementation.
*/
@Test(singleThreaded = true,
// by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
suiteName = "EffectorResourceTest")
public class EffectorResourceTest extends BrooklynRestResourceTest {
BasicApplication app;
TestEntity entity;
@Override
public void initMethod() throws Exception {
super.initMethod();
app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)
.child(EntitySpec.create(TestEntity.class)));
entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
}
@Override
public void destroyMethod() throws Exception {
try {
if (app != null) Entities.destroy(app, true);
} finally {
super.destroyMethod();
}
}
@Test
public void testInvokeEffectorNoArgs() throws Exception {
String path = "/applications/"+app.getId()+"/entities/"+entity.getId()+"/effectors/"+"myEffector";
Response response = client().path(path)
.accept(MediaType.APPLICATION_JSON)
.post(null);
assertEquals(response.getStatus(), 202);
Asserts.succeedsEventually(() -> assertTrue(entity.getCallHistory().contains("myEffector")));
}
@Test(retryAnalyzer = FlakyRetryAnalyser.class)
public void testInvokeEffectorNoArgsBlocking() throws Exception {
String path = "/applications/"+app.getId()+"/entities/"+entity.getId()+"/effectors/"+"myEffector";
Response response = client().path(path)
.query("timeout", "0")
.accept(MediaType.APPLICATION_JSON)
.post(null);
assertEquals(response.getStatus(), 202);
assertTrue(entity.getCallHistory().contains("myEffector"));
}
@Test
public void testInvokeEffectorWithArg() throws Exception {
String path = "/applications/"+app.getId()+"/entities/"+entity.getId()+"/effectors/"+"identityEffector";
Response response = client().path(path)
.accept(MediaType.APPLICATION_JSON)
.header("Content-Type", MediaType.APPLICATION_JSON)
.post("{\"arg\": \"myval\"}");
assertEquals(response.getStatus(), 202);
String responseBody = response.readEntity(String.class);
assertTrue(entity.getCallHistory().contains("identityEffector"));
assertEquals(responseBody, "myval");
}
@Test
public void testInvokeEffectorWithTimeoutWaits() throws Exception {
String path = "/applications/"+app.getId()+"/entities/"+entity.getId()+"/effectors/"+"sleepEffector";
Stopwatch stopwatch = Stopwatch.createStarted();
Response response = client().path(path)
.query("timeout", "1m")
.accept(MediaType.APPLICATION_JSON)
.header("Content-Type", MediaType.APPLICATION_JSON)
.post("{\"duration\": \"50ms\"}");
Duration runDuration = Duration.of(stopwatch);
assertEquals(response.getStatus(), 202);
assertTrue(entity.getCallHistory().contains("sleepEffector"));
assertTrue(runDuration.isLongerThan(Duration.millis(40)), "runDuration="+runDuration);
}
@Test
public void testInvokeEffectorWithTimeoutTimesOut() throws Exception {
/*
* The effector is invoked via:
* Task<?> task = entity.invoke(effector.get(), parameters)
* task.get(timeout)
* On timeout, the task is not cancelled. The effector keeps executing in the background.
*
* The task might not yet have reached the effector body's sleep. It sometimes is
* still setting up the call (e.g. preparing the effector args).
*
* The response returned is a snapshot of the task's state/stacktrace at the time
* of the timeout.
*/
String path = "/applications/"+app.getId()+"/entities/"+entity.getId()+"/effectors/"+"sleepEffector";
Stopwatch stopwatch = Stopwatch.createStarted();
Response response = client().path(path)
.query("timeout", "1ms")
.header("Content-Type", MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.post("{\"duration\": \"5m\"}");
Duration runDuration = Duration.of(stopwatch);
response.bufferEntity();
assertEquals(response.getStatus(), 202);
String responseBody = response.readEntity(String.class);
Asserts.succeedsEventually(() -> assertTrue(entity.getCallHistory().contains("sleepEffector")));
assertTrue(runDuration.isShorterThan(Asserts.DEFAULT_LONG_TIMEOUT), "runDuration="+runDuration);
// Expect to get a task back, representing the currently executing effector
Map<?,?> responseMap = new Gson().fromJson(responseBody, Map.class);
assertTrue((""+responseMap.get("displayName")).contains("sleepEffector"), "responseMap="+responseMap);
String detailedStatus = ""+responseMap.get("detailedStatus");
boolean taskSubmitted = detailedStatus.contains("Submitted") && (detailedStatus.contains("TestEntityImpl.sleepEffector") || detailedStatus.contains("for execution"));
boolean taskSleeping = detailedStatus.contains("In progress, thread waiting") && detailedStatus.contains("TestEntityImpl.sleepEffector");
boolean taskPreparing = detailedStatus.contains("In progress (RUNNABLE)") && detailedStatus.contains("EffectorUtils.invokeMethodEffector");
assertTrue(taskSubmitted || taskSleeping || taskPreparing, "responseMap="+responseMap);
}
}