FELIX-6250 Improved documentation, allow to send events for changes or
for all executions
diff --git a/healthcheck/README.md b/healthcheck/README.md
index 5567d5f..4147de1 100644
--- a/healthcheck/README.md
+++ b/healthcheck/README.md
@@ -247,7 +247,7 @@
### Webconsole plugin
-If the `org.apache.felix.hc.webconsole` bundle is active, a webconsole plugin
+If the `org.apache.felix.hc.webconsole` bundle is installed, a webconsole plugin
at `/system/console/healthcheck` allows for executing health checks, optionally selected
based on their tags (positive and negative selection, see the `HealthCheckFilter` mention above).
@@ -255,7 +255,7 @@
### Gogo Console
-The Gogo command `hc:exec` that can be used as follows:
+The Gogo command `hc:exec` can be used as follows:
hc:exec [-v] [-a] tag1,tag2
-v verbose/debug
@@ -276,7 +276,7 @@
`registerHealthyMarkerService` | boolean | true | For the case a given tag/name is healthy, will register a service `org.apache.felix.hc.api.condition.Healthy` with property tag=<tagname> (or name=<hc.name>) that other services can depend on. For the special case of the tag `systemready`, the marker service `org.apache.felix.hc.api.condition.SystemReady` is registered
`registerUnhealthyMarkerService` | boolean | false | For the case a given tag/name is **un**healthy, will register a service `org.apache.felix.hc.api.condition.Unhealthy` with property tag=<tagname> (or name=<hc.name>) that other services can depend on
`treatWarnAsHealthy` | boolean | true | `WARN` usually means [the system is usable](#semantic-meaning-of-health-check-results), hence WARN is treated as healthy by default. When set to false `WARN` is treated as `Unhealthy`
-`sendEvents` | boolean | false | Whether to send events for health check status changes. See [below](#osgi-events-for-health-check-status-changes) for details.
+`sendEvents` | enum `NONE`, `STATUS_CHANGES` or `ALL` | `STATUS_CHANGES` | Whether to send events for health check status changes. See [below](#osgi-events-for-health-check-status-changes) for details.
### Marker Service to depend on a health status in SCR Components
@@ -305,15 +305,38 @@
NOTE: This does not support the [RFC 242 Condition Service](https://github.com/osgi/design/blob/master/rfcs/rfc0242/rfc-0242-Condition-Service.pdf) yet - however once final the marker services will also be able to implement the `Condition` interface.
-### OSGi events for Health Check status changes
+### OSGi events for Health Check status changes and updates
-For tags/names that a `HealthCheckMonitor` is configured for and `sendEvents` is set to true, events are sent whenever a result status for any of the given tags/names has changed. The events are sent for the topic `org/apache/felix/healthchange/*`. The following events are sent:
+OSGi events with topic `org/apache/felix/health/*` are sent for tags/names that a `HealthCheckMonitor` is configured for and if `sendEvents` is set to `STATUS_CHANGES` or `ALL`:
-Topic Prefix | Example | Description
------- | ------- | -----
-`org/apache/felix/healthchange/tag/` | `org/apache/felix/healthchange/tag/mytag` | Sent whenever the aggregate status for any of the tags as configured in `HealthCheckMonitor` changed
-`org/apache/felix/healthchange/name/` | `org/apache/felix/healthchange/tag/My_Tag_Name ` (spaces are replaced with underscores to ensure valid topic names) | Sent whenever the status for any of the names as configured in `HealthCheckMonitor` changed
-`org/apache/felix/healthchange/component/` | `org/apache/felix/healthchange/component/com/mycomp/myproj/MyHealthCheck` (`.` are replaced with slashes to produce valid topic names) | Sent for health checks that are based on SCR components (in addition to the name event)
+* `STATUS_CHANGES` notifies only of status changes with suffix `/STATUS_CHANGED`
+* `ALL` sends events whenever the monitor runs, depending on status will either send the event with suffix `/UPDATED` or `/STATUS_CHANGED`
+
+All events sent generally carry the properties `executionResult`, `status` and `previousStatus`.
+
+| Example | Description
+------- | -----
+`org/apache/felix/health/tag/mytag/STATUS_CHANGED` | Status for tag `mytag` has changed compared to last execution
+`org/apache/felix/health/tag/My_HC_Name/UPDATED ` (spaces in names are replaced with underscores to ensure valid topic names) | Status for name `My HC Name` has not changed but HC was executed and execution result is available in event property `executionResult`.
+`org/apache/felix/health/component/com/myprj/MyHealthCheck/UPDATED` (`.` are replaced with slashes to produce valid topic names) | HC based on SCR component `com.myprj.MyHealthCheck` was executed without having the status changed. The SCR component event is sent in addition to the name event
+
+Event listener example:
+
+```
+@Component(property = { EventConstants.EVENT_TOPIC + "=org/apache/felix/health/*"})
+public class HealthEventHandler implements EventHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(HealthEventHandler.class);
+
+ public void handleEvent(Event event) {
+ LOG.info("Received event: "+event.getTopic());
+ LOG.info(" previousStatus: "+event.getProperty("previousStatus"));
+ LOG.info(" status: "+event.getProperty("status"));
+ HealthCheckExecutionResult executionResult = (HealthCheckExecutionResult) event.getProperty("executionResult");
+ LOG.info(" executionResult: "+executionResult);
+ LOG.info(" result: "+executionResult.getHealthCheckResult());
+ }
+}
+```
## Servlet Filters
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
index f2f991a..c6bf00f 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
@@ -76,7 +76,10 @@
public static final String TAG_SYSTEMREADY = "systemready";
- public static final String EVENT_TOPIC_PREFIX = "org/apache/felix/healthchange";
+ public static final String EVENT_TOPIC_PREFIX = "org/apache/felix/health";
+ public static final String EVENT_TOPIC_SUFFIX_STATUS_CHANGED = "STATUS_CHANGED";
+ public static final String EVENT_TOPIC_SUFFIX_STATUS_UPDATED = "UPDATED";
+
public static final String EVENT_PROP_EXECUTION_RESULT = "executionResult";
public static final String EVENT_PROP_STATUS = "status";
public static final String EVENT_PROP_PREVIOUS_STATUS = "previousStatus";
@@ -88,6 +91,10 @@
static final SystemReady MARKER_SERVICE_SYSTEMREADY = new SystemReady() {
};
+ public enum SendEventsConfig {
+ NONE, STATUS_CHANGES, ALL
+ }
+
@ObjectClassDefinition(name = "Health Check Monitor", description = "Regularly executes health checks according to given interval/cron expression")
public @interface Config {
@@ -112,8 +119,8 @@
@AttributeDefinition(name = "Treat WARN as Healthy", description = "Whether to treat status WARN as healthy (it normally should because WARN indicates a working system that only possibly might become unavailable if no action is taken")
boolean treatWarnAsHealthy() default true;
- @AttributeDefinition(name = "Send Events", description = "Whether to send OSGi events for the case a status has changed")
- boolean sendEvents() default true;
+ @AttributeDefinition(name = "Send Events", description = "Send OSGi events for the case a status has changed or for all executions or for none.")
+ SendEventsConfig sendEvents() default SendEventsConfig.STATUS_CHANGES;
@AttributeDefinition
String webconsole_configurationFactory_nameHint() default "Health Monitor for '{tags}'/'{names}', {intervalInSec}sec/{cronExpression}, Marker Service Healthy:{registerHealthyMarkerService} Unhealthy:{registerUnhealthyMarkerService}, Send Events {sendEvents}";
@@ -145,7 +152,7 @@
private boolean treatWarnAsHealthy;
- private boolean sendEvents;
+ private SendEventsConfig sendEventsConfig;
private BundleContext bundleContext;
@@ -168,7 +175,7 @@
this.registerUnhealthyMarkerService = config.registerUnhealthyMarkerService();
this.treatWarnAsHealthy = config.treatWarnAsHealthy();
- this.sendEvents = config.sendEvents();
+ this.sendEventsConfig = config.sendEvents();
this.intervalInSec = config.intervalInSec();
this.cronExpression = config.cronExpression();
@@ -272,7 +279,7 @@
LOG.trace(" {}: isHealthy={} statusChanged={}", tagOrName, isHealthy, statusChanged);
registerMarkerServices();
- sendEvent(executionResult, previousStatus);
+ sendEvents(executionResult, previousStatus);
}
private void registerMarkerServices() {
@@ -339,26 +346,28 @@
}
}
- private void sendEvent(HealthCheckExecutionResult executionResult, Result.Status previousStatus) {
- if (sendEvents && statusChanged) {
+ private void sendEvents(HealthCheckExecutionResult executionResult, Result.Status previousStatus) {
+ if ((sendEventsConfig == SendEventsConfig.STATUS_CHANGES && statusChanged) || sendEventsConfig == SendEventsConfig.ALL) {
+
+ String eventSuffix = statusChanged ? EVENT_TOPIC_SUFFIX_STATUS_CHANGED : EVENT_TOPIC_SUFFIX_STATUS_UPDATED;
+ String logMsg = "Posted event for topic '{}': " + (statusChanged ? "Status change from {} to {}" : "Result updated (status {})");
+
Map<String, Object> properties = new HashMap<>();
properties.put(EVENT_PROP_STATUS, status);
if (previousStatus != null) {
properties.put(EVENT_PROP_PREVIOUS_STATUS, previousStatus);
}
properties.put(EVENT_PROP_EXECUTION_RESULT, executionResult);
- String topic = EVENT_TOPIC_PREFIX + "/" + propertyName + "/" + tagOrName.replaceAll("\\s+", "_");
+ String topic = String.join("/", EVENT_TOPIC_PREFIX, propertyName, tagOrName.replaceAll("\\s+", "_"), eventSuffix);
eventAdmin.postEvent(new Event(topic, properties));
- LOG.debug("HealthCheckMonitor: Posted event for topic '{}': Status change from {} to {}", topic,
- previousStatus, status);
+ LOG.debug(logMsg, topic, previousStatus, status);
if (!(executionResult instanceof CombinedExecutionResult)) {
String componentName = (String) executionResult.getHealthCheckMetadata().getServiceReference()
.getProperty(ComponentConstants.COMPONENT_NAME);
if (StringUtils.isNotBlank(componentName)) {
- String topicClass = EVENT_TOPIC_PREFIX + "/component/" + componentName.replace(".", "/");
+ String topicClass = String.join("/", EVENT_TOPIC_PREFIX, "component", componentName.replace(".", "/"), eventSuffix);
eventAdmin.postEvent(new Event(topicClass, properties));
- LOG.debug("HealthCheckMonitor: Posted event for topic '{}': Status change from {} to {}",
- topicClass, previousStatus, status);
+ LOG.debug(logMsg, topicClass, previousStatus, status);
}
}
}
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
index 7610015..3426cf6 100644
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
@@ -96,7 +96,7 @@
@Captor
private ArgumentCaptor<Event> postedEventsCaptor2;
-
+
@Mock
private ServiceRegistration<? extends Healthy> healthyRegistration;
@@ -192,9 +192,9 @@
}
@Test
- public void testRunSendEvents() throws InvalidSyntaxException {
+ public void testRunSendEventsStatusChanges() throws InvalidSyntaxException {
- when(config.sendEvents()).thenReturn(true);
+ when(config.sendEvents()).thenReturn(HealthCheckMonitor.SendEventsConfig.STATUS_CHANGES);
when(healthCheckServiceRef.getProperty(ComponentConstants.COMPONENT_NAME)).thenReturn("org.apache.felix.TestHealthCheck");
healthCheckMonitor.activate(bundleContext, config, componentContext);
@@ -208,9 +208,9 @@
verify(eventAdmin, times(2)).postEvent(postedEventsCaptor1.capture());
List<Event> postedEvents = postedEventsCaptor1.getAllValues();
assertEquals(2, postedEvents.size());
- assertEquals("org/apache/felix/healthchange/tag/test-tag", postedEvents.get(0).getTopic());
+ assertEquals("org/apache/felix/health/tag/test-tag/STATUS_CHANGED", postedEvents.get(0).getTopic());
assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
- assertEquals("org/apache/felix/healthchange/component/org/apache/felix/TestHealthCheck", postedEvents.get(1).getTopic());
+ assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/STATUS_CHANGED", postedEvents.get(1).getTopic());
reset(eventAdmin);
// without status change
@@ -225,18 +225,54 @@
verify(eventAdmin, times(2)).postEvent(postedEventsCaptor2.capture());
postedEvents = postedEventsCaptor2.getAllValues();
assertEquals(2, postedEvents.size());
- assertEquals("org/apache/felix/healthchange/tag/test-tag", postedEvents.get(0).getTopic());
+ assertEquals("org/apache/felix/health/tag/test-tag/STATUS_CHANGED", postedEvents.get(0).getTopic());
assertEquals(Result.Status.CRITICAL, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_PREVIOUS_STATUS));
- assertEquals("org/apache/felix/healthchange/component/org/apache/felix/TestHealthCheck", postedEvents.get(1).getTopic());
+ assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/STATUS_CHANGED", postedEvents.get(1).getTopic());
reset(eventAdmin);
// without status change
healthCheckMonitor.run();
// no event
verifyNoInteractions(eventAdmin);
+
}
+ @Test
+ public void testRunSendEventsAll() throws InvalidSyntaxException {
+
+ when(config.sendEvents()).thenReturn(HealthCheckMonitor.SendEventsConfig.ALL);
+ when(healthCheckServiceRef.getProperty(ComponentConstants.COMPONENT_NAME)).thenReturn("org.apache.felix.TestHealthCheck");
+
+ healthCheckMonitor.activate(bundleContext, config, componentContext);
+
+ setHcResult(Result.Status.OK);
+
+ healthCheckMonitor.run();
+
+ verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
+
+ verify(eventAdmin, times(2)).postEvent(postedEventsCaptor1.capture());
+ List<Event> postedEvents = postedEventsCaptor1.getAllValues();
+ assertEquals(2, postedEvents.size());
+ assertEquals("org/apache/felix/health/tag/test-tag/STATUS_CHANGED", postedEvents.get(0).getTopic());
+ assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
+ assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/STATUS_CHANGED", postedEvents.get(1).getTopic());
+
+ reset(eventAdmin);
+ // without status change
+ healthCheckMonitor.run();
+
+ verify(eventAdmin, times(2)).postEvent(postedEventsCaptor2.capture());
+ postedEvents = postedEventsCaptor2.getAllValues();
+ assertEquals(2, postedEvents.size());
+ assertEquals("org/apache/felix/health/tag/test-tag/UPDATED", postedEvents.get(0).getTopic());
+ assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
+ assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_PREVIOUS_STATUS));
+ assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/UPDATED", postedEvents.get(1).getTopic());
+ }
+
+
private void setHcResult(Result.Status status) {
when(healthCheckExecutor.execute(HealthCheckSelector.tags(TEST_TAG)))
.thenReturn(Arrays.asList(new ExecutionResult(healthCheckMetadata, new Result(status, status.toString()), 1)));