| /* |
| * 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.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.concurrent.TimeoutException; |
| |
| import javax.ws.rs.core.GenericType; |
| import javax.ws.rs.core.MediaType; |
| import javax.ws.rs.core.Response; |
| |
| import org.apache.brooklyn.api.effector.Effector; |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.entity.EntitySpec; |
| import org.apache.brooklyn.api.mgmt.HasTaskChildren; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.core.effector.SampleManyTasksEffector; |
| import org.apache.brooklyn.core.entity.Dumper; |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.mgmt.EntityManagementUtils; |
| import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult; |
| import org.apache.brooklyn.core.mgmt.internal.TestEntityWithEffectors; |
| import org.apache.brooklyn.entity.stock.BasicApplication; |
| import org.apache.brooklyn.rest.domain.TaskSummary; |
| import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; |
| import org.apache.brooklyn.test.Asserts; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.config.ConfigBag; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.http.HttpAsserts; |
| import org.apache.brooklyn.util.time.CountdownTimer; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.testng.Assert; |
| import org.testng.annotations.BeforeClass; |
| import org.testng.annotations.BeforeMethod; |
| import org.testng.annotations.Test; |
| |
| import com.google.common.collect.Iterables; |
| |
| /** Tests {@link ActivityResource} and activity methods on {@link EntityResource} */ |
| public class ActivityRestTest extends BrooklynRestResourceTest { |
| |
| private static final Logger log = LoggerFactory.getLogger(ActivityRestTest.class); |
| |
| /* a nice seed, initial run as follows; |
| |
| Task[eatand]@J90TKfIX: Waiting on Task[eat-sleep-rave-repeat]@QPa5o4kF |
| Task[eat-sleep-rave-repeat]@QPa5o4kF: Waiting on Task[rave]@yP9KjuWD |
| Task[rave]@yP9KjuWD: Waiting on Task[repeat]@Dd1AqB7Q |
| Task[repeat]@Dd1AqB7Q: Waiting on Task[repeat]@remQL5eD |
| Task[repeat]@remQL5eD: Waiting on Task[repeat]@g1ReP4BP |
| Task[sleep]@iV3iWg2N: Completed, result: slept 46ms |
| Task[eat]@fpIttX07: Completed, result: eat |
| Task[eat]@w6sxLefq: Completed, result: eat |
| Task[repeat]@g1ReP4BP: Waiting on Task[sleep]@zRTOQ4ak |
| Task[eat]@TvcdOUx7: Completed, result: eat |
| Task[rave]@yJndzNLf: Completed, result: raved with 1 tasks |
| Task[eat]@oiJ3eZZQ: Completed, result: eat |
| Task[sleep]@zRTOQ4ak: sleeping 74ms |
| Task[eat]@qoFRPEfM: Not submitted |
| Task[sleep]@fNX16uvi: Not submitted |
| |
| */ |
| private final int SEED = 1; |
| |
| private Entity entity; |
| private Effector<?> effector; |
| |
| private Task<?> lastTask; |
| |
| @BeforeClass(alwaysRun = true) |
| public void setUp() throws Exception { |
| startServer(); |
| } |
| |
| @BeforeMethod(alwaysRun = true) |
| public void setUpOneTest() throws Exception { |
| initEntity(SEED); |
| } |
| |
| @SuppressWarnings("deprecation") |
| protected void initEntity(int seed) { |
| if (entity != null && Entities.isManaged(entity)) { |
| Entities.destroy(entity.getApplication(), true); |
| } |
| |
| CreationResult<BasicApplication, Void> app = EntityManagementUtils.createStarting(getManagementContext(), |
| EntitySpec.create(BasicApplication.class) |
| .child(EntitySpec.create(TestEntityWithEffectors.class)) ); |
| app.blockUntilComplete(); |
| entity = Iterables.getOnlyElement( app.get().getChildren() ); |
| |
| SampleManyTasksEffector manyTasksAdder = new SampleManyTasksEffector(ConfigBag.newInstance().configure(SampleManyTasksEffector.RANDOM_SEED, seed)); |
| effector = manyTasksAdder.getEffector(); |
| manyTasksAdder.apply((org.apache.brooklyn.api.entity.EntityLocal) entity); |
| } |
| |
| /** finds a good seed, in case the effector changes */ |
| public static void main(String[] args) throws Exception { |
| ActivityRestTest me = new ActivityRestTest(); |
| me.setUpClass(); |
| int i=0; |
| do { |
| me.initEntity(i); |
| try { |
| log.info("Trying seed "+i+"..."); |
| me.testGood(Duration.millis(200)); |
| break; |
| } catch (Throwable e) { |
| log.info(" "+Exceptions.collapseText(e)); |
| // e.printStackTrace(); |
| // continue |
| } |
| i++; |
| } while (true); |
| Dumper.dumpInfo(me.lastTask); |
| log.info("Seed "+i+" is good ^"); |
| } |
| |
| @Test |
| public void testGood() { |
| testGood(Duration.ONE_SECOND); |
| } |
| |
| void testGood(Duration timeout) { |
| lastTask = entity.invoke(effector, null); |
| Task<?> leaf = waitForCompletedDescendantWithChildAndSibling(lastTask, lastTask, CountdownTimer.newInstanceStarted(timeout), 0); |
| Assert.assertTrue(depthOf(leaf)>=4, "Not deep enough: "+depthOf(leaf)); |
| } |
| |
| @Test |
| public void testGetActivity() { |
| Task<?> t = entity.invoke(effector, MutableMap.of(SampleManyTasksEffector.RANDOM_SEED.getName(), 10)); |
| |
| Response response = client().path("/activities/"+t.getId()) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| TaskSummary task = response.readEntity(TaskSummary.class); |
| Assert.assertEquals(task.getId(), t.getId()); |
| Asserts.assertThat(task.getTags(), tags -> tags.contains("EFFECTOR")); |
| Optional<Object> effectorParams = task.getTags().stream().map(tag -> tag instanceof Map ? ((Map) tag).get("effectorParams") : null).filter(p -> p != null).findAny(); |
| Asserts.assertTrue(effectorParams.isPresent()); |
| Asserts.assertEquals(((Map)effectorParams.get()).get(SampleManyTasksEffector.RANDOM_SEED.getName()), 10); |
| } |
| |
| // See https://issues.apache.org/jira/browse/BROOKLYN-571 |
| @Test |
| public void testGetTaskOfUnmanagedEntity() { |
| Task<?> t = entity.invoke(effector, null); |
| Entities.unmanage(entity.getParent()); |
| |
| Response response = client().path("/activities/"+t.getId()) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| TaskSummary task = response.readEntity(TaskSummary.class); |
| Assert.assertEquals(task.getId(), t.getId()); |
| Assert.assertEquals(task.getEntityId(), entity.getId()); |
| } |
| |
| @Test |
| public void testGetActivitiesChildren() { |
| Task<?> t = entity.invoke(effector, null); |
| Task<?> leaf = waitForCompletedDescendantWithChildAndSibling(t, t, CountdownTimer.newInstanceStarted(Duration.ONE_SECOND), 0); |
| |
| Response response = client().path("/activities/"+leaf.getSubmittedByTask().getId()+"/children") |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| List<TaskSummary> tasks = response.readEntity(new GenericType<List<TaskSummary>>() {}); |
| log.info("Tasks children: "+tasks.size()); |
| Assert.assertTrue(tasksContain(tasks, leaf), "tasks should have included leaf "+leaf+"; was "+tasks); |
| } |
| |
| @Test(groups = "WIP") // we rejigged how this works, it should have one unique name now, and gives intermittent errors |
| public void testGetActivitiesRecursiveAndWithLimit() { |
| Task<?> t = entity.invoke(effector, null); |
| Task<?> leaf = waitForCompletedDescendantWithChildAndSibling(t, t, CountdownTimer.newInstanceStarted(Duration.ONE_SECOND), 0); |
| Task<?> leafParent = leaf.getSubmittedByTask(); |
| Task<?> leafGrandparent = leafParent.getSubmittedByTask(); |
| |
| Response response = client().path("/activities/"+leafGrandparent.getId()+"/children") |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| List<TaskSummary> tasksL = response.readEntity(new GenericType<List<TaskSummary>>() {}); |
| Assert.assertFalse(tasksContain(tasksL, leaf), "non-recursive tasks should not have included leaf "+leaf+"; was "+tasksL); |
| Assert.assertTrue(tasksContain(tasksL, leafParent), "non-recursive tasks should have included leaf parent "+leafParent+"; was "+tasksL); |
| Assert.assertFalse(tasksContain(tasksL, leafGrandparent), "non-recursive tasks should not have included leaf grandparent "+leafGrandparent+"; was "+tasksL); |
| |
| response = client().path("/activities/"+leafGrandparent.getId()+"/children/recurse") |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| Map<String,TaskSummary> tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertTrue(tasksContain(tasks, leaf), "recursive tasks should have included leaf "+leaf+"; was "+tasks); |
| Assert.assertTrue(tasksContain(tasks, leafParent), "recursive tasks should have included leaf parent "+leafParent+"; was "+tasks); |
| Assert.assertFalse(tasksContain(tasks, leafGrandparent), "recursive tasks should not have included leaf grandparent "+leafGrandparent+"; was "+tasks); |
| |
| response = client().path("/activities/"+leafGrandparent.getId()+"/children/recurse") |
| .query("maxDepth", 1) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertFalse(tasksContain(tasks, leaf), "depth 1 recursive tasks should nont have included leaf "+leaf+"; was "+tasks); |
| Assert.assertTrue(tasksContain(tasks, leafParent), "depth 1 recursive tasks should have included leaf parent "+leafParent+"; was "+tasks); |
| |
| response = client().path("/activities/"+leafGrandparent.getId()+"/children/recurse") |
| .query("maxDepth", 2) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertTrue(tasksContain(tasks, leaf), "depth 2 recursive tasks should have included leaf "+leaf+"; was "+tasks); |
| Assert.assertTrue(tasksContain(tasks, leafParent), "depth 2 recursive tasks should have included leaf parent "+leafParent+"; was "+tasks); |
| Assert.assertFalse(tasksContain(tasks, leafGrandparent), "depth 2 recursive tasks should not have included leaf grandparent "+leafGrandparent+"; was "+tasks); |
| |
| Assert.assertTrue(children(leafGrandparent).size() >= 2, "children: "+children(leafGrandparent)); |
| response = client().path("/activities/"+leafGrandparent.getId()+"/children/recurse") |
| .query("limit", children(leafGrandparent).size()) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertEquals(tasks.size(), children(leafGrandparent).size()); |
| Assert.assertTrue(tasksContain(tasks, leafParent), "count limited recursive tasks should have included leaf parent "+leafParent+"; was "+tasks); |
| Assert.assertFalse(tasksContain(tasks, leaf), "count limited recursive tasks should not have included leaf "+leaf+"; was "+tasks); |
| |
| response = client().path("/activities/"+leafGrandparent.getId()+"/children/recurse") |
| .query("limit", children(leafGrandparent).size()+1) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| response.bufferEntity(); |
| assertHealthy(response); |
| tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertEquals(tasks.size(), children(leafGrandparent).size()+1); |
| tasks = response.readEntity(new GenericType<Map<String,TaskSummary>>() {}); |
| Assert.assertTrue(tasksContain(tasks, leafParent), "count+1 limited recursive tasks should have included leaf parent "+leafParent+"; was "+tasks); |
| // 2022-05-09 - race can cause this to fail occasionally. example output on failure: |
| /* |
| * expected: Task[eat]@qlQs4isq |
| * |
| * was: { |
| * ehqkY6bX=TaskSummary{id='ehqkY6bX', displayName='eat', entityId='ngvnrjgey4', |
| * entityDisplayName='TestEntityWithEffectors:ngvn', description='', |
| * tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}},{type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], |
| * submitTimeUtc=1655894929948, startTimeUtc=1655894929948, endTimeUtc=1655894929948, currentStatus='Completed', |
| * result=eat, isError=false, isCancelled=false, children=[], |
| * submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, |
| * blockingTask=null, blockingDetails='null', detailedStatus='Completed after 0ms |
| * Result: eat', streams={}, links={self=/activities/ehqkY6bX, children=/activities/ehqkY6bX/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * OfboqeEb=TaskSummary{id='OfboqeEb', displayName='rave', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894929948, startTimeUtc=1655894929948, endTimeUtc=1655894929948, currentStatus='Completed', result=raved with 1 tasks, isError=false, isCancelled=false, |
| * children=[LinkWithMetadata{link='/activities/CYrgJWOX', metadata={id=CYrgJWOX, taskName=eat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', |
| * detailedStatus='Completed after 0ms Result: raved with 1 tasks', streams={}, links={self=/activities/OfboqeEb, |
| * children=/activities/OfboqeEb/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * PraVlsNu=TaskSummary{id='PraVlsNu', displayName='sleep', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='Sleeping 74ms', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894929948, startTimeUtc=1655894929948, endTimeUtc=1655894930024, currentStatus='Completed', result=slept 74ms, isError=false, isCancelled=false, children=[], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', detailedStatus='Completed after 76ms |
| * Result: slept 74ms', streams={}, links={self=/activities/PraVlsNu, children=/activities/PraVlsNu/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * rozpPf9w=TaskSummary{id='rozpPf9w', displayName='eat', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894930024, startTimeUtc=1655894930024, endTimeUtc=1655894930024, currentStatus='Completed', result=eat, isError=false, isCancelled=false, children=[], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', detailedStatus='Completed after 0ms |
| * Result: eat', streams={}, links={self=/activities/rozpPf9w, children=/activities/rozpPf9w/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * q9rmyAYQ=TaskSummary{id='q9rmyAYQ', displayName='sleep', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='Sleeping 89ms', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894930024, startTimeUtc=1655894930024, endTimeUtc=1655894930119, currentStatus='Completed', result=slept 89ms, isError=false, isCancelled=false, children=[], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', detailedStatus='Completed after 95ms |
| * Result: slept 89ms', streams={}, links={self=/activities/q9rmyAYQ, children=/activities/q9rmyAYQ/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * u1vdPlK0=TaskSummary{id='u1vdPlK0', displayName='repeat', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894930119, startTimeUtc=1655894930119, endTimeUtc=1655894930119, currentStatus='Completed', result=[eat, eat], isError=false, isCancelled=false, |
| * children=[LinkWithMetadata{link='/activities/qlQs4isq', metadata={id=qlQs4isq, taskName=eat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, LinkWithMetadata{link='/activities/n84y8H5W', metadata={id=n84y8H5W, taskName=eat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', detailedStatus='Completed after 0ms |
| * Result: [eat, eat]', streams={}, links={self=/activities/u1vdPlK0, children=/activities/u1vdPlK0/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * DQf6nl7F=TaskSummary{id='DQf6nl7F', displayName='sleep', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='Sleeping 345ms', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894930119, startTimeUtc=1655894930119, endTimeUtc=null, currentStatus='In progress', result=null, isError=false, isCancelled=false, children=[], submittedByTask=LinkWithMetadata{link='/activities/ys1TfWnj', metadata={id=ys1TfWnj, taskName=repeat, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='sleeping 345ms', detailedStatus='sleeping 345ms |
| * Task[sleep]@DQf6nl7F |
| * Submitted by MaybeSupplier[value=Task[repeat]@ys1TfWnj] In progress, thread waiting (timed) on unknown (sleep) At: org.apache.brooklyn.util.time.Time.sleep(Time.java:451) org.apache.brooklyn.util.time.Time.sleep(Time.java:459) org.apache.brooklyn.core.effector.SampleManyTasksEffector$1$2.call(SampleManyTasksEffector.java:102) org.apache.brooklyn.util.core.task.DynamicSequentialTask$DstJob.call(DynamicSequentialTask.java:370) org.apache.brooklyn.util.core.task.BasicExecutionManager$SubmissionCallable.call(BasicExecutionManager.java:874)', streams={}, links={self=/activities/DQf6nl7F, children=/activities/DQf6nl7F/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}, |
| * MSGthJav=TaskSummary{id='MSGthJav', displayName='repeat', entityId='null', entityDisplayName='null', description='', tags=[SUB-TASK], submitTimeUtc=null, startTimeUtc=null, endTimeUtc=null, currentStatus='Not submitted', result=null, isError=false, isCancelled=false, |
| * children=[LinkWithMetadata{link='/activities/TLiYvXdI', |
| * metadata={id=TLiYvXdI, taskName=eat}}, LinkWithMetadata{link='/activities/cMxssIEB', metadata={id=cMxssIEB, taskName=eat}}], submittedByTask=null, blockingTask=null, blockingDetails='null', detailedStatus='Not submitted', streams={}, links={self=/activities/MSGthJav, children=/activities/MSGthJav/children}}, |
| * y2hMjkYv=TaskSummary{id='y2hMjkYv', displayName='eat', entityId='null', entityDisplayName='null', description='', tags=[SUB-TASK], submitTimeUtc=null, startTimeUtc=null, endTimeUtc=null, currentStatus='Not submitted', result=null, isError=false, isCancelled=false, children=[], submittedByTask=null, blockingTask=null, blockingDetails='null', detailedStatus='Not submitted', streams={}, links={self=/activities/y2hMjkYv, children=/activities/y2hMjkYv/children}}, |
| * Hmnbssz2=TaskSummary{id='Hmnbssz2', displayName='sleep', entityId='null', entityDisplayName='null', description='Sleeping 169ms', tags=[SUB-TASK], submitTimeUtc=null, startTimeUtc=null, endTimeUtc=null, currentStatus='Not submitted', result=null, isError=false, isCancelled=false, children=[], submittedByTask=null, blockingTask=null, blockingDetails='null', detailedStatus='Not submitted', streams={}, links={self=/activities/Hmnbssz2, children=/activities/Hmnbssz2/children}}, |
| * kX5ZAJIb=TaskSummary{id='kX5ZAJIb', displayName='eat', entityId='null', entityDisplayName='null', description='', tags=[SUB-TASK], submitTimeUtc=null, startTimeUtc=null, endTimeUtc=null, currentStatus='Not submitted', result=null, isError=false, isCancelled=false, children=[], submittedByTask=null, blockingTask=null, blockingDetails='null', detailedStatus='Not submitted', streams={}, links={self=/activities/kX5ZAJIb, children=/activities/kX5ZAJIb/children}}, |
| * CYrgJWOX=TaskSummary{id='CYrgJWOX', displayName='eat', entityId='ngvnrjgey4', entityDisplayName='TestEntityWithEffectors:ngvn', description='', tags=[{wrappingType=contextEntity, entity={type=org.apache.brooklyn.api.entity.Entity, id=ngvnrjgey4}}, {type=org.apache.brooklyn.api.mgmt.ManagementContext}, SUB-TASK], submitTimeUtc=1655894929948, startTimeUtc=1655894929948, endTimeUtc=1655894929948, currentStatus='Completed', result=eat, isError=false, isCancelled=false, children=[], submittedByTask=LinkWithMetadata{link='/activities/OfboqeEb', metadata={id=OfboqeEb, taskName=rave, entityId=ngvnrjgey4, entityDisplayName=TestEntityWithEffectors:ngvn}}, blockingTask=null, blockingDetails='null', detailedStatus='Completed after 0ms |
| * Result: eat', streams={}, links={self=/activities/CYrgJWOX, children=/activities/CYrgJWOX/children, entity=/applications/frblu0wgjz/entities/ngvnrjgey4}}} |
| */ |
| Assert.assertTrue(tasksContain(tasks, leaf), "count+1 limited recursive tasks should have included leaf "+leaf+"; was "+tasks); |
| } |
| |
| private boolean tasksContain(Map<String, TaskSummary> tasks, Task<?> leaf) { |
| return tasks.keySet().contains(leaf.getId()); |
| } |
| |
| private List<Task<?>> children(Task<?> t) { |
| return MutableList.copyOf( ((HasTaskChildren)t).getChildren() ); |
| } |
| |
| @Test |
| public void testGetEntityActivitiesAndWithLimit() { |
| Task<?> t = entity.invoke(effector, null); |
| Task<?> leaf = waitForCompletedDescendantWithChildAndSibling(t, t, CountdownTimer.newInstanceStarted(Duration.ONE_SECOND), 0); |
| |
| Response response = client().path("/applications/"+entity.getApplicationId()+ |
| "/entities/"+entity.getId()+"/activities") |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| List<TaskSummary> tasks = response.readEntity(new GenericType<List<TaskSummary>>() {}); |
| log.info("Tasks now: "+tasks.size()); |
| Assert.assertTrue(tasks.size() > 4, "tasks should have been big; was "+tasks); |
| Assert.assertTrue(tasksContain(tasks, leaf), "tasks should have included leaf "+leaf+"; was "+tasks); |
| |
| response = client().path("/applications/"+entity.getApplicationId()+ |
| "/entities/"+entity.getId()+"/activities") |
| .query("limit", 3) |
| .accept(MediaType.APPLICATION_JSON) |
| .get(); |
| assertHealthy(response); |
| tasks = response.readEntity(new GenericType<List<TaskSummary>>() {}); |
| log.info("Tasks limited: "+tasks.size()); |
| Assert.assertEquals(tasks.size(), 3, "tasks should have been limited; was "+tasks); |
| Assert.assertFalse(tasksContain(tasks, leaf), "tasks should not have included leaf "+leaf+"; was "+tasks); |
| } |
| |
| private void assertHealthy(Response response) { |
| if (!HttpAsserts.isHealthyStatusCode(response.getStatus())) { |
| Asserts.fail("Bad response: "+response.getStatus()+" "+response.readEntity(String.class)); |
| } |
| } |
| |
| private static boolean tasksContain(List<TaskSummary> tasks, Task<?> leaf) { |
| for (TaskSummary ts: tasks) { |
| if (ts.getId().equals(leaf.getId())) return true; |
| } |
| return false; |
| } |
| |
| private int depthOf(Task<?> t) { |
| int depth = -1; |
| while (t!=null) { |
| t = t.getSubmittedByTask(); |
| depth++; |
| } |
| return depth; |
| } |
| |
| private Task<?> waitForCompletedDescendantWithChildAndSibling(Task<?> tRoot, Task<?> t, CountdownTimer timer, int depthSoFar) { |
| while (timer.isLive()) { |
| Iterable<Task<?>> children = ((HasTaskChildren)t).getChildren(); |
| Iterator<Task<?>> ci = children.iterator(); |
| Task<?> bestFinishedDescendant = null; |
| while (ci.hasNext()) { |
| Task<?> tc = ci.next(); |
| Task<?> finishedDescendant = waitForCompletedDescendantWithChildAndSibling(tRoot, tc, timer, depthSoFar+1); |
| if (depthOf(finishedDescendant) > depthOf(bestFinishedDescendant)) { |
| bestFinishedDescendant = finishedDescendant; |
| } |
| int finishedDescendantDepth = depthOf(bestFinishedDescendant); |
| // log.info("finished "+tc+", depth "+finishedDescendantDepth); |
| if (finishedDescendantDepth < 2) { |
| if (ci.hasNext()) continue; |
| throw new IllegalStateException("not deep enough: "+finishedDescendantDepth); |
| } |
| if (finishedDescendantDepth == depthSoFar+1) { |
| // child completed; now check we complete soon, and assert we have siblings |
| if (ci.hasNext()) continue; |
| if (!t.blockUntilEnded(timer.getDurationRemaining())) { |
| Dumper.dumpInfo(tRoot); |
| // log.info("Incomplete after "+t+": "+t.getStatusDetail(false)); |
| throw Exceptions.propagate( new TimeoutException("parent didn't complete after child depth "+finishedDescendantDepth) ); |
| } |
| } |
| if (finishedDescendantDepth == depthSoFar+2) { |
| if (Iterables.size(children)<2) { |
| Dumper.dumpInfo(tRoot); |
| throw new IllegalStateException("finished child's parent has no sibling"); |
| } |
| } |
| |
| return bestFinishedDescendant; |
| } |
| Thread.yield(); |
| |
| // leaf nodeƄ |
| if (t.isDone()) return t; |
| } |
| throw Exceptions.propagate( new TimeoutException("expired waiting for children") ); |
| } |
| |
| } |