You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by dr...@apache.org on 2017/03/23 12:45:43 UTC

[3/4] brooklyn-server git commit: UsageListener.onAppEvent called for CREATED

UsageListener.onAppEvent called for CREATED


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/e9bd30c6
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/e9bd30c6
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/e9bd30c6

Branch: refs/heads/master
Commit: e9bd30c6fbb70c96b977dc3bdb1fa5dadb081997
Parents: 7547432
Author: Aled Sage <al...@gmail.com>
Authored: Wed Mar 22 16:17:37 2017 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Mar 22 17:07:25 2017 +0000

----------------------------------------------------------------------
 .../core/entity/AbstractApplication.java        |   8 ++
 .../core/mgmt/usage/RecordingUsageListener.java |  50 +++++++-
 .../rest/resources/UsageResourceTest.java       |  18 +--
 .../ApplicationUsageTrackingRebindTest.java     | 105 ++++++++++++++++
 .../usage/ApplicationUsageTrackingTest.java     | 123 +++++++++++++++----
 .../mgmt/usage/LocationUsageTrackingTest.java   |  10 +-
 6 files changed, 271 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java b/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
index 55b0c27..86007cd 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
@@ -136,6 +136,14 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application created but not yet started, at "+Time.makeDateString());
     }
 
+    @Override
+    public void onManagementStarted() {
+        super.onManagementStarted();
+        if (!isRebinding()) {
+            recordApplicationEvent(Lifecycle.CREATED);
+        }
+    }
+
     /**
      * Default start will start all Startable children (child.start(Collection<? extends Location>)),
      * calling preStart(locations) first and postStart(locations) afterwards.

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/core/src/test/java/org/apache/brooklyn/core/mgmt/usage/RecordingUsageListener.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/usage/RecordingUsageListener.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/usage/RecordingUsageListener.java
index 73f50d6..c4749a9 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/usage/RecordingUsageListener.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/usage/RecordingUsageListener.java
@@ -19,13 +19,22 @@
 
 package org.apache.brooklyn.core.mgmt.usage;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
 import java.util.List;
 
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent;
 import org.apache.brooklyn.core.mgmt.usage.LocationUsage.LocationEvent;
-import org.apache.brooklyn.core.mgmt.usage.UsageListener;
+import org.apache.brooklyn.core.objs.proxy.EntityProxy;
 import org.apache.brooklyn.util.collections.MutableList;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
@@ -66,4 +75,43 @@ public class RecordingUsageListener implements UsageListener {
         }
         return ImmutableList.copyOf(result);
     }
+    
+    public void assertAppEvent(int index, Application expectedApp, Lifecycle expectedState, String errMsg) {
+        List<?> actual = getApplicationEvents().get(index);
+        ApplicationMetadata appMetadata = (ApplicationMetadata) actual.get(1);
+        ApplicationEvent appEvent = (ApplicationEvent) actual.get(2);
+        
+        assertEquals(appMetadata.getApplication(), expectedApp, errMsg);
+        assertTrue(appMetadata.getApplication() instanceof EntityProxy, errMsg);
+        assertEquals(appMetadata.getApplicationId(), expectedApp.getId(), errMsg);
+        assertNotNull(appMetadata.getApplicationName(), errMsg);
+        assertEquals(appMetadata.getCatalogItemId(), expectedApp.getCatalogItemId(), errMsg);
+        assertNotNull(appMetadata.getEntityType(), errMsg);
+        assertNotNull(appMetadata.getMetadata(), errMsg);
+        assertEquals(appEvent.getState(), expectedState, errMsg);
+    }
+    
+    public void assertLocEvent(int index, Location expectedLoc, Application expectedApp, Lifecycle expectedState, String errMsg) {
+        List<?> actual = getLocationEvents().get(index);
+        LocationMetadata locMetadata = (LocationMetadata) actual.get(1);
+        LocationEvent locEvent = (LocationEvent) actual.get(2);
+        
+        assertEquals(locMetadata.getLocation(), expectedLoc, errMsg);
+        assertEquals(locMetadata.getLocationId(), expectedLoc.getId(), errMsg);
+        assertNotNull(locMetadata.getMetadata(), errMsg);
+        assertEquals(locEvent.getApplicationId(), expectedApp.getId(), errMsg);
+        assertEquals(locEvent.getState(), Lifecycle.CREATED, errMsg);
+    }
+
+    public void assertHasAppEvent(Application expectedApp, Lifecycle expectedState, String errMsg) {
+        for (List<?> contender : getApplicationEvents()) {
+            ApplicationMetadata appMetadata = (ApplicationMetadata) contender.get(1);
+            ApplicationEvent appEvent = (ApplicationEvent) contender.get(2);
+            if (Objects.equal(appMetadata.getApplication(), expectedApp) && appEvent.getState() == expectedState) {
+                return;
+            }
+        }
+        
+        fail(errMsg);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
index d5780e6..7d5b6a8 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
@@ -102,7 +102,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         Iterable<UsageStatistics> usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
         UsageStatistics usage = Iterables.getOnlyElement(usages);
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
 
         // check app ignored if endCalendar before app started
         response = client().path("/usage/applications").query("start", 0).query("end", preStart.getTime().getTime()-1).get();
@@ -138,8 +138,8 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
         usage = Iterables.getOnlyElement(usages);
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
-        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(3, 4), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
 
         long afterPostDelete = postDelete.getTime().getTime()+1;
         waitForFuture(afterPostDelete);
@@ -167,12 +167,12 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         Response response = client().path("/usage/applications/" + appId).get();
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         UsageStatistics usage = response.readEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
 
         // Time-constrained requests
         response = client().path("/usage/applications/" + appId).query("start", "1970-01-01T00:00:00-0100").get();
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
         
         response = client().path("/usage/applications/" + appId).query("start", "9999-01-01T00:00:00+0100").get();
         assertTrue(response.getStatus() >= 400, "end defaults to NOW, so future start should fail, instead got code "+response.getStatus());
@@ -183,7 +183,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
 
         response = client().path("/usage/applications/" + appId).query("end", "9999-01-01T00:00:00+0100").get();
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
 
         response = client().path("/usage/applications/" + appId).query("start", "9999-01-01T00:00:00+0100").query("end", "9999-02-01T00:00:00+0100").get();
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
@@ -191,7 +191,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
 
         response = client().path("/usage/applications/" + appId).query("start", "1970-01-01T00:00:00-0100").query("end", "9999-01-01T00:00:00+0100").get();
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
         
         response = client().path("/usage/applications/" + appId).query("end", "1970-01-01T00:00:00-0100").get();
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
@@ -206,8 +206,8 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         response = client().path("/usage/applications/" + appId).get();
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         usage = response.readEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
-        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.ACCEPTED, Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(3, 4), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
 
         // Deleted app not returned if terminated before time range begins
         long afterPostDelete = postDelete.getTime().getTime()+1;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingRebindTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingRebindTest.java
new file mode 100644
index 0000000..e01a298
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingRebindTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.entity.software.base.test.core.mgmt.usage;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotSame;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalUsageManagerTest.RecordingStaticUsageListener;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixture;
+import org.apache.brooklyn.core.mgmt.usage.UsageManager;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class ApplicationUsageTrackingRebindTest extends RebindTestFixture<TestApplication> {
+
+    @BeforeMethod(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        RecordingStaticUsageListener.clearInstances();
+        super.setUp();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            super.tearDown();
+        } finally {
+            RecordingStaticUsageListener.clearInstances();
+        }
+    }
+
+    @Override
+    protected TestApplication createApp() {
+        return null; // no-op
+    }
+    
+    @Override
+    protected BrooklynProperties createBrooklynProperties() {
+        // TODO Need tests in brooklyn that can register multiple usage-listeners
+        // (e.g. for metering and for amp-cluster).
+        BrooklynProperties result = BrooklynProperties.Factory.newEmpty();
+        result.put(UsageManager.USAGE_LISTENERS, RecordingStaticUsageListener.class.getName());
+        return result;
+    }
+    
+    @Test
+    public void testUsageListenerReceivesEventsAfterRebind() throws Exception {
+        final RecordingStaticUsageListener origListener = RecordingStaticUsageListener.getLastInstance();
+
+        // Expect CREATED
+        final TestApplication app = mgmt().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = origListener.getApplicationEvents();
+                assertEquals(events.size(), 1, "events="+events);
+                origListener.assertAppEvent(0, app, Lifecycle.CREATED, "events="+events);
+            }});
+        
+        // After rebind, expect it to have a new listener
+        rebind();
+        final TestApplication newApp = (TestApplication) mgmt().getEntityManager().getEntity(app.getId());
+        final RecordingStaticUsageListener newListener = RecordingStaticUsageListener.getLastInstance();
+        assertNotSame(origListener, newListener);
+        
+        // Expect STARTING and RUNNING on the new listener (but not CREATED again)
+        newApp.start(ImmutableList.<Location>of());
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = newListener.getApplicationEvents();
+                assertEquals(events.size(), 2, "events="+events);
+                newListener.assertAppEvent(0, newApp, Lifecycle.STARTING, "events="+events);
+                newListener.assertAppEvent(1, newApp, Lifecycle.RUNNING, "events="+events);
+            }});
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java
index adcf6bc..a4c983a 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java
@@ -20,21 +20,19 @@ package org.apache.brooklyn.entity.software.base.test.core.mgmt.usage;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage;
 import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent;
 import org.apache.brooklyn.core.mgmt.usage.RecordingUsageListener;
-import org.apache.brooklyn.core.mgmt.usage.UsageListener.ApplicationMetadata;
-import org.apache.brooklyn.core.objs.proxy.EntityProxy;
 import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.test.Asserts;
@@ -47,9 +45,11 @@ import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 
 public class ApplicationUsageTrackingTest extends BrooklynMgmtUnitTestSupport {
 
+    @SuppressWarnings("unused")
     private static final Logger LOG = LoggerFactory.getLogger(ApplicationUsageTrackingTest.class);
 
     protected TestApplication app;
@@ -61,31 +61,105 @@ public class ApplicationUsageTrackingTest extends BrooklynMgmtUnitTestSupport {
     }
 
     @Test
-    public void testAddAndRemoveUsageListener() throws Exception {
+    public void testUsageListenerReceivesEvents() throws Exception {
         final RecordingUsageListener listener = new RecordingUsageListener();
         mgmt.getUsageManager().addUsageListener(listener);
-        
+
+        // Expect CREATED
         app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = listener.getApplicationEvents();
+                assertEquals(events.size(), 1, "events="+events);
+                listener.assertAppEvent(0, app, Lifecycle.CREATED, "events="+events);
+            }});
+
+        // Expect STARTING and RUNNING
         app.setCatalogItemId("testCatalogItem");
         app.start(ImmutableList.<Location>of());
 
         Asserts.succeedsEventually(new Runnable() {
             @Override public void run() {
                 List<List<?>> events = listener.getApplicationEvents();
-                assertEquals(events.size(), 2, "events="+events); // expect STARTING and RUNNING
-                ApplicationMetadata appMetadata = (ApplicationMetadata) events.get(0).get(1);
-                ApplicationEvent appEvent = (ApplicationEvent) events.get(0).get(2);
-                
-                assertEquals(appMetadata.getApplication(), app, "events="+events);
-                assertTrue(appMetadata.getApplication() instanceof EntityProxy, "events="+events);
-                assertEquals(appMetadata.getApplicationId(), app.getId(), "events="+events);
-                assertNotNull(appMetadata.getApplicationName(), "events="+events);
-                assertEquals(appMetadata.getCatalogItemId(), app.getCatalogItemId(), "events="+events);
-                assertNotNull(appMetadata.getEntityType(), "events="+events);
-                assertNotNull(appMetadata.getMetadata(), "events="+events);
-                assertEquals(appEvent.getState(), Lifecycle.STARTING, "events="+events);
+                assertEquals(events.size(), 3, "events="+events);
+                listener.assertAppEvent(1, app, Lifecycle.STARTING, "events="+events);
+                listener.assertAppEvent(2, app, Lifecycle.RUNNING, "events="+events);
+            }});
+
+        // Expect STOPPING, STOPPED and DESTROYED
+        app.stop();
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = listener.getApplicationEvents();
+                assertEquals(events.size(), 6, "events="+events);
+                listener.assertAppEvent(3, app, Lifecycle.STOPPING, "events="+events);
+                listener.assertAppEvent(4, app, Lifecycle.STOPPED, "events="+events);
+                listener.assertAppEvent(5, app, Lifecycle.DESTROYED, "events="+events);
+            }});
+    }
+    
+
+    @Test
+    public void testUsageListenerCanRecogniseTopLevelApps() throws Exception {
+        final LinkedHashSet<Application> topLevelApps = Sets.newLinkedHashSet();
+        final RecordingUsageListener listener = new RecordingUsageListener() {
+            public void onApplicationEvent(ApplicationMetadata app, ApplicationEvent event) {
+                if (app.getApplication().getParent() == null) {
+                    topLevelApps.add(app.getApplication());
+                }
+                super.onApplicationEvent(app, event);
+            }
+        };
+        mgmt.getUsageManager().addUsageListener(listener);
+
+        // Expect CREATED
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class)
+                .child(EntitySpec.create(TestApplication.class)));
+        final TestApplication childApp = (TestApplication) Iterables.getOnlyElement(app.getChildren());
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = listener.getApplicationEvents();
+                assertEquals(events.size(), 2, "events="+events);
+                listener.assertHasAppEvent(app, Lifecycle.CREATED, "events="+events);
+                listener.assertHasAppEvent(app, Lifecycle.CREATED, "events="+events);
+            }});
+        assertEquals(topLevelApps, ImmutableSet.of(app));
+        
+        listener.clearEvents();
+        topLevelApps.clear();
+
+        // Expect STOPPING, STOPPED and DESTROYED
+        app.stop();
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = listener.getApplicationEvents();
+                listener.assertHasAppEvent(app, Lifecycle.DESTROYED, "events="+events);
+                listener.assertHasAppEvent(childApp, Lifecycle.DESTROYED, "events="+events);
             }});
+        
+        // TODO By the point of being DESTROYED, we've cleared the app.parent(), so we can't tell
+        // if it was a top-level app anymore. Therefore we are not asserting:
+        //    assertEquals(topLevelApps, ImmutableSet.of(app));
+    }
+    
+    @Test
+    public void testAddAndRemoveUsageListener() throws Exception {
+        final RecordingUsageListener listener = new RecordingUsageListener();
+        mgmt.getUsageManager().addUsageListener(listener);
 
+        // Expect CREATED
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                List<List<?>> events = listener.getApplicationEvents();
+                assertEquals(events.size(), 1, "events="+events);
+                listener.assertAppEvent(0, app, Lifecycle.CREATED, "events="+events);
+            }});
 
         // Remove the listener; will get no more notifications
         listener.clearEvents();
@@ -110,8 +184,9 @@ public class ApplicationUsageTrackingTest extends BrooklynMgmtUnitTestSupport {
         Set<ApplicationUsage> usages1 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue());
         ApplicationUsage usage1 = Iterables.getOnlyElement(usages1);
         assertApplicationUsage(usage1, app);
-        assertApplicationEvent(usage1.getEvents().get(0), Lifecycle.STARTING, preStart, postStart);
-        assertApplicationEvent(usage1.getEvents().get(1), Lifecycle.RUNNING, preStart, postStart);
+        assertApplicationEvent(usage1.getEvents().get(0), Lifecycle.CREATED, preStart, postStart);
+        assertApplicationEvent(usage1.getEvents().get(1), Lifecycle.STARTING, preStart, postStart);
+        assertApplicationEvent(usage1.getEvents().get(2), Lifecycle.RUNNING, preStart, postStart);
 
         // Stop events
         long preStop = System.currentTimeMillis();
@@ -121,10 +196,10 @@ public class ApplicationUsageTrackingTest extends BrooklynMgmtUnitTestSupport {
         Set<ApplicationUsage> usages2 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue());
         ApplicationUsage usage2 = Iterables.getOnlyElement(usages2);
         assertApplicationUsage(usage2, app);
-        assertApplicationEvent(usage2.getEvents().get(2), Lifecycle.STOPPING, preStop, postStop);
-        assertApplicationEvent(usage2.getEvents().get(3), Lifecycle.STOPPED, preStop, postStop);
+        assertApplicationEvent(usage2.getEvents().get(3), Lifecycle.STOPPING, preStop, postStop);
+        assertApplicationEvent(usage2.getEvents().get(4), Lifecycle.STOPPED, preStop, postStop);
         //Apps unmanage themselves on stop
-        assertApplicationEvent(usage2.getEvents().get(4), Lifecycle.DESTROYED, preStop, postStop);
+        assertApplicationEvent(usage2.getEvents().get(5), Lifecycle.DESTROYED, preStop, postStop);
         
         assertFalse(mgmt.getEntityManager().isManaged(app), "App should already be unmanaged");
         
@@ -132,7 +207,7 @@ public class ApplicationUsageTrackingTest extends BrooklynMgmtUnitTestSupport {
         ApplicationUsage usage3 = Iterables.getOnlyElement(usages3);
         assertApplicationUsage(usage3, app);
         
-        assertEquals(usage3.getEvents().size(), 5, "usage="+usage3);
+        assertEquals(usage3.getEvents().size(), 6, "usage="+usage3);
     }
     
     private void assertApplicationUsage(ApplicationUsage usage, Application expectedApp) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e9bd30c6/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java
index 9eb1f94..999dd7a 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java
@@ -36,7 +36,6 @@ import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.core.mgmt.usage.LocationUsage;
 import org.apache.brooklyn.core.mgmt.usage.LocationUsage.LocationEvent;
 import org.apache.brooklyn.core.mgmt.usage.RecordingUsageListener;
-import org.apache.brooklyn.core.mgmt.usage.UsageListener.LocationMetadata;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
@@ -82,15 +81,8 @@ public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport {
         Asserts.succeedsEventually(new Runnable() {
             @Override public void run() {
                 List<List<?>> events = listener.getLocationEvents();
-                LocationMetadata locMetadata = (LocationMetadata) events.get(0).get(1);
-                LocationEvent locEvent = (LocationEvent) events.get(0).get(2);
-                
                 assertEquals(events.size(), 1, "events="+events);
-                assertEquals(locMetadata.getLocation(), machine, "events="+events);
-                assertEquals(locMetadata.getLocationId(), machine.getId(), "events="+events);
-                assertNotNull(locMetadata.getMetadata(), "events="+events);
-                assertEquals(locEvent.getApplicationId(), app.getId(), "events="+events);
-                assertEquals(locEvent.getState(), Lifecycle.CREATED, "events="+events);
+                listener.assertLocEvent(0, machine, app, Lifecycle.CREATED, "events="+events);
             }});
 
         // Remove the listener; will get no more notifications