Sensor feeds: avoid repeated log.warn on failure
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/a088f468 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/a088f468 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/a088f468 Branch: refs/heads/master Commit: a088f468d6968de2ab8352019c64e654f42782ba Parents: f6a2ed1 Author: Aled Sage <aled.s...@gmail.com> Authored: Mon Jan 29 16:43:27 2018 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Mon Feb 5 09:43:16 2018 +0000 ---------------------------------------------------------------------- .../camp/brooklyn/FunctionSensorYamlTest.java | 82 ++++++++++++++++++++ .../core/feed/AttributePollHandler.java | 32 +++----- .../apache/brooklyn/core/feed/FeedConfig.java | 42 +++++++++- .../core/sensor/AbstractAddSensorFeed.java | 56 +++++++++++++ .../core/sensor/function/FunctionSensor.java | 17 ++-- .../core/sensor/http/HttpRequestSensor.java | 17 ++-- .../core/sensor/ssh/SshCommandSensor.java | 16 ++-- .../entity/java/JmxAttributeSensor.java | 16 ++-- .../core/sensor/windows/WinRmCommandSensor.java | 16 ++-- .../org/apache/brooklyn/test/LogWatcher.java | 61 ++++++++++++++- 10 files changed, 290 insertions(+), 65 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/FunctionSensorYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/FunctionSensorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/FunctionSensorYamlTest.java index ba6e669..33102f0 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/FunctionSensorYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/FunctionSensorYamlTest.java @@ -18,22 +18,41 @@ */ package org.apache.brooklyn.camp.brooklyn; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.feed.AttributePollHandler; +import org.apache.brooklyn.core.feed.Poller; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.sensor.function.FunctionSensor; import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.LogWatcher; +import org.apache.brooklyn.test.LogWatcher.EventPredicates; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; + public class FunctionSensorYamlTest extends AbstractYamlRebindTest { private static final Logger log = LoggerFactory.getLogger(FunctionSensorYamlTest.class); @@ -42,12 +61,25 @@ public class FunctionSensorYamlTest extends AbstractYamlRebindTest { public static class MyCallable implements Callable<Object> { public static AtomicReference<Object> val = new AtomicReference<>(); + public static AtomicInteger callCounter = new AtomicInteger(); + public static void clear() { + callCounter.set(0); + val.set(null); + } @Override public Object call() throws Exception { + callCounter.incrementAndGet(); return val.get(); } } + @BeforeMethod(alwaysRun = true) + @Override + public void setUp() throws Exception { + super.setUp(); + MyCallable.clear(); + } + @Test public void testFunctionSensor() throws Exception { MyCallable.val.set("first"); @@ -116,6 +148,56 @@ public class FunctionSensorYamlTest extends AbstractYamlRebindTest { EntityAsserts.assertAttributeEqualsEventually(newEntity, SENSOR_INT, 3); } + @Test + public void testWarnOnlyOnceOnRepeatedCoercionException() throws Exception { + MyCallable.val.set("my-not-a-number"); + + List<String> loggerNames = ImmutableList.of( + AttributePollHandler.class.getName(), + Poller.class.getName()); + ch.qos.logback.classic.Level logLevel = ch.qos.logback.classic.Level.TRACE; + Predicate<ILoggingEvent> filter = Predicates.alwaysTrue(); + LogWatcher watcher = new LogWatcher(loggerNames, logLevel, filter); + + watcher.start(); + try { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " brooklyn.config:", + " onbox.base.dir.skipResolution: true", + " brooklyn.initializers:", + " - type: "+FunctionSensor.class.getName(), + " brooklyn.config:", + " "+FunctionSensor.SENSOR_PERIOD.getName()+": 1ms", + " "+FunctionSensor.SENSOR_NAME.getName()+": mysensor", + " "+FunctionSensor.SENSOR_TYPE.getName()+": int", + " "+FunctionSensor.LOG_WARNING_GRACE_TIME_ON_STARTUP.getName()+": 0s", + " "+FunctionSensor.SENSOR_TYPE.getName()+": int", + " "+FunctionSensor.FUNCTION.getName()+":", + " $brooklyn:object:", + " type: "+MyCallable.class.getName()); + waitForApplicationTasks(app); + + // Wait until we've polled (and thus presumably tried to handle the response) 3 times, + // then shutdown the app so we don't risk flooding the log too much if it's going wrong! + Asserts.succeedsEventually(() -> assertTrue(MyCallable.callCounter.get() > 3)); + ((BasicApplication)app).stop(); + + // Ensure we log.warn only once + Iterable<ILoggingEvent> warnEvents = Iterables.filter(watcher.getEvents(), EventPredicates.levelGeaterOrEqual(Level.WARN)); + assertTrue(Iterables.tryFind(warnEvents, EventPredicates.containsMessages("Read of", "gave exception", "Cannot coerce ")).isPresent(), "warnEvents="+warnEvents); + assertEquals(Iterables.size(warnEvents), 1, "warnEvents="+warnEvents); + + // Ensure we log the stacktrace only once + Iterable<ILoggingEvent> exceptionEvents = Iterables.filter(watcher.getEvents(), EventPredicates.containsException()); + assertTrue(Iterables.tryFind(exceptionEvents, EventPredicates.containsExceptionMessage("Cannot coerce ")).isPresent(), "exceptionEvents="+exceptionEvents); + assertEquals(Iterables.size(exceptionEvents), 1, "exceptionEvents="+exceptionEvents); + } finally { + watcher.close(); + } + } + @Override protected Logger getLogger() { return log; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/feed/AttributePollHandler.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/feed/AttributePollHandler.java b/core/src/main/java/org/apache/brooklyn/core/feed/AttributePollHandler.java index 903b6e0..0377eed 100644 --- a/core/src/main/java/org/apache/brooklyn/core/feed/AttributePollHandler.java +++ b/core/src/main/java/org/apache/brooklyn/core/feed/AttributePollHandler.java @@ -55,12 +55,6 @@ public class AttributePollHandler<V> implements PollHandler<V> { private final AbstractFeed feed; private final boolean suppressDuplicates; - // allow 30 seconds before logging at WARN, if there has been no success yet; - // after success WARN immediately - // TODO these should both be configurable - private Duration logWarningGraceTimeOnStartup = Duration.THIRTY_SECONDS; - private Duration logWarningGraceTime = Duration.millis(0); - // internal state to look after whether to log warnings private volatile Long lastSuccessTime = null; private volatile Long currentProblemStartTime = null; @@ -84,6 +78,9 @@ public class AttributePollHandler<V> implements PollHandler<V> { @Override public void onSuccess(V val) { + if (log.isTraceEnabled()) log.trace("poll for "+getBriefDescription()+" got: "+val); + setSensor(transformValueOnSuccess(val)); + if (lastWasProblem) { if (currentProblemLoggedAsWarning) { log.info("Success (following previous problem) reading "+getBriefDescription()); @@ -95,17 +92,6 @@ public class AttributePollHandler<V> implements PollHandler<V> { currentProblemLoggedAsWarning = false; } lastSuccessTime = System.currentTimeMillis(); - if (log.isTraceEnabled()) log.trace("poll for {} got: {}", new Object[] {getBriefDescription(), val}); - - try { - setSensor(transformValueOnSuccess(val)); - } catch (Exception e) { - if (feed.isConnected()) { - log.warn("unable to compute "+getBriefDescription()+"; on val="+val, e); - } else { - if (log.isDebugEnabled()) log.debug("unable to compute "+getBriefDescription()+"; val="+val+" (when inactive)", e); - } - } } /** allows post-processing, such as applying a success handler; @@ -137,7 +123,7 @@ public class AttributePollHandler<V> implements PollHandler<V> { @Override public void onException(Exception exception) { if (!feed.isConnected()) { - if (log.isTraceEnabled()) log.trace("Read of {} in {} gave exception (while not connected or not yet connected): {}", new Object[] {this, getBriefDescription(), exception}); + if (log.isTraceEnabled()) log.trace("Read of "+this+" in "+getBriefDescription()+" gave exception (while not connected or not yet connected): "+ exception); } else { logProblem("exception", exception); } @@ -158,15 +144,17 @@ public class AttributePollHandler<V> implements PollHandler<V> { protected void logProblem(String type, Object val) { if (lastWasProblem && currentProblemLoggedAsWarning) { if (log.isTraceEnabled()) - log.trace("Recurring {} reading {} in {}: {}", new Object[] {type, this, getBriefDescription(), val}); + log.trace("Recurring "+type+" reading "+this+" in "+getBriefDescription()+": "+val); } else { long nowTime = System.currentTimeMillis(); // get a non-volatile value Long currentProblemStartTimeCache = currentProblemStartTime; long expiryTime = - (lastSuccessTime!=null && !isTransitioningOrStopped()) ? lastSuccessTime+logWarningGraceTime.toMilliseconds() : - currentProblemStartTimeCache!=null ? currentProblemStartTimeCache+logWarningGraceTimeOnStartup.toMilliseconds() : - nowTime+logWarningGraceTimeOnStartup.toMilliseconds(); + (lastSuccessTime!=null && !isTransitioningOrStopped()) + ? lastSuccessTime+config.getLogWarningGraceTime().toMilliseconds() + : (currentProblemStartTimeCache != null) + ? currentProblemStartTimeCache+config.getLogWarningGraceTimeOnStartup().toMilliseconds() + : nowTime+config.getLogWarningGraceTimeOnStartup().toMilliseconds(); if (!lastWasProblem) { if (expiryTime <= nowTime) { currentProblemLoggedAsWarning = true; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java b/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java index 91f6f7c..7e7a5b0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java +++ b/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.guava.Functionals; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; import com.google.common.base.Function; import com.google.common.base.Functions; @@ -63,6 +64,26 @@ public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> { private boolean suppressDuplicates; private boolean enabled = true; + // allow 30 seconds before logging at WARN, if there has been no success yet; + // after success WARN immediately + // TODO these should both be configurable + /** + * On startup, the length of time before which a failure can be logged at WARN. + * This grace period is useful to avoid flooding the logs if the feed is expected + * to sometimes be unavailable for a few seconds while the process-under-management + * initialises. + * + * Defaults to 30 seconds. + */ + private Duration logWarningGraceTimeOnStartup = Duration.THIRTY_SECONDS; + + /** + * Length of time, after a successful poll, before a subsequent failure can be logged at WARN. + * + * Defaults to 0. + */ + private Duration logWarningGraceTime = Duration.millis(0); + public FeedConfig(AttributeSensor<T> sensor) { this.sensor = checkNotNull(sensor, "sensor"); } @@ -74,6 +95,8 @@ public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> { this.onexception = other.onexception; this.checkSuccess = other.checkSuccess; this.suppressDuplicates = other.suppressDuplicates; + this.logWarningGraceTimeOnStartup = other.logWarningGraceTimeOnStartup; + this.logWarningGraceTime = other.logWarningGraceTime; this.enabled = other.enabled; } @@ -201,6 +224,24 @@ public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> { return self(); } + public F logWarningGraceTimeOnStartup(Duration val) { + this.logWarningGraceTimeOnStartup = checkNotNull(val); + return self(); + } + + public F logWarningGraceTime(Duration val) { + this.logWarningGraceTime = checkNotNull(val); + return self(); + } + + public Duration getLogWarningGraceTimeOnStartup() { + return logWarningGraceTimeOnStartup; + } + + public Duration getLogWarningGraceTime() { + return logWarningGraceTime; + } + /** * Whether this feed is enabled (defaulting to true). */ @@ -300,5 +341,4 @@ public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> { if (!Objects.equal(equalsFields(), other.equalsFields())) return false; return true; } - } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/sensor/AbstractAddSensorFeed.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/AbstractAddSensorFeed.java b/core/src/main/java/org/apache/brooklyn/core/sensor/AbstractAddSensorFeed.java new file mode 100644 index 0000000..5ff73ce --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/AbstractAddSensorFeed.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.sensor; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddSensor; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.annotations.Beta; + +/** + * Super-class for entity initializers that add feeds. + */ +@Beta +public abstract class AbstractAddSensorFeed<T> extends AddSensor<T> { + + public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( + "suppressDuplicates", + "Whether to publish the sensor value again, if it is the same as the previous value", + Boolean.FALSE); + + public static final ConfigKey<Duration> LOG_WARNING_GRACE_TIME_ON_STARTUP = ConfigKeys.newDurationConfigKey( + "logWarningGraceTimeOnStartup", + "On startup, the length of time before which a failure can be logged at WARN. " + + "This grace period is useful to avoid flooding the logs if the feed is expected " + + "to sometimes be unavailable for a few seconds while the process-under-management" + + "initialises.", + Duration.millis(0)); + + public static final ConfigKey<Duration> LOG_WARNING_GRACE_TIME = ConfigKeys.newDurationConfigKey( + "logWarningGraceTime", + "Length of time, after a successful poll, before a subsequent failure can be logged at WARN.", + Duration.millis(0)); + + public AbstractAddSensorFeed(final ConfigBag params) { + super(params); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java index 4b32451..62e785c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java @@ -23,11 +23,12 @@ import java.util.concurrent.Callable; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.sensor.AbstractAddSensorFeed; import org.apache.brooklyn.feed.function.FunctionFeed; import org.apache.brooklyn.feed.function.FunctionPollConfig; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,21 +43,17 @@ import com.google.common.reflect.TypeToken; * @see FunctionFeed */ @Beta -public final class FunctionSensor<T> extends AddSensor<T> { +public final class FunctionSensor<T> extends AbstractAddSensorFeed<T> { private static final Logger LOG = LoggerFactory.getLogger(FunctionSensor.class); - public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( - "suppressDuplicates", - "Whether to publish the sensor value again, if it is the same as the previous value", - Boolean.FALSE); - + @SuppressWarnings("serial") public static final ConfigKey<Callable<?>> FUNCTION = ConfigKeys.newConfigKey( new TypeToken<Callable<?>>() {}, "function", "The callable to be executed periodically", null); - + public FunctionSensor(final ConfigBag params) { super(params); } @@ -73,11 +70,15 @@ public final class FunctionSensor<T> extends AddSensor<T> { final Callable<?> function = EntityInitializers.resolve(allConfig, FUNCTION); final Boolean suppressDuplicates = EntityInitializers.resolve(allConfig, SUPPRESS_DUPLICATES); + final Duration logWarningGraceTimeOnStartup = EntityInitializers.resolve(allConfig, LOG_WARNING_GRACE_TIME_ON_STARTUP); + final Duration logWarningGraceTime = EntityInitializers.resolve(allConfig, LOG_WARNING_GRACE_TIME); FunctionPollConfig<?, T> pollConfig = new FunctionPollConfig<Object, T>(sensor) .callable(function) .onFailureOrException(Functions.constant((T) null)) .suppressDuplicates(Boolean.TRUE.equals(suppressDuplicates)) + .logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup) + .logWarningGraceTime(logWarningGraceTime) .period(period); FunctionFeed feed = FunctionFeed.builder().entity(entity) http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java index 842bcb4..a6c73e4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java @@ -25,17 +25,16 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.MapConfigKey; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.EntityInitializers; -import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.sensor.AbstractAddSensorFeed; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; import org.apache.brooklyn.feed.http.HttpFeed; import org.apache.brooklyn.feed.http.HttpPollConfig; import org.apache.brooklyn.feed.http.HttpValueFunctions; import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.core.config.ResolvingConfigBag; import org.apache.brooklyn.util.http.HttpToolResponse; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +52,7 @@ import net.minidev.json.JSONObject; * @see SshCommandSensor */ @Beta -public final class HttpRequestSensor<T> extends AddSensor<T> { +public final class HttpRequestSensor<T> extends AbstractAddSensorFeed<T> { private static final Logger LOG = LoggerFactory.getLogger(HttpRequestSensor.class); @@ -62,16 +61,12 @@ public final class HttpRequestSensor<T> extends AddSensor<T> { public static final ConfigKey<String> USERNAME = ConfigKeys.newStringConfigKey("username", "Username for HTTP request, if required"); public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password", "Password for HTTP request, if required"); public static final ConfigKey<Map<String, String>> HEADERS = new MapConfigKey<>(String.class, "headers"); - public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( - "suppressDuplicates", - "Whether to publish the sensor value again, if it is the same as the previous value", - Boolean.FALSE); public static final ConfigKey<Boolean> PREEMPTIVE_BASIC_AUTH = ConfigKeys.newBooleanConfigKey( "preemptiveBasicAuth", "Whether to pre-emptively including a basic-auth header of the username:password (rather than waiting for a challenge)", Boolean.FALSE); - + public HttpRequestSensor(final ConfigBag params) { super(params); } @@ -101,6 +96,8 @@ public final class HttpRequestSensor<T> extends AddSensor<T> { final Map<String, String> headers = EntityInitializers.resolve(allConfig, HEADERS); final Boolean preemptiveBasicAuth = EntityInitializers.resolve(allConfig, PREEMPTIVE_BASIC_AUTH); final Boolean suppressDuplicates = EntityInitializers.resolve(allConfig, SUPPRESS_DUPLICATES); + final Duration logWarningGraceTimeOnStartup = EntityInitializers.resolve(allConfig, LOG_WARNING_GRACE_TIME_ON_STARTUP); + final Duration logWarningGraceTime = EntityInitializers.resolve(allConfig, LOG_WARNING_GRACE_TIME); Function<? super HttpToolResponse, T> successFunction; if (Strings.isBlank(jsonPath)) { @@ -115,6 +112,8 @@ public final class HttpRequestSensor<T> extends AddSensor<T> { .onFailureOrException(Functions.constant((T) null)) .onSuccess(successFunction) .suppressDuplicates(Boolean.TRUE.equals(suppressDuplicates)) + .logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup) + .logWarningGraceTime(logWarningGraceTime) .period(period); HttpFeed.Builder httpRequestBuilder = HttpFeed.builder().entity(entity) http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java index 2fd3f3f..84b7322 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java @@ -27,10 +27,10 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.MapConfigKey; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.entity.EntityInitializers; import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.sensor.AbstractAddSensorFeed; import org.apache.brooklyn.core.sensor.http.HttpRequestSensor; import org.apache.brooklyn.feed.CommandPollConfig; import org.apache.brooklyn.feed.ssh.SshFeed; @@ -43,6 +43,7 @@ import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +62,7 @@ import com.google.common.collect.ImmutableMap; * @see HttpRequestSensor */ @Beta -public final class SshCommandSensor<T> extends AddSensor<T> { +public final class SshCommandSensor<T> extends AbstractAddSensorFeed<T> { private static final Logger LOG = LoggerFactory.getLogger(SshCommandSensor.class); @@ -71,11 +72,6 @@ public final class SshCommandSensor<T> extends AddSensor<T> { + "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir"); public static final MapConfigKey<Object> SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT; - public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( - "suppressDuplicates", - "Whether to publish the sensor value again, if it is the same as the previous value", - Boolean.FALSE); - protected final String command; protected final String executionDir; protected final Map<String,Object> sensorEnv; @@ -98,6 +94,8 @@ public final class SshCommandSensor<T> extends AddSensor<T> { } final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Duration logWarningGraceTimeOnStartup = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME_ON_STARTUP); + final Duration logWarningGraceTime = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME); Supplier<Map<String,String>> envSupplier = new Supplier<Map<String,String>>() { @Override @@ -143,7 +141,9 @@ public final class SshCommandSensor<T> extends AddSensor<T> { @Override public T apply(String input) { return TypeCoercions.coerce(Strings.trimEnd(input), (Class<T>) sensor.getType()); - }}, SshValueFunctions.stdout())); + }}, SshValueFunctions.stdout())) + .logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup) + .logWarningGraceTime(logWarningGraceTime); SshFeed feed = SshFeed.builder() .entity(entity) http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java index 21046ad..61f753e 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java @@ -27,8 +27,8 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.sensor.AbstractAddSensorFeed; import org.apache.brooklyn.core.sensor.DependentConfiguration; import org.apache.brooklyn.core.sensor.http.HttpRequestSensor; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; @@ -38,6 +38,7 @@ import org.apache.brooklyn.feed.jmx.JmxHelper; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +54,7 @@ import com.google.common.base.Preconditions; * @see HttpRequestSensor */ @Beta -public final class JmxAttributeSensor<T> extends AddSensor<T> { +public final class JmxAttributeSensor<T> extends AbstractAddSensorFeed<T> { private static final Logger LOG = LoggerFactory.getLogger(JmxAttributeSensor.class); @@ -61,11 +62,6 @@ public final class JmxAttributeSensor<T> extends AddSensor<T> { public static final ConfigKey<String> ATTRIBUTE = ConfigKeys.newStringConfigKey("attribute", "JMX attribute to poll in object"); public static final ConfigKey<Object> DEFAULT_VALUE = ConfigKeys.newConfigKey(Object.class, "defaultValue", "Default value for sensor; normally null"); - public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( - "suppressDuplicates", - "Whether to publish the sensor value again, if it is the same as the previous value", - Boolean.FALSE); - protected final String objectName; protected final String attribute; protected final Object defaultValue; @@ -89,6 +85,8 @@ public final class JmxAttributeSensor<T> extends AddSensor<T> { super.apply(entity); final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Duration logWarningGraceTimeOnStartup = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME_ON_STARTUP); + final Duration logWarningGraceTime = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME); if (entity instanceof UsesJmx) { if (LOG.isDebugEnabled()) { @@ -111,7 +109,9 @@ public final class JmxAttributeSensor<T> extends AddSensor<T> { .objectName(objectName) .attributeName(attribute) .suppressDuplicates(Boolean.TRUE.equals(suppressDuplicates)) - .onFailureOrException(Functions.<T>constant((T) defaultValue))) + .onFailureOrException(Functions.<T>constant((T) defaultValue)) + .logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup) + .logWarningGraceTime(logWarningGraceTime)) .build(); entity.addFeed(feed); return feed; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java index 2fcc315..3ee5e24 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java @@ -26,9 +26,9 @@ import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.EntityInitializers; import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.sensor.AbstractAddSensorFeed; import org.apache.brooklyn.core.sensor.http.HttpRequestSensor; import org.apache.brooklyn.feed.CommandPollConfig; import org.apache.brooklyn.feed.ssh.SshValueFunctions; @@ -41,6 +41,7 @@ import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +61,7 @@ import com.google.common.base.Supplier; * @see HttpRequestSensor */ @Beta -public final class WinRmCommandSensor<T> extends AddSensor<T> { +public final class WinRmCommandSensor<T> extends AbstractAddSensorFeed<T> { private static final Logger LOG = LoggerFactory.getLogger(WinRmCommandSensor.class); @@ -70,11 +71,6 @@ public final class WinRmCommandSensor<T> extends AddSensor<T> { + "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir"); public static final ConfigKey<Map<String, String>> SENSOR_ENVIRONMENT = WinRmTool.ENVIRONMENT; - public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey( - "suppressDuplicates", - "Whether to publish the sensor value again, if it is the same as the previous value", - Boolean.FALSE); - protected final String command; protected final String executionDir; protected final Map<String,String> sensorEnv; @@ -97,6 +93,8 @@ public final class WinRmCommandSensor<T> extends AddSensor<T> { } final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Duration logWarningGraceTimeOnStartup = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME_ON_STARTUP); + final Duration logWarningGraceTime = EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME); Supplier<Map<String,String>> envSupplier = new Supplier<Map<String,String>>() { @Override @@ -137,7 +135,9 @@ public final class WinRmCommandSensor<T> extends AddSensor<T> { @Override public T apply(String input) { return TypeCoercions.coerce(Strings.trimEnd(input), (Class<T>) sensor.getType()); - }}, SshValueFunctions.stdout())); + }}, SshValueFunctions.stdout())) + .logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup) + .logWarningGraceTime(logWarningGraceTime); CmdFeed feed = CmdFeed.builder() .entity(entity) http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a088f468/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java ---------------------------------------------------------------------- diff --git a/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java b/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java index 0f630bb..15fd245 100644 --- a/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java +++ b/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java @@ -23,12 +23,14 @@ import static com.google.common.base.Preconditions.checkState; import static org.testng.Assert.assertFalse; import java.io.Closeable; +import java.io.PrintStream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.apache.brooklyn.util.time.Time; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -78,6 +80,7 @@ public class LogWatcher implements Closeable { } }; } + public static Predicate<ILoggingEvent> containsExceptionStackLine(final Class<?> clazz, final String methodName) { return new Predicate<ILoggingEvent>() { @Override public boolean apply(ILoggingEvent input) { @@ -94,6 +97,42 @@ public class LogWatcher implements Closeable { } }; } + + public static Predicate<ILoggingEvent> containsException() { + return new Predicate<ILoggingEvent>() { + @Override public boolean apply(ILoggingEvent input) { + return (input != null) && (input.getThrowableProxy() != null); + } + }; + } + + public static Predicate<ILoggingEvent> containsExceptionMessage(final String expected) { + return containsExceptionMessages(expected); + } + + public static Predicate<ILoggingEvent> containsExceptionMessages(final String... expecteds) { + return new Predicate<ILoggingEvent>() { + @Override public boolean apply(ILoggingEvent input) { + IThrowableProxy throwable = (input != null) ? input.getThrowableProxy() : null; + String msg = (throwable != null) ? throwable.getMessage() : null; + if (msg == null) return false; + for (String expected : expecteds) { + if (!msg.contains(expected)) return false; + } + return true; + } + }; + } + + public static Predicate<ILoggingEvent> levelGeaterOrEqual(final Level expectedLevel) { + return new Predicate<ILoggingEvent>() { + @Override public boolean apply(ILoggingEvent input) { + if (input == null) return false; + Level level = input.getLevel(); + return level.isGreaterOrEqual(expectedLevel); + } + }; + } } private final List<ILoggingEvent> events = Collections.synchronizedList(Lists.<ILoggingEvent>newLinkedList()); @@ -191,13 +230,33 @@ public class LogWatcher implements Closeable { return ImmutableList.copyOf(events); } } - + public List<ILoggingEvent> getEvents(Predicate<? super ILoggingEvent> filter) { synchronized (events) { return ImmutableList.copyOf(Iterables.filter(events, filter)); } } + + public void printEvents() { + printEvents(System.out, getEvents()); + } + public void printEvents(PrintStream stream, Iterable<? extends ILoggingEvent> events) { + for (ILoggingEvent event : events) { + stream.println(Time.makeDateString(event.getTimeStamp()) + ": " + event.getThreadName() + + ": " + event.getLevel() + ": " + event.getMessage()); + IThrowableProxy throwable = event.getThrowableProxy(); + if (throwable != null) { + stream.println("\t" + throwable.getMessage()); + if (throwable.getStackTraceElementProxyArray() != null) { + for (StackTraceElementProxy element : throwable.getStackTraceElementProxyArray()) { + stream.println("\t\t" + "at " + element); + } + } + } + } + } + public void clearEvents() { synchronized (events) { events.clear();