Repository: incubator-brooklyn Updated Branches: refs/heads/master 3610d8a5f -> 3cfda7e8f
Adds Listener support for usage/metering info Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/beb87db7 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/beb87db7 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/beb87db7 Branch: refs/heads/master Commit: beb87db7c78dc57258626ce0dc53e5cece39846d Parents: c323b00 Author: Aled Sage <[email protected]> Authored: Wed Nov 5 22:44:40 2014 +0000 Committer: Aled Sage <[email protected]> Committed: Mon Nov 10 11:34:30 2014 +0000 ---------------------------------------------------------------------- .../management/internal/LocalUsageManager.java | 55 +++++- .../internal/NonDeploymentUsageManager.java | 22 +++ .../management/internal/UsageManager.java | 29 +++ .../usage/ApplicationUsageTrackingTest.java | 179 +++++++++++++++++++ .../usage/LocationUsageTrackingTest.java | 108 ++++++++--- .../usage/RecordingUsageListener.java | 70 ++++++++ 6 files changed, 430 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java b/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java index 65efe44..1bb4a8e 100644 --- a/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java +++ b/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java @@ -20,9 +20,12 @@ package brooklyn.management.internal; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +40,13 @@ import brooklyn.location.basic.LocationConfigKeys; import brooklyn.location.basic.LocationInternal; import brooklyn.management.usage.ApplicationUsage; import brooklyn.management.usage.LocationUsage; +import brooklyn.util.exceptions.Exceptions; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ThreadFactoryBuilder; public class LocalUsageManager implements UsageManager { @@ -62,13 +68,19 @@ public class LocalUsageManager implements UsageManager { private final LocalManagementContext managementContext; private final Object mutex = new Object(); + + private final List<UsageListener> listeners = Lists.newCopyOnWriteArrayList(); + private ExecutorService listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("brooklyn-usagemanager-listener-%d") + .build()); + public LocalUsageManager(LocalManagementContext managementContext) { this.managementContext = checkNotNull(managementContext, "managementContext"); } @Override - public void recordApplicationEvent(Application app, Lifecycle state) { + public void recordApplicationEvent(final Application app, final Lifecycle state) { log.debug("Storing application lifecycle usage event: application {} in state {}", new Object[] {app, state}); ConcurrentMap<String, ApplicationUsage> eventMap = managementContext.getStorage().getMap(APPLICATION_USAGE_KEY); synchronized (mutex) { @@ -76,8 +88,21 @@ public class LocalUsageManager implements UsageManager { if (usage == null) { usage = new ApplicationUsage(app.getId(), app.getDisplayName(), app.getEntityType().getName(), ((EntityInternal)app).toMetadataRecord()); } - usage.addEvent(new ApplicationUsage.ApplicationEvent(state)); + final ApplicationUsage.ApplicationEvent event = new ApplicationUsage.ApplicationEvent(state); + usage.addEvent(event); eventMap.put(app.getId(), usage); + + for (final UsageListener listener : listeners) { + listenerExecutor.execute(new Runnable() { + public void run() { + try { + listener.onApplicationEvent(app.getId(), app.getDisplayName(), app.getEntityType().getName(), ((EntityInternal)app).toMetadataRecord(), event); + } catch (Exception e) { + log.error("Problem notifying listener "+listener+" of applicationEvent("+app+", "+state+")", e); + Exceptions.propagateIfFatal(e); + } + }}); + } } } @@ -86,7 +111,7 @@ public class LocalUsageManager implements UsageManager { * record if one does not already exist). */ @Override - public void recordLocationEvent(Location loc, Lifecycle state) { + public void recordLocationEvent(final Location loc, final Lifecycle state) { // TODO This approach (i.e. recording events on manage/unmanage would not work for // locations that are reused. For example, in a FixedListMachineProvisioningLocation // the ssh machine location is returned to the pool and handed back out again. @@ -116,7 +141,7 @@ public class LocalUsageManager implements UsageManager { Entity caller = (Entity) callerContext; String entityTypeName = caller.getEntityType().getName(); String appId = caller.getApplicationId(); - LocationUsage.LocationEvent event = new LocationUsage.LocationEvent(state, caller.getId(), entityTypeName, appId); + final LocationUsage.LocationEvent event = new LocationUsage.LocationEvent(state, caller.getId(), entityTypeName, appId); ConcurrentMap<String, LocationUsage> usageMap = managementContext.getStorage().<String, LocationUsage>getMap(LOCATION_USAGE_KEY); synchronized (mutex) { @@ -126,6 +151,18 @@ public class LocalUsageManager implements UsageManager { } usage.addEvent(event); usageMap.put(loc.getId(), usage); + + for (final UsageListener listener : listeners) { + listenerExecutor.execute(new Runnable() { + public void run() { + try { + listener.onLocationEvent(loc.getId(), ((LocationInternal)loc).toMetadataRecord(), event); + } catch (Exception e) { + log.error("Problem notifying listener "+listener+" of locationEvent("+loc+", "+state+")", e); + Exceptions.propagateIfFatal(e); + } + }}); + } } } else { // normal for high-level locations @@ -194,4 +231,14 @@ public class LocalUsageManager implements UsageManager { } return result; } + + @Override + public void addUsageListener(UsageListener listener) { + listeners.add(listener); + } + + @Override + public void removeUsageListener(UsageListener listener) { + listeners.remove(listener); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/core/src/main/java/brooklyn/management/internal/NonDeploymentUsageManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentUsageManager.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentUsageManager.java index 9eb79d4..f8e6a81 100644 --- a/core/src/main/java/brooklyn/management/internal/NonDeploymentUsageManager.java +++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentUsageManager.java @@ -31,6 +31,10 @@ import com.google.common.base.Predicate; public class NonDeploymentUsageManager implements UsageManager { + // TODO All the `isInitialManagementContextReal()` code-checks is a code-smell. + // Expect we can delete a lot of this once we guarantee that all entities are + // instantiated via EntitySpec / EntityManager. Until then, we'll live with this. + private final ManagementContextInternal initialManagementContext; public NonDeploymentUsageManager(ManagementContextInternal initialManagementContext) { @@ -94,4 +98,22 @@ public class NonDeploymentUsageManager implements UsageManager { throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation"); } } + + @Override + public void addUsageListener(UsageListener listener) { + if (isInitialManagementContextReal()) { + initialManagementContext.getUsageManager().addUsageListener(listener); + } else { + throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation"); + } + } + + @Override + public void removeUsageListener(UsageListener listener) { + if (isInitialManagementContextReal()) { + initialManagementContext.getUsageManager().removeUsageListener(listener); + } else { + throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation"); + } + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/core/src/main/java/brooklyn/management/internal/UsageManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/management/internal/UsageManager.java b/core/src/main/java/brooklyn/management/internal/UsageManager.java index 809ef66..35e602f 100644 --- a/core/src/main/java/brooklyn/management/internal/UsageManager.java +++ b/core/src/main/java/brooklyn/management/internal/UsageManager.java @@ -18,13 +18,16 @@ */ package brooklyn.management.internal; +import java.util.Map; import java.util.Set; import brooklyn.entity.Application; import brooklyn.entity.basic.Lifecycle; import brooklyn.location.Location; import brooklyn.management.usage.ApplicationUsage; +import brooklyn.management.usage.ApplicationUsage.ApplicationEvent; import brooklyn.management.usage.LocationUsage; +import brooklyn.management.usage.LocationUsage.LocationEvent; import com.google.common.annotations.Beta; import com.google.common.base.Predicate; @@ -32,6 +35,19 @@ import com.google.common.base.Predicate; @Beta public interface UsageManager { + public interface UsageListener { + public static final UsageListener NOOP = new UsageListener() { + @Override public void onApplicationEvent(String applicationId, String applicationName, String entityType, + Map<String, String> metadata, ApplicationEvent event) {} + @Override public void onLocationEvent(String locationId, Map<String, String> metadata, LocationEvent event) {} + }; + + void onApplicationEvent(String applicationId, String applicationName, String entityType, + Map<String, String> metadata, ApplicationEvent event); + + void onLocationEvent(String locationId, Map<String, String> metadata, LocationEvent event); + } + /** * Adds this application event to the usage record for the given app (creating the usage * record if one does not already exist). @@ -66,4 +82,17 @@ public interface UsageManager { */ Set<ApplicationUsage> getApplicationUsage(Predicate<? super ApplicationUsage> filter); + /** + * Adds the given listener, to be notified on recording of application/location events. + * The listener notifications may be asynchronous. + * + * As of 0.7.0, the listener is not persisted so will be lost on restart/rebind. This + * behaviour may change in a subsequent release. + */ + void addUsageListener(UsageListener listener); + + /** + * Removes the given listener. + */ + void removeUsageListener(UsageListener listener); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/software/base/src/test/java/brooklyn/management/usage/ApplicationUsageTrackingTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/brooklyn/management/usage/ApplicationUsageTrackingTest.java b/software/base/src/test/java/brooklyn/management/usage/ApplicationUsageTrackingTest.java new file mode 100644 index 0000000..e90f2df --- /dev/null +++ b/software/base/src/test/java/brooklyn/management/usage/ApplicationUsageTrackingTest.java @@ -0,0 +1,179 @@ +/* + * 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.management.usage; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.Application; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.location.Location; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.management.usage.ApplicationUsage.ApplicationEvent; +import brooklyn.test.Asserts; +import brooklyn.test.entity.LocalManagementContextForTests; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.time.Time; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class ApplicationUsageTrackingTest { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationUsageTrackingTest.class); + + protected TestApplication app; + protected ManagementContextInternal mgmt; + + protected boolean shouldSkipOnBoxBaseDirResolution() { + return true; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + mgmt = LocalManagementContextForTests.newInstance(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + try { + if (mgmt != null) Entities.destroyAll(mgmt); + } catch (Throwable t) { + LOG.error("Caught exception in tearDown method", t); + } finally { + mgmt = null; + } + } + + @Test + public void testUsageInitiallyEmpty() { + Set<ApplicationUsage> usage = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + assertEquals(usage, ImmutableSet.of()); + } + + @Test + public void testAddAndRemoveUsageListener() throws Exception { + final RecordingUsageListener listener = new RecordingUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + 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 + + String appId = (String) events.get(0).get(1); + String appName = (String) events.get(0).get(2); + String entityType = (String) events.get(0).get(3); + Map<?,?> metadata = (Map<?, ?>) events.get(0).get(4); + ApplicationEvent appEvent = (ApplicationEvent) events.get(0).get(5); + + assertEquals(appId, app.getId(), "events="+events); + assertNotNull(appName, "events="+events); + assertNotNull(entityType, "events="+events); + assertNotNull(metadata, "events="+events); + assertEquals(appEvent.getState(), Lifecycle.STARTING, "events="+events); + }}); + + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.start(ImmutableList.<Location>of()); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test + public void testUsageIncludesStartAndStopEvents() { + // Start event + long preStart = System.currentTimeMillis(); + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.start(ImmutableList.<Location>of()); + long postStart = System.currentTimeMillis(); + + 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); + + // Stop events + long preStop = System.currentTimeMillis(); + app.stop(); + long postStop = System.currentTimeMillis(); + + 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); + + // Destroy + long preDestroy = System.currentTimeMillis(); + Entities.unmanage(app); + long postDestroy = System.currentTimeMillis(); + + Set<ApplicationUsage> usages3 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + ApplicationUsage usage3 = Iterables.getOnlyElement(usages3); + assertApplicationUsage(usage3, app); + assertApplicationEvent(usage3.getEvents().get(4), Lifecycle.DESTROYED, preDestroy, postDestroy); + + assertEquals(usage3.getEvents().size(), 5, "usage="+usage3); + } + + private void assertApplicationUsage(ApplicationUsage usage, Application expectedApp) { + assertEquals(usage.getApplicationId(), expectedApp.getId()); + assertEquals(usage.getApplicationName(), expectedApp.getDisplayName()); + assertEquals(usage.getEntityType(), expectedApp.getEntityType().getName()); + } + + private void assertApplicationEvent(ApplicationEvent event, Lifecycle expectedState, long preEvent, long postEvent) { + // Saw times differ by 1ms - perhaps different threads calling currentTimeMillis() can get out-of-order times?! + final int TIMING_GRACE = 5; + + assertEquals(event.getState(), expectedState); + long eventTime = event.getDate().getTime(); + if (eventTime < (preEvent - TIMING_GRACE) || eventTime > (postEvent + TIMING_GRACE)) { + fail("for "+expectedState+": event=" + Time.makeDateString(eventTime) + "("+eventTime + "); " + + "pre=" + Time.makeDateString(preEvent) + " ("+preEvent+ "); " + + "post=" + Time.makeDateString(postEvent) + " ("+postEvent + ")"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/software/base/src/test/java/brooklyn/management/usage/LocationUsageTrackingTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/brooklyn/management/usage/LocationUsageTrackingTest.java b/software/base/src/test/java/brooklyn/management/usage/LocationUsageTrackingTest.java index c390b04..3ad836d 100644 --- a/software/base/src/test/java/brooklyn/management/usage/LocationUsageTrackingTest.java +++ b/software/base/src/test/java/brooklyn/management/usage/LocationUsageTrackingTest.java @@ -19,7 +19,8 @@ package brooklyn.management.usage; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; import java.util.List; import java.util.Map; @@ -29,15 +30,17 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import brooklyn.entity.BrooklynAppUnitTestSupport; +import brooklyn.entity.Entity; import brooklyn.entity.basic.Lifecycle; import brooklyn.entity.basic.SoftwareProcessEntityTest; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; import brooklyn.location.LocationSpec; import brooklyn.location.NoMachinesAvailableException; import brooklyn.location.basic.LocalhostMachineProvisioningLocation; import brooklyn.location.basic.SshMachineLocation; import brooklyn.management.usage.LocationUsage.LocationEvent; -import brooklyn.util.time.Duration; +import brooklyn.test.Asserts; import brooklyn.util.time.Time; import com.google.common.base.Predicates; @@ -48,8 +51,8 @@ import com.google.common.collect.Iterables; public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport { private DynamicLocalhostMachineProvisioningLocation loc; - - @BeforeMethod(alwaysRun=true) + + @BeforeMethod(alwaysRun = true) @Override public void setUp() throws Exception { super.setUp(); @@ -63,46 +66,68 @@ public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport { } @Test + public void testAddAndRemoveUsageListener() throws Exception { + final RecordingUsageListener listener = new RecordingUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app.createAndManageChild(EntitySpec.create(SoftwareProcessEntityTest.MyService.class)); + app.start(ImmutableList.of(loc)); + final SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + String locId = (String) events.get(0).get(1); + LocationEvent locEvent = (LocationEvent) events.get(0).get(3); + Map<?,?> metadata = (Map<?, ?>) events.get(0).get(2); + + assertEquals(events.size(), 1, "events="+events); + assertEquals(locId, machine.getId(), "events="+events); + assertNotNull(metadata, "events="+events); + assertEquals(locEvent.getApplicationId(), app.getId(), "events="+events); + assertEquals(locEvent.getState(), Lifecycle.CREATED, "events="+events); + }}); + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.stop(); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test public void testUsageIncludesStartAndStopEvents() { SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(EntitySpec.create(SoftwareProcessEntityTest.MyService.class)); - + // Start the app; expect record of location in use long preStart = System.currentTimeMillis(); app.start(ImmutableList.of(loc)); long postStart = System.currentTimeMillis(); SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines()); - + Set<LocationUsage> usages1 = mgmt.getUsageManager().getLocationUsage(Predicates.alwaysTrue()); LocationUsage usage1 = Iterables.getOnlyElement(usages1); - List<LocationEvent> events1 = usage1.getEvents(); - LocationEvent event1 = Iterables.getOnlyElement(events1); - - assertEquals(usage1.getLocationId(), machine.getId()); - assertEquals(event1.getApplicationId(), app.getId()); - assertEquals(event1.getEntityId(), entity.getId()); - assertEquals(event1.getState(), Lifecycle.CREATED); - long event1Time = event1.getDate().getTime(); - assertTrue(event1Time >= preStart && event1Time <= postStart, "event1="+event1Time+"; pre="+preStart+"; post="+postStart); - + assertLocationUsage(usage1, machine); + assertLocationEvent(usage1.getEvents().get(0), entity, Lifecycle.CREATED, preStart, postStart); + // Stop the app; expect record of location no longer in use long preStop = System.currentTimeMillis(); app.stop(); long postStop = System.currentTimeMillis(); - + Set<LocationUsage> usages2 = mgmt.getUsageManager().getLocationUsage(Predicates.alwaysTrue()); LocationUsage usage2 = Iterables.getOnlyElement(usages2); - List<LocationEvent> events2 = usage2.getEvents(); - LocationEvent event2 = events2.get(1); - - assertEquals(events2.get(0).getDate(), event1.getDate()); - assertEquals(usage2.getLocationId(), machine.getId()); - assertEquals(event2.getApplicationId(), app.getId()); - assertEquals(event2.getEntityId(), entity.getId()); - assertEquals(event2.getState(), Lifecycle.DESTROYED); - long event2Time = event2.getDate().getTime(); - assertTrue(event2Time >= preStop && event2Time <= postStop, "event2="+event2Time+"; pre="+preStop+"; post="+postStop); + assertLocationUsage(usage2, machine); + assertLocationEvent(usage2.getEvents().get(1), app.getApplicationId(), entity.getId(), entity.getEntityType().getName(), Lifecycle.DESTROYED, preStop, postStop); + + assertEquals(usage2.getEvents().size(), 2, "usage="+usage2); } - + public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation { private static final long serialVersionUID = 4822009936654077946L; @@ -111,7 +136,7 @@ public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport { System.out.println("called DynamicLocalhostMachineProvisioningLocation.obtain"); return super.obtain(flags); } - + @Override public void release(SshMachineLocation machine) { System.out.println("called DynamicLocalhostMachineProvisioningLocation.release"); @@ -120,4 +145,29 @@ public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport { super.removeChild(machine); } } + + private void assertLocationUsage(LocationUsage usage, Location expectedLoc) { + assertEquals(usage.getLocationId(), expectedLoc.getId(), "usage="+usage); + assertNotNull(usage.getMetadata(), "usage="+usage); + } + + private void assertLocationEvent(LocationEvent event, Entity expectedEntity, Lifecycle expectedState, long preEvent, long postEvent) { + assertLocationEvent(event, expectedEntity.getApplicationId(), expectedEntity.getId(), expectedEntity.getEntityType().getName(), expectedState, preEvent, postEvent); + } + + private void assertLocationEvent(LocationEvent event, String expectedAppId, String expectedEntityId, String expectedEntityType, Lifecycle expectedState, long preEvent, long postEvent) { + // Saw times differ by 1ms - perhaps different threads calling currentTimeMillis() can get out-of-order times?! + final int TIMING_GRACE = 5; + + assertEquals(event.getApplicationId(), expectedAppId); + assertEquals(event.getEntityId(), expectedEntityId); + assertEquals(event.getEntityType(), expectedEntityType); + assertEquals(event.getState(), expectedState); + long eventTime = event.getDate().getTime(); + if (eventTime < (preEvent - TIMING_GRACE) || eventTime > (postEvent + TIMING_GRACE)) { + fail("for "+expectedState+": event=" + Time.makeDateString(eventTime) + "("+eventTime + "); " + + "pre=" + Time.makeDateString(preEvent) + " ("+preEvent+ "); " + + "post=" + Time.makeDateString(postEvent) + " ("+postEvent + ")"); + } + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/beb87db7/software/base/src/test/java/brooklyn/management/usage/RecordingUsageListener.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/brooklyn/management/usage/RecordingUsageListener.java b/software/base/src/test/java/brooklyn/management/usage/RecordingUsageListener.java new file mode 100644 index 0000000..fa5cadc --- /dev/null +++ b/software/base/src/test/java/brooklyn/management/usage/RecordingUsageListener.java @@ -0,0 +1,70 @@ +/* + * 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.management.usage; + +import java.util.List; +import java.util.Map; + +import brooklyn.management.internal.UsageManager.UsageListener; +import brooklyn.management.usage.ApplicationUsage.ApplicationEvent; +import brooklyn.management.usage.LocationUsage.LocationEvent; +import brooklyn.util.collections.MutableList; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class RecordingUsageListener implements UsageListener { + + private final List<List<?>> events = Lists.newCopyOnWriteArrayList(); + + @Override + public void onApplicationEvent(String applicationId, String applicationName, String entityType, + Map<String, String> metadata, ApplicationEvent event) { + events.add(MutableList.of("application", applicationId, applicationName, entityType, metadata, event)); + } + + @Override + public void onLocationEvent(String locationId, Map<String, String> metadata, LocationEvent event) { + events.add(MutableList.of("location", locationId, metadata, event)); + } + + public void clearEvents() { + events.clear(); + } + + public List<List<?>> getEvents() { + return ImmutableList.copyOf(events); + } + + public List<List<?>> getLocationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("location")) result.add(event); + } + return ImmutableList.copyOf(result); + } + + public List<List<?>> getApplicationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("application")) result.add(event); + } + return ImmutableList.copyOf(result); + } +}
