You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2014/11/10 16:59:47 UTC
[1/4] incubator-brooklyn git commit: Adds Listener support for
usage/metering info
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 <al...@gmail.com>
Authored: Wed Nov 5 22:44:40 2014 +0000
Committer: Aled Sage <al...@gmail.com>
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);
+ }
+}
[2/4] incubator-brooklyn git commit: TypeCoercions: recursively
unpack for List
Posted by al...@apache.org.
TypeCoercions: recursively unpack for List<T>
- e.g. if given “1,2” for type List<Integer>, then first pass will
coerce to List.of(“1”, “2”), and second pass will coerce to
List.of(1,2)
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/e3154097
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/e3154097
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/e3154097
Branch: refs/heads/master
Commit: e3154097506624c6c385a64fc6a2e5c466039b8d
Parents: beb87db
Author: Aled Sage <al...@gmail.com>
Authored: Fri Nov 7 19:44:14 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Nov 10 11:34:35 2014 +0000
----------------------------------------------------------------------
.../java/brooklyn/util/flags/TypeCoercions.java | 16 ++++++++++++++--
.../brooklyn/util/internal/TypeCoercionsTest.java | 6 ++++++
2 files changed, 20 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e3154097/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index f3caeaf..ec08ad0 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -64,6 +64,7 @@ import brooklyn.util.yaml.Yamls;
import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
+import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBasedTable;
@@ -235,7 +236,18 @@ public class TypeCoercions {
Map<Class, Function> adapters = registry.row(targetType);
for (Map.Entry<Class, Function> entry : adapters.entrySet()) {
if (entry.getKey().isInstance(value)) {
- return (T) entry.getValue().apply(value);
+ T result = (T) entry.getValue().apply(value);
+
+ // Check if need to unwrap again (e.g. if want List<Integer> and are given a String "1,2,3"
+ // then we'll have so far converted to List.of("1", "2", "3"). Call recursively.
+ // First check that value has changed, to avoid stack overflow!
+ if (!Objects.equal(value, result) && targetTypeToken.getType() instanceof ParameterizedType) {
+ // Could duplicate check for `result instanceof Collection` etc; but recursive call
+ // will be fine as if that doesn't match we'll safely reach `targetType.isInstance(value)`
+ // and just return the result.
+ return coerce(result, targetTypeToken);
+ }
+ return result;
}
}
}
@@ -459,7 +471,7 @@ public class TypeCoercions {
return null;
}
- public synchronized static <A,B> void registerAdapter(Class<A> sourceType, Class<B> targetType, Function<A,B> fn) {
+ public synchronized static <A,B> void registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
registry.put(targetType, sourceType, fn);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e3154097/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
index 8ac1e9c..b4d0470 100644
--- a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
@@ -222,6 +222,12 @@ public class TypeCoercionsTest {
}
@Test
+ @SuppressWarnings("serial")
+ public void testCoerceRecursivelyStringToGenericsCollection() {
+ assertEquals(TypeCoercions.coerce("1,2", new TypeToken<List<Integer>>() {}), ImmutableList.of(1, 2));
+ }
+
+ @Test
public void testJsonStringToMapCoercion() {
Map<?,?> s = TypeCoercions.coerce("{ \"a\" : \"1\", b : 2 }", Map.class);
Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2));
[4/4] incubator-brooklyn git commit: This closes #310
Posted by al...@apache.org.
This closes #310
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3cfda7e8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3cfda7e8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3cfda7e8
Branch: refs/heads/master
Commit: 3cfda7e8ffd573dc1144c47f954f0350973b9e3d
Parents: 3610d8a 7ee06ba
Author: Aled Sage <al...@gmail.com>
Authored: Mon Nov 10 15:59:33 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Nov 10 15:59:33 2014 +0000
----------------------------------------------------------------------
.../management/internal/LocalUsageManager.java | 84 ++++++++-
.../internal/NonDeploymentUsageManager.java | 22 +++
.../management/internal/UsageManager.java | 40 +++++
.../java/brooklyn/util/flags/TypeCoercions.java | 16 +-
.../util/internal/TypeCoercionsTest.java | 6 +
.../usage/ApplicationUsageTrackingTest.java | 179 +++++++++++++++++++
.../usage/LocationUsageTrackingTest.java | 108 ++++++++---
.../usage/RecordingUsageListener.java | 70 ++++++++
.../management/usage/UsageListenerTest.java | 108 +++++++++++
9 files changed, 598 insertions(+), 35 deletions(-)
----------------------------------------------------------------------
[3/4] incubator-brooklyn git commit: Read UsageListeners from config
in brooklyn.properties
Posted by al...@apache.org.
Read UsageListeners from config in brooklyn.properties
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/7ee06ba3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/7ee06ba3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/7ee06ba3
Branch: refs/heads/master
Commit: 7ee06ba3c67a6d5ea1cb494b75aabb20b00c13f0
Parents: e315409
Author: Aled Sage <al...@gmail.com>
Authored: Fri Nov 7 19:44:34 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Nov 10 11:39:53 2014 +0000
----------------------------------------------------------------------
.../management/internal/LocalUsageManager.java | 29 +++++
.../management/internal/UsageManager.java | 11 ++
.../management/usage/UsageListenerTest.java | 108 +++++++++++++++++++
3 files changed, 148 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7ee06ba3/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 1bb4a8e..476ea3e 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalUsageManager.java
@@ -20,6 +20,7 @@ package brooklyn.management.internal;
import static com.google.common.base.Preconditions.checkNotNull;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,8 +42,12 @@ import brooklyn.location.basic.LocationInternal;
import brooklyn.management.usage.ApplicationUsage;
import brooklyn.management.usage.LocationUsage;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.javalang.Reflections;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -59,6 +64,23 @@ public class LocalUsageManager implements UsageManager {
private static final Logger log = LoggerFactory.getLogger(LocalUsageManager.class);
+ // Register a coercion from String->UsageListener, so that USAGE_LISTENERS defined in brooklyn.properties
+ // will be instantiated, given their class names.
+ static {
+ TypeCoercions.registerAdapter(String.class, UsageListener.class, new Function<String, UsageListener>() {
+ @Override public UsageListener apply(String input) {
+ // TODO Want to use classLoader = mgmt.getCatalog().getRootClassLoader();
+ ClassLoader classLoader = LocalUsageManager.class.getClassLoader();
+ Optional<Object> result = Reflections.invokeConstructorWithArgs(classLoader, input);
+ if (result.isPresent()) {
+ return (UsageListener) result.get();
+ } else {
+ throw new IllegalStateException("Failed to create UsageListener from class name '"+input+"' using no-arg constructor");
+ }
+ }
+ });
+ }
+
@VisibleForTesting
public static final String APPLICATION_USAGE_KEY = "usage-application";
@@ -77,6 +99,13 @@ public class LocalUsageManager implements UsageManager {
public LocalUsageManager(LocalManagementContext managementContext) {
this.managementContext = checkNotNull(managementContext, "managementContext");
+
+ Collection<UsageListener> listeners = managementContext.getBrooklynProperties().getConfig(UsageManager.USAGE_LISTENERS);
+ if (listeners != null) {
+ for (UsageListener listener : listeners) {
+ addUsageListener(listener);
+ }
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7ee06ba3/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 35e602f..56a5ace 100644
--- a/core/src/main/java/brooklyn/management/internal/UsageManager.java
+++ b/core/src/main/java/brooklyn/management/internal/UsageManager.java
@@ -18,10 +18,13 @@
*/
package brooklyn.management.internal;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import brooklyn.config.ConfigKey;
import brooklyn.entity.Application;
+import brooklyn.entity.basic.ConfigKeys;
import brooklyn.entity.basic.Lifecycle;
import brooklyn.location.Location;
import brooklyn.management.usage.ApplicationUsage;
@@ -31,10 +34,18 @@ import brooklyn.management.usage.LocationUsage.LocationEvent;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
@Beta
public interface UsageManager {
+ @SuppressWarnings("serial")
+ public static final ConfigKey<List<UsageListener>> USAGE_LISTENERS = ConfigKeys.newConfigKey(
+ new TypeToken<List<UsageListener>>() {},
+ "brooklyn.usageManager.listeners", "Optional usage listeners (i.e. for metering)",
+ ImmutableList.<UsageListener>of());
+
public interface UsageListener {
public static final UsageListener NOOP = new UsageListener() {
@Override public void onApplicationEvent(String applicationId, String applicationName, String entityType,
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7ee06ba3/software/base/src/test/java/brooklyn/management/usage/UsageListenerTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/management/usage/UsageListenerTest.java b/software/base/src/test/java/brooklyn/management/usage/UsageListenerTest.java
new file mode 100644
index 0000000..0cc27c7
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/management/usage/UsageListenerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.assertTrue;
+
+import java.util.List;
+
+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.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import brooklyn.location.Location;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.management.internal.UsageManager;
+import brooklyn.management.internal.UsageManager.UsageListener;
+import brooklyn.test.Asserts;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.test.entity.TestApplication;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class UsageListenerTest {
+
+ // Also see {Application|Location}UsageTrackingTest for listener functionality
+
+ 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 {
+ RecordingStaticUsageListener.clearInstances();
+ }
+
+ @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;
+ RecordingStaticUsageListener.clearInstances();
+ }
+ }
+
+ @Test
+ public void testAddUsageListenerViaProperties() throws Exception {
+ BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty();
+ brooklynProperties.put(UsageManager.USAGE_LISTENERS, RecordingStaticUsageListener.class.getName());
+ mgmt = LocalManagementContextForTests.newInstance(brooklynProperties);
+
+ app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+ app.start(ImmutableList.<Location>of());
+
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ List<List<?>> events = RecordingStaticUsageListener.getInstance().getApplicationEvents();
+ assertTrue(events.size() > 0, "events="+events); // expect some events
+ }});
+ }
+
+ public static class RecordingStaticUsageListener extends RecordingUsageListener implements UsageListener {
+ private static final List<RecordingStaticUsageListener> STATIC_INSTANCES = Lists.newCopyOnWriteArrayList();
+
+ public static RecordingStaticUsageListener getInstance() {
+ return Iterables.getOnlyElement(STATIC_INSTANCES);
+ }
+
+ public static void clearInstances() {
+ STATIC_INSTANCES.clear();
+ }
+
+ public RecordingStaticUsageListener() {
+ // Bad to leak a ref to this before constructor finished, but we'll live with it because
+ // it's just test code!
+ STATIC_INSTANCES.add(this);
+ }
+ }
+}