/* * 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.core.effector; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionManager; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.core.annotation.Effector; import org.apache.brooklyn.core.annotation.EffectorParam; import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestApplicationImpl; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.Tasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; public class EffectorConcatenateTest { private static final Logger log = LoggerFactory.getLogger(EffectorConcatenateTest.class); private static final long TIMEOUT = 10*1000; public static class MyEntityImpl extends AbstractEntity { public static MethodEffector<String> CONCATENATE = new MethodEffector<String>(MyEntityImpl.class, "concatenate"); public static MethodEffector<Void> WAIT_A_BIT = new MethodEffector<Void>(MyEntityImpl.class, "waitabit"); public static MethodEffector<Void> SPAWN_CHILD = new MethodEffector<Void>(MyEntityImpl.class, "spawnchild"); public MyEntityImpl() { super(); } public MyEntityImpl(Entity parent) { super(parent); } /** The "current task" representing the effector currently executing */ AtomicReference<Task<?>> waitingTask = new AtomicReference<Task<?>>(); /** latch is .countDown'ed by the effector at the beginning of the "waiting" point */ CountDownLatch nowWaitingLatch = new CountDownLatch(1); /** latch is await'ed on by the effector when it is in the "waiting" point */ CountDownLatch continueFromWaitingLatch = new CountDownLatch(1); @Effector(description="sample effector concatenating strings") public String concatenate(@EffectorParam(name="first", description="first argument") String first, @EffectorParam(name="second", description="2nd arg") String second) throws Exception { return first+second; } @Effector(description="sample effector doing some waiting") public void waitabit() throws Exception { waitingTask.set(Tasks.current()); Tasks.setExtraStatusDetails("waitabit extra status details"); Tasks.withBlockingDetails("waitabit.blocking", new Callable<Void>() { public Void call() throws Exception { nowWaitingLatch.countDown(); if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { fail("took too long to be told to continue"); } return null; }}); } @Effector(description="sample effector that spawns a child task that waits a bit") public void spawnchild() throws Exception { // spawn a child, then wait BasicExecutionContext.getCurrentExecutionContext().submit( MutableMap.of("displayName", "SpawnedChildName"), new Callable<Void>() { public Void call() throws Exception { log.info("beginning spawned child response "+Tasks.current()+", with tags "+Tasks.current().getTags()); Tasks.setBlockingDetails("spawned child blocking details"); nowWaitingLatch.countDown(); if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { fail("took too long to be told to continue"); } return null; }}); } } private TestApplication app; private MyEntityImpl e; @BeforeMethod(alwaysRun=true) public void setUp() { app = new TestApplicationImpl(); e = new MyEntityImpl(app); Entities.startManagement(app); } @AfterMethod(alwaysRun=true) public void tearDown() { if (app != null) Entities.destroyAll(app.getManagementContext()); } @Test public void testCanInvokeEffector() throws Exception { // invocation map syntax Task<String> task = e.invoke(MyEntityImpl.CONCATENATE, ImmutableMap.of("first", "a", "second", "b")); assertEquals(task.get(TIMEOUT, TimeUnit.MILLISECONDS), "ab"); // method syntax assertEquals("xy", e.concatenate("x", "y")); } @Test public void testReportsTaskDetails() throws Exception { final AtomicReference<String> result = new AtomicReference<String>(); Thread bg = new Thread(new Runnable() { public void run() { try { // Expect "wait a bit" to tell us it's blocking if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { result.set("took too long for waitabit to be waiting"); return; } // Expect "wait a bit" to have retrieved and set its task try { Task<?> t = e.waitingTask.get(); String status = t.getStatusDetail(true); log.info("waitabit task says:\n"+status); if (!status.contains("waitabit extra status details")) { result.set("Status not in expected format: doesn't contain extra status details phrase 'My extra status details'\n"+status); return; } if (!status.startsWith("waitabit.blocking")) { result.set("Status not in expected format: doesn't start with blocking details 'waitabit.blocking'\n"+status); return; } } finally { e.continueFromWaitingLatch.countDown(); } } catch (Throwable t) { log.warn("Failure: "+t, t); result.set("Failure: "+t); } }}); bg.start(); e.invoke(MyEntityImpl.WAIT_A_BIT, ImmutableMap.<String,Object>of()) .get(TIMEOUT, TimeUnit.MILLISECONDS); bg.join(TIMEOUT*2); assertFalse(bg.isAlive()); String problem = result.get(); if (problem!=null) fail(problem); } @Test public void testReportsSpawnedTaskDetails() throws Exception { final AtomicReference<String> result = new AtomicReference<String>(); Thread bg = new Thread(new Runnable() { public void run() { try { // Expect "spawned child" to tell us it's blocking if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { result.set("took too long for spawnchild's sub-task to be waiting"); return; } // Expect spawned task to be have been tagged with entity ExecutionManager em = e.getManagementContext().getExecutionManager(); Task<?> subtask = Iterables.find(BrooklynTaskTags.getTasksInEntityContext(em, e), new Predicate<Task<?>>() { public boolean apply(Task<?> input) { return "SpawnedChildName".equals(input.getDisplayName()); } }); // Expect spawned task to haev correct "blocking details" try { String status = subtask.getStatusDetail(true); log.info("subtask task says:\n"+status); if (!status.contains("spawned child blocking details")) { result.set("Status not in expected format: doesn't contain blocking details phrase 'spawned child blocking details'\n"+status); return; } } finally { e.continueFromWaitingLatch.countDown(); } } catch (Throwable t) { log.warn("Failure: "+t, t); result.set("Failure: "+t); } }}); bg.start(); e.invoke(MyEntityImpl.SPAWN_CHILD, ImmutableMap.<String,Object>of()) .get(TIMEOUT, TimeUnit.MILLISECONDS); bg.join(TIMEOUT*2); assertFalse(bg.isAlive()); String problem = result.get(); if (problem!=null) fail(problem); } }