Adds JMX reachable listener, for setting service-down - adds JmxReachableAdapter, for responding to MBean not-reachable - adds AbstractPollHelper.polledListeners (rather than just setting sensors) - JmxAttributeAdapter: -- don't wait 15 seconds for MBean to exist during activate -- and catch exception if connect exception - tests JmxSensorAdapter for when jmx service/mbean unreachable - tests/fixes web-apps for kill, that they report service-down
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/79ddd7e9 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/79ddd7e9 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/79ddd7e9 Branch: refs/heads/0.4.0 Commit: 79ddd7e921f6614f706afc5136087b7c685d5b27 Parents: 191fa83 Author: Aled Sage <[email protected]> Authored: Sat Oct 6 15:30:32 2012 +0100 Committer: Aled Sage <[email protected]> Committed: Tue Oct 9 10:01:36 2012 +0100 ---------------------------------------------------------------------- .../event/adapter/AbstractPollHelper.groovy | 14 +++- .../event/adapter/AbstractSensorAdapter.groovy | 10 +-- .../event/adapter/JmxAttributeAdapter.groovy | 15 ++-- .../java/brooklyn/event/adapter/JmxHelper.java | 2 +- .../event/adapter/JmxObjectNameAdapter.groovy | 5 ++ .../event/adapter/JmxReachableAdapter.java | 78 ++++++++++++++++++++ .../event/adapter/SensorRegistry.groovy | 10 +-- .../java/brooklyn/util/GroovyJavaMethods.groovy | 4 +- .../event/adapter/JmxSensorAdapterTest.groovy | 55 +++++++++++++- 9 files changed, 173 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/AbstractPollHelper.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/AbstractPollHelper.groovy b/core/src/main/java/brooklyn/event/adapter/AbstractPollHelper.groovy index d412b64..51b3107 100644 --- a/core/src/main/java/brooklyn/event/adapter/AbstractPollHelper.groovy +++ b/core/src/main/java/brooklyn/event/adapter/AbstractPollHelper.groovy @@ -18,6 +18,8 @@ public abstract class AbstractPollHelper { final Map<AttributeSensor, Closure> polledSensors = [:] + final List<Closure> polledListeners = [] + boolean lastWasSuccessful = false; AbstractSensorAdapter adapter; @@ -32,6 +34,10 @@ public abstract class AbstractPollHelper { if (old!=null) log.warn "change of provider (discouraged) for sensor ${s} on ${entity}", new Throwable("path of discouraged change"); } + public void addListener(Closure c) { + polledListeners.add(c) + } + ScheduledTask schedule; protected activatePoll() { @@ -50,7 +56,7 @@ public abstract class AbstractPollHelper { } protected boolean isEmpty() { - polledSensors.isEmpty(); + polledSensors.isEmpty() && polledListeners.isEmpty(); } /** implementation-specific generation of AbstractSensorEvaluationContext which is then typically passed to evaluateSensorsOnPollResponse */ @@ -87,6 +93,12 @@ public abstract class AbstractPollHelper { void evaluateSensorsOnResponse(AbstractSensorEvaluationContext response) { polledSensors.each { s, c -> evaluateSensorOnResponse(s, c, response) } + polledListeners.each { + Object v = response.evaluate({it}) + if (v != AbstractSensorEvaluationContext.UNSET) { + it.call(v) + } + } } Object evaluateSensorOnResponse(Sensor<?> s, Closure c, AbstractSensorEvaluationContext response) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/AbstractSensorAdapter.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/AbstractSensorAdapter.groovy b/core/src/main/java/brooklyn/event/adapter/AbstractSensorAdapter.groovy index 4092c01..8107cad 100644 --- a/core/src/main/java/brooklyn/event/adapter/AbstractSensorAdapter.groovy +++ b/core/src/main/java/brooklyn/event/adapter/AbstractSensorAdapter.groovy @@ -44,22 +44,22 @@ public abstract class AbstractSensorAdapter { registry.addActivationLifecycleListeners({ activateAdapter() }, { deactivateAdapter() }) } - private List<Closure> activationListeners = [] - private List<Closure> deactivationListeners = [] - protected void addActivationLifecycleListeners(Closure onUp, Closure onDown) { + private List<Runnable> activationListeners = [] + private List<Runnable> deactivationListeners = [] + protected void addActivationLifecycleListeners(Runnable onUp, Runnable onDown) { activationListeners << onUp deactivationListeners << onDown } protected void activateAdapter() { if (activated) return; //prevent double activation if (log.isDebugEnabled()) log.debug "activating adapter {} for {}", this, entity - activationListeners.each { it.call() } + activationListeners.each { it.run() } activated = true; } protected void deactivateAdapter() { if (log.isDebugEnabled()) log.debug "deactivating adapter {} for {}", this, entity activated = false; - deactivationListeners.each { it.call() } + deactivationListeners.each { it.run() } } protected boolean isConnected() { isActivated() } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/JmxAttributeAdapter.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/JmxAttributeAdapter.groovy b/core/src/main/java/brooklyn/event/adapter/JmxAttributeAdapter.groovy index d76be3d..a917ece 100644 --- a/core/src/main/java/brooklyn/event/adapter/JmxAttributeAdapter.groovy +++ b/core/src/main/java/brooklyn/event/adapter/JmxAttributeAdapter.groovy @@ -1,5 +1,6 @@ package brooklyn.event.adapter; +import groovy.time.TimeDuration import groovy.transform.InheritConstructors import javax.management.ObjectName @@ -65,11 +66,15 @@ public class JmxAttributeAdapter extends AbstractSensorAdapter { @Override protected void activateAdapter() { super.activateAdapter(); - if (adapter.checkObjectNameExists(objectName)) { - if (log.isDebugEnabled()) - log.debug("For $entity ${adapter.helper.url}, MBean ${objectName} exists"); - } else { - log.warn("For $entity ${adapter.helper.url}, MBean ${objectName} does not yet exist; continuing..."); + try { + if (adapter.checkObjectNameExists(objectName, new TimeDuration(0, 0, 0, 0))) { + if (log.isDebugEnabled()) + log.debug("For $entity ${adapter.helper.url}, MBean ${objectName} exists"); + } else { + log.warn("For $entity ${adapter.helper.url}, MBean ${objectName} does not yet exist; continuing..."); + } + } catch (Exception e) { + log.warn("For $entity ${adapter.helper.url}, not reachable for MBean ${objectName}; continuing...", e); } } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/JmxHelper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/JmxHelper.java b/core/src/main/java/brooklyn/event/adapter/JmxHelper.java index 194833f..9664c4f 100644 --- a/core/src/main/java/brooklyn/event/adapter/JmxHelper.java +++ b/core/src/main/java/brooklyn/event/adapter/JmxHelper.java @@ -225,7 +225,7 @@ public class JmxHelper { int attempt = 0; while (currentTime <= endMs) { currentTime = System.currentTimeMillis(); - if (attempt != 0) sleep(100); //sleep 100 to prevent trashing and facilitate interruption + if (attempt != 0) sleep(100); //sleep 100 to prevent thrashing and facilitate interruption if (LOG.isTraceEnabled()) LOG.trace("trying connection to {} at time {}", url, currentTime); try { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/JmxObjectNameAdapter.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/JmxObjectNameAdapter.groovy b/core/src/main/java/brooklyn/event/adapter/JmxObjectNameAdapter.groovy index 8a87cf4..d923e45 100644 --- a/core/src/main/java/brooklyn/event/adapter/JmxObjectNameAdapter.groovy +++ b/core/src/main/java/brooklyn/event/adapter/JmxObjectNameAdapter.groovy @@ -18,6 +18,11 @@ public class JmxObjectNameAdapter { this.adapter = adapter; this.objectName = objectName; } + + JmxReachableAdapter reachable(Map flags=[:]) { + adapter.registry.register(new JmxReachableAdapter(flags, adapter, objectName)); + } + JmxAttributeAdapter attribute(Map flags=[:], String attributeName) { adapter.registry.register(new JmxAttributeAdapter(flags, adapter, objectName, attributeName)); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/JmxReachableAdapter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/JmxReachableAdapter.java b/core/src/main/java/brooklyn/event/adapter/JmxReachableAdapter.java new file mode 100644 index 0000000..e83b1ab --- /dev/null +++ b/core/src/main/java/brooklyn/event/adapter/JmxReachableAdapter.java @@ -0,0 +1,78 @@ +package brooklyn.event.adapter; + +import groovy.lang.Closure; + +import java.util.Map; + +import javax.management.ObjectName; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.GroovyJavaMethods; +import brooklyn.util.MutableMap; + +import com.google.common.base.Function; + + +/** + * Adapter that polls for a JMX attribute. + * "Attribute" here refers to the JMX concept, rather than the brooklyn concept. + * + * @see {@link JmxSensorAdapter} for recommended way of using this + */ +public class JmxReachableAdapter extends AbstractSensorAdapter { + private static final Logger LOG = LoggerFactory.getLogger(JmxReachableAdapter.class); + + final JmxSensorAdapter adapter; + final ObjectName objectName; + final ReachablePollHelper poller; + + public JmxReachableAdapter(JmxSensorAdapter adapter, ObjectName objectName) { + this(MutableMap.of(), adapter, objectName); + } + public JmxReachableAdapter(Map flags, JmxSensorAdapter adapter, ObjectName objectName) { + super(flags); + this.adapter = adapter; + adapter.addActivationLifecycleListeners( + new Runnable() { public void run() { activateAdapter(); } }, + new Runnable() { public void run() { deactivateAdapter(); } }); + poller = new ReachablePollHelper(adapter, objectName); + this.objectName = objectName; + } + + static class ReachablePollHelper extends AbstractPollHelper { + JmxSensorAdapter adapter; + ObjectName objectName; + ReachablePollHelper(JmxSensorAdapter adapter, ObjectName objectName) { + super(adapter); + this.adapter = adapter; + this.objectName = objectName; + } + @Override + protected AbstractSensorEvaluationContext executePollOnSuccess() { + SingleValueResponseContext result = new SingleValueResponseContext(); + try { + result.setValue(adapter.getHelper().findMBean(objectName) != null); + } catch (Exception e) { + if (LOG.isDebugEnabled()) LOG.debug("Error for "+getEntity()+" "+adapter.getHelper().getUrl()+ + ", finding MBean "+objectName+"; assuming unreachable", e); + result.setValue(false); + } + return result; + } + } + + public void poll(Closure listener) { + poller.addListener(listener); + } + + public void poll(Function<Boolean, Void> listener) { + poller.addListener(GroovyJavaMethods.closureFromFunction(listener)); + } + + @Override + protected void activateAdapter() { + super.activateAdapter(); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/event/adapter/SensorRegistry.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/event/adapter/SensorRegistry.groovy b/core/src/main/java/brooklyn/event/adapter/SensorRegistry.groovy index 5246ef3..c122afd 100644 --- a/core/src/main/java/brooklyn/event/adapter/SensorRegistry.groovy +++ b/core/src/main/java/brooklyn/event/adapter/SensorRegistry.groovy @@ -81,20 +81,20 @@ public class SensorRegistry { boolean activated = false; - private List<Closure> activationListeners = [] - private List<Closure> deactivationListeners = [] - void addActivationLifecycleListeners(Closure onUp, Closure onDown) { + private List<Runnable> activationListeners = [] + private List<Runnable> deactivationListeners = [] + void addActivationLifecycleListeners(Runnable onUp, Runnable onDown) { activationListeners << onUp deactivationListeners << onDown } public void activateAdapters() { if (log.isDebugEnabled()) log.debug "activating adapters at sensor registry for {}", this, entity activated = true; - activationListeners.each { it.call() } + activationListeners.each { it.run() } } public void deactivateAdapters() { if (log.isDebugEnabled()) log.debug "deactivating adapters at sensor registry for {}", this, entity - deactivationListeners.each { it.call() } + deactivationListeners.each { it.run() } } public void close() { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/main/java/brooklyn/util/GroovyJavaMethods.groovy ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/GroovyJavaMethods.groovy b/core/src/main/java/brooklyn/util/GroovyJavaMethods.groovy index 24cdb55..edbbd76 100644 --- a/core/src/main/java/brooklyn/util/GroovyJavaMethods.groovy +++ b/core/src/main/java/brooklyn/util/GroovyJavaMethods.groovy @@ -13,8 +13,8 @@ import com.google.common.base.Predicate public class GroovyJavaMethods { //TODO use named subclasses, would that be more efficient? - - // TODO xFromY nethods not in correct class: they are not "handy method available in groovy"? + + // TODO xFromY methods not in correct class: they are not "handy method available in groovy"? public static Closure closureFromRunnable(final Runnable job) { return { if (job in Callable) { return job.call() } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79ddd7e9/core/src/test/java/brooklyn/event/adapter/JmxSensorAdapterTest.groovy ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/event/adapter/JmxSensorAdapterTest.groovy b/core/src/test/java/brooklyn/event/adapter/JmxSensorAdapterTest.groovy index b154822..99351d0 100644 --- a/core/src/test/java/brooklyn/event/adapter/JmxSensorAdapterTest.groovy +++ b/core/src/test/java/brooklyn/event/adapter/JmxSensorAdapterTest.groovy @@ -2,7 +2,6 @@ package brooklyn.event.adapter import static org.testng.Assert.* -import java.util.Map import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -60,11 +59,14 @@ public class JmxSensorAdapterTest { private JmxHelper jmxHelper private BasicAttributeSensor<Integer> intAttribute = [ Integer, "brooklyn.test.intAttribute", "Brooklyn testing int attribute" ] + private BasicAttributeSensor<Boolean> boolAttribute = [ Boolean, "brooklyn.test.boolAttribute", "Brooklyn testing bool attribute" ] private BasicAttributeSensor<String> stringAttribute = [ String, "brooklyn.test.stringAttribute", "Brooklyn testing string attribute" ] private BasicAttributeSensor<Map> mapAttribute = [ Map, "brooklyn.test.mapAttribute", "Brooklyn testing map attribute" ] private String objectName = 'Brooklyn:type=MyTestMBean,name=myname' + private String wrongObjectName = 'Brooklyn:type=MyTestMBean,name=wrongname' private ObjectName jmxObjectName = new ObjectName('Brooklyn:type=MyTestMBean,name=myname') private String attributeName = 'myattrib' + private String wrongAttributeName = 'wrongattrib' private String opName = 'myop' @BeforeMethod(alwaysRun=true) @@ -117,6 +119,57 @@ public class JmxSensorAdapterTest { } } + @Test + public void jmxPollerWillPollEvenIfOnlyConnectsAfterActivatingAdapters() { + jmxService.shutdown(); + + jmxAdapter.setJmxConnectionTimeout(0); + jmxAdapter.objectName(objectName).with { + attribute(attributeName).subscribe(intAttribute) + } + registry.activateAdapters() + + jmxService = new JmxService(entity); + GeneralisedDynamicMBean mbean = jmxService.registerMBean(objectName, (attributeName): 42) + + // Retrieves value after JMX Service becomes available + TestUtils.executeUntilSucceeds(timeout:TIMEOUT) { + assertEquals entity.getAttribute(intAttribute), 42 + } + } + + @Test(enabled=false) + public void jmxReachablePollerRespondsToConnectException() { + jmxService.shutdown(); + + boolean isup = true; + + jmxAdapter.setJmxConnectionTimeout(10); + jmxAdapter.objectName(wrongObjectName).with { + reachable().poll( { isup = it } ) + } + registry.activateAdapters() + + TestUtils.executeUntilSucceeds(timeout:TIMEOUT) { + assertFalse(isup); + } + } + + @Test + public void jmxReachablePollerRespondsToMBeanNotFound() { + GeneralisedDynamicMBean mbean = jmxService.registerMBean(objectName, (attributeName): 42) + boolean isup = true; + + jmxAdapter.objectName(wrongObjectName).with { + reachable().poll( { isup = it } ) + } + registry.activateAdapters() + + TestUtils.executeUntilSucceeds(timeout:TIMEOUT) { + assertFalse(isup); + } + } + @Test(expectedExceptions=[IllegalStateException.class]) public void jmxCheckInstanceExistsEventuallyThrowsIfNotFound() { jmxHelper.connect(TIMEOUT)
