This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch issue-6768
in repository https://gitbox.apache.org/repos/asf/felix-dev.git
The following commit(s) were added to refs/heads/issue-6768 by this push:
new deea465d54 FELIX-6768 : Support OSGi Conditions
deea465d54 is described below
commit deea465d541f66e589780d214281bf23f9398749
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Sat Apr 12 16:39:03 2025 +0200
FELIX-6768 : Support OSGi Conditions
---
healthcheck/README.md | 116 +++++++++++++--------
healthcheck/core/pom.xml | 19 ++--
.../felix/hc/core/impl/monitor/HealthState.java | 39 ++++---
.../hc/core/impl/CompositeHealthCheckTest.java | 19 +++-
.../core/impl/monitor/HealthCheckMonitorTest.java | 88 ++++++++++------
5 files changed, 174 insertions(+), 107 deletions(-)
diff --git a/healthcheck/README.md b/healthcheck/README.md
index af8418e2c1..02fbaff9e3 100644
--- a/healthcheck/README.md
+++ b/healthcheck/README.md
@@ -34,11 +34,11 @@ The strength of Health Checks are to surface internal state
for external use:
* Check that all OSGi bundles are up and running
* Verify that performance counters are in range
* Ping external systems and raise alarms if they are down
-* Run smoke tests at system startup
+* Run smoke tests at system startup
* Check that demo content has been removed from a production system
* Check that demo accounts are disabled
-The health check subsystem uses tags to select which health checks to execute
so you can for example execute just the _performance_ or _security_ health
+The health check subsystem uses tags to select which health checks to execute
so you can for example execute just the _performance_ or _security_ health
checks once they are configured with the corresponding tags.
The out of the box health check services also allow for using them as JMX
aggregators and processors, which take JMX
@@ -54,15 +54,15 @@ A `HealthCheck` is just an OSGi service that returns a
`Result`.
```
public interface HealthCheck {
-
- /** Execute this health check and return a {@link Result}
+
+ /** Execute this health check and return a {@link Result}
* This is meant to execute quickly, access to external
* systems, for example, should be managed asynchronously.
*/
public Result execute();
}
```
-
+
A simple health check implementation might look like follows:
```
@@ -93,9 +93,9 @@ Instead of using Log4j side by side with
ResultLog/FormattingResultLog it is rec
### Semantic meaning of health check results
In order to make health check results aggregatable in a reasonable way, it is
important that result status values are used in a consistent way across
different checks. When implementing custom health checks, comply to the
following table:
-Status | System is functional | Meaning | Actions possible for machine clients
| Actions possible for human clients
---- | --- | --- | --- | ---
-OK | yes | Everything is ok. | <ul><li>If system is not actively used yet, a
load balancer might decide to take the system to production after receiving
this status for the first time.</li><li>Otherwise no action needed</li></ul> |
Response logs might still provide information to a human on why the system
currently is healthy. E.g. it might show 30% disk used which indicates that no
action will be required for a long time
+Status | System is functional | Meaning | Actions possible for machine clients
| Actions possible for human clients
+--- | --- | --- | --- | ---
+OK | yes | Everything is ok. | <ul><li>If system is not actively used yet, a
load balancer might decide to take the system to production after receiving
this status for the first time.</li><li>Otherwise no action needed</li></ul> |
Response logs might still provide information to a human on why the system
currently is healthy. E.g. it might show 30% disk used which indicates that no
action will be required for a long time
WARN | yes | **Tendency to CRITICAL** <br>System is fully functional but
actions are needed to avoid a CRITICAL status in the future | <ul><li>Certain
actions can be configured for known, actionable warnings, e.g. if disk space is
low, it could be dynamically extended using infrastructure APIs if on virtual
infrastructure)</li><li>Pass on information to monitoring system to be
available to humans (in other aggregator UIs)</li></ul> | Any manual steps that
a human can perform based on the [...]
TEMPORARILY_UNAVAILABLE *) | no | **Tendency to OK** <br>System is not
functional at the moment but is expected to become OK (or at least WARN)
without action. An health check using this status is expected to turn CRITICAL
after a certain period returning TEMPORARILY_UNAVAILABLE | <ul><li>Take out
system from load balancing</li><li>Wait until TEMPORARILY_UNAVAILABLE status
turns into either OK or CRITICAL</li></ul> | Wait and monitor result logs of
health check returning TEMPORARILY_UNAVAILABLE
CRITICAL | no | System is not functional and must not be used | <ul><li>Take
out system from load balancing</li><li>Decommission system entirely and
re-provision from scratch</li></ul> | Any manual steps that a human can
perform based on their knowledge to bring the system back to state OK
@@ -109,12 +109,12 @@ HEALTH\_CHECK\_ERROR | no | **Actual status unknown**
<br>There was an error in
The following generic Health Check properties may be used for all checks
(**all service properties are optional**):
-Property | Type | Description
+Property | Type | Description
----------- | -------- | ------------
hc.name | String | The name of the health check as shown in UI
hc.tags | String[] | List of tags: Both Felix Console Plugin and Health
Check servlet support selecting relevant checks by providing a list of tags
hc.mbean.name | String | Makes the HC result available via given MBean name.
If not provided no MBean is created for that `HealthCheck`
-hc.async.cronExpression | String | Executes the health check asynchronously
using the cron expression provided. Use this for **long running health checks**
to avoid execution every time the tag/name is queried. Prefer configuring a
HealthCheckMonitor if you only want to regularly execute a HC.
+hc.async.cronExpression | String | Executes the health check asynchronously
using the cron expression provided. Use this for **long running health checks**
to avoid execution every time the tag/name is queried. Prefer configuring a
HealthCheckMonitor if you only want to regularly execute a HC.
hc.async.intervalInSec | Long | Async execution like `hc.async.cronExpression`
but using an interval
hc.resultCacheTtlInMs | Long | Overrides the global default TTL as configured
in health check executor for health check responses
hc.keepNonOkResultsStickyForSec | Long | If given, non-ok results from past
executions will be taken into account as well for the given seconds (use
Long.MAX_VALUE for indefinitely). Useful for unhealthy system states that
disappear but might leave the system at an inconsistent state (e.g. an event
queue overflow where somebody needs to intervene manually) or for checks that
should only go back to OK with a delay (can be useful for load balancers).
@@ -124,18 +124,18 @@ hc.keepNonOkResultsStickyForSec | Long | If given, non-ok
results from past exec
To configure the defaults for the service properties
[above](#configuring-health-checks), the following annotations can be used:
// standard OSGi
- @Component
- @Designate(ocd = MyCustomCheckConfig.class, factory = true)
-
+ @Component
+ @Designate(ocd = MyCustomCheckConfig.class, factory = true)
+
// to set `hc.name` and `hc.tags`
@HealthCheckService(name = "Custom Check Name", tags= {"tag1", "tag2"})
-
+
// to set `hc.async.cronExpression` or `hc.async.intervalInSec`
@Async(cronExpression="0 0 12 1 * ?" /*, intervalInSec = 60 */)
-
+
// to set `hc.resultCacheTtlInMs`:
@ResultTTL(resultCacheTtlInMs = 10000)
-
+
// to set `hc.mbean.name`:
@HealthCheckMBean(name = "MyCustomCheck")
@@ -145,20 +145,20 @@ To configure the defaults for the service properties
[above](#configuring-health
...
-## General purpose health checks available out-of-the-box
+## General purpose health checks available out-of-the-box
The following checks are contained in bundle
`org.apache.felix.healthcheck.generalchecks` and can be activated by simple
configuration:
-Check | PID | Factory | Description
+Check | PID | Factory | Description
--- | --- | --- | ---
-Framework Startlevel | org.apache.felix.hc.generalchecks.FrameworkStartCheck |
no | Checks the OSGi framework startlevel - `targetStartLevel` allows to
configure a target start level, `targetStartLevel.propName` can be used to read
it from the framework/system properties.
-Services Ready | org.apache.felix.hc.generalchecks.ServicesCheck | yes |
Checks for the existance of the given services. `services.list` can contain
simple service names or filter expressions
-Components Ready | org.apache.felix.hc.generalchecks.DsComponentsCheck | yes |
Checks for the existance of the given components. Use `components.list` to list
required active components (use component names)
-Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes
| Checks for started bundles - `includesRegex` and `excludesRegex` control what
bundles are checked.
+Framework Startlevel | org.apache.felix.hc.generalchecks.FrameworkStartCheck |
no | Checks the OSGi framework startlevel - `targetStartLevel` allows to
configure a target start level, `targetStartLevel.propName` can be used to read
it from the framework/system properties.
+Services Ready | org.apache.felix.hc.generalchecks.ServicesCheck | yes |
Checks for the existance of the given services. `services.list` can contain
simple service names or filter expressions
+Components Ready | org.apache.felix.hc.generalchecks.DsComponentsCheck | yes |
Checks for the existance of the given components. Use `components.list` to list
required active components (use component names)
+Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes
| Checks for started bundles - `includesRegex` and `excludesRegex` control what
bundles are checked.
Disk Space | org.apache.felix.hc.generalchecks.DiskSpaceCheck | yes | Checks
for disk space usage at the given paths `diskPaths` and checks them against
thresholds `diskUsedThresholdWarn` (default 90%) and diskUsedThresholdCritical
(default 97%)
Memory | org.apache.felix.hc.generalchecks.MemoryCheck | no | Checks for
Memory usage - `heapUsedPercentageThresholdWarn` (default 90%) and
`heapUsedPercentageThresholdCritical` (default 99%) can be set to control what
memory usage produces status `WARN` and `CRITICAL`
CPU | org.apache.felix.hc.generalchecks.CpuCheck | no | Checks for CPU usage -
`cpuPercentageThresholdWarn` (default 95%) can be set to control what CPU usage
produces status `WARN` (check never results in `CRITICAL`)
-Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no |
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses
the CPU usage of each thread via a configurable time period (`samplePeriodInMs`
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN`
about high thread utilisation.
+Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no |
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses
the CPU usage of each thread via a configurable time period (`samplePeriodInMs`
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN`
about high thread utilisation.
JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheck |
yes | Allows to check an arbitrary JMX attribute (using the configured mbean
`mbean.name`'s attribute `attribute.name`) against a given constraint
`attribute.value.constraint` (see [Constraints](#constraints)). Can check
multiple attributes by providing additional config properties with numbers:
`mbean2.name` (defaults to `mbean.name` if ommitted), `attribute2.name` and
`attribute2.value.constraint` and `mbean3.n [...]
Http Requests Check | org.apache.felix.hc.generalchecks.HttpRequestsCheck |
yes | Allows to check a list of URLs against response code, response headers,
timing, response content (plain content via RegEx or JSON via path expression).
See [Request Spec Syntax](#request-spec-syntax)
Scripted Check | org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes |
Allows to run an arbitrary script. To configure use either `script` to provide
a script directly or `scriptUrl` to link to an external script (may be a file
URL or a link to a JCR file if a Sling Repository exists, e.g.
`jcr:/etc/hc/check1.groovy`). Use the `language` property to refer to a
registered script engine (e.g. install bundle `groovy-all` to be able to use
language `groovy`). The script has the bindi [...]
@@ -168,7 +168,7 @@ Scripted Check |
org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes | A
The `JMX Attribute Check` and `Http Requests Check` allow to check values
against contraints. See the following examples:
* value `string value` (checks for equality)
-* value ` = 0`
+* value ` = 0`
* value ` > 0`
* value ` < 100`
* value ` BETWEEN 3 AND 7`
@@ -183,7 +183,7 @@ Also see class
`org.apache.felix.hc.generalchecks.util.SimpleConstraintsChecker`
### Request Spec Syntax
-The `Http Requests Check` allows to configure a list of request specs.
Requests specs have two parts: Before `=>` can be a simple URL/path with
curl-syntax advanced options (e.g. setting a header with `-H "Test: Test
val"`), after the `=>` it is a simple response code that can be followed ` &&
MATCHES <RegEx>` to match the response entity against or other matchers like
HEADER, TIME or JSON.
+The `Http Requests Check` allows to configure a list of request specs.
Requests specs have two parts: Before `=>` can be a simple URL/path with
curl-syntax advanced options (e.g. setting a header with `-H "Test: Test
val"`), after the `=>` it is a simple response code that can be followed ` &&
MATCHES <RegEx>` to match the response entity against or other matchers like
HEADER, TIME or JSON.
Examples:
@@ -205,9 +205,9 @@ This is a health check that can be dynamically controlled
via JMX bean `org.apac
## Executing Health Checks
-Health Checks can be executed via a [webconsole plugin](#webconsole-plugin),
the [health check servlet](#health-check-servlet) or via
[JMX](#jmx-access-to-health-checks). `HealthCheck` services can be selected for
execution based on their `hc.tags` multi-value service property.
+Health Checks can be executed via a [webconsole plugin](#webconsole-plugin),
the [health check servlet](#health-check-servlet) or via
[JMX](#jmx-access-to-health-checks). `HealthCheck` services can be selected for
execution based on their `hc.tags` multi-value service property.
-The `HealthCheckFilter` utility accepts positive and negative tag parameters,
so that `osgi,-security`
+The `HealthCheckFilter` utility accepts positive and negative tag parameters,
so that `osgi,-security`
selects all `HealthCheck` having the `osgi` tag but not the `security` tag,
for example.
For advanced use cases it is also possible to use the API directly by using
the interface `org.apache.felix.hc.api.execution.HealthCheckExecutor`.
@@ -216,7 +216,7 @@ For advanced use cases it is also possible to use the API
directly by using the
The health check executor can **optionally** be configured via service PID
`org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImpl`:
-Property | Type | Default | Description
+Property | Type | Default | Description
----------- | -------- | ------ | ------------
`timeoutInMs` | Long | 2000ms | Timeout in ms until a check is marked as
timed out
`longRunningFutureThresholdForCriticalMs` | Long | 300000ms (5min) | Threshold
in ms until a check is marked as 'exceedingly' timed out and will marked
CRITICAL instead of WARN only
@@ -248,7 +248,7 @@ By default the HC servlet sends the CORS header
`Access-Control-Allow-Origin: *`
### Webconsole plugin
-If the `org.apache.felix.hc.webconsole` bundle is installed, 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).
@@ -261,7 +261,7 @@ The Gogo command `hc:exec` can be used as follows:
hc:exec [-v] [-a] tag1,tag2
-v verbose/debug
-a combine tags with and logic (instead of or logic)
-
+
The command is available without installing additional bundles (it is included
in the core bundle `org.apache.felix.healthcheck.core`)
## Monitoring Health Checks
@@ -270,7 +270,7 @@ The command is available without installing additional
bundles (it is included i
By default, health checks are only executed if explicitly triggered via one of
the mechanisms as described in [Executing Health
Checks](#executing-health-checks) (servlet, web console plugin, JMX, executor
API). With the `HealthCheckMonitor`, Health checks can be regularly monitored
by configuring the the **factory PID**
`org.apache.felix.hc.core.impl.monitor.HealthCheckMonitor` with the following
properties:
-Property | Type | Default | Description
+Property | Type | Default | Description
----------- | -------- | ------ | ------------
`tags` and/or `names` | String[] | none, at least one of the two is required |
**Will regularly call all given tags and/or names**. All given tags/names are
executed in parallel. If the set of tags/names include some checks multiple
times it does not matter, the `HealthCheckExecutor` will always ensure checks
are executed once at a time only.
`intervalInSec` or `cronExpression` | Long or String (cron) | none, one of the
two is required | The interval in which the given tags/names will be executed
@@ -282,15 +282,48 @@ Property | Type | Default | Description
`logAllResultsAsInfo` | boolean | false | If `logResults` is enabled and this
is enabled, all results will be logged with INFO log level. Otherwise WARN and
INFO are used depending on the health state.
`isDynamic` | boolean | false | In dynamic mode all checks for names/tags are
monitored individually (this means events are sent/services registered for name
only, never for given tags). This mode allows to use `*` in tags to query for
all health checks in system. It is also possible to query for all except
certain tags by using `-`, e.g. by configuring the values `*`, `-tag1` and
`-tag2` for `tags`.
-### Marker Service to depend on a health status in SCR Components
+### OSGi Condition to depend on a health status in
+
+It is possible to use [OSGi
Conditions](https://docs.osgi.org/specification/osgi.core/8.0.0/service.condition.html)
to depend on the health status of a certain tag or name. For that to work, a
`HealthCheckMonitor` needs to be configured for the relevant tag or name. An
OSGi Condition service is registered using the tag or name prefixed by
`felix.hc.`.
+
+For example, to depend on a health status in a Declarative Service component,
use `@SatisfyingConditionTarget`. The below will only activate the component on
healthiness of a certain tag/name:
+
+```
+@Component
+@SatisfyingConditionTarget("(osgi.condition.id=felix.hc.dbavail)")
+public class MyComponent {
+ ...
+}
+```
+
+It is also possible to use a Condition in a reference and later on in the code
figure out if healthiness is reached:
+
+```
+@Component
+public class MyComponent {
-It is possible to use OSGi service references to depend on the health status
of a certain
-tag or name. For that to work, a `HealthCheckMonitor` needs to be configured
for the relevant tag or name. To depend on a health status in a component, use
a `@Reference` to one of the marker services `Healthy`, `Unhealthy` and
`SystemReady` - this will then automatically activate/deactivate the component
based on the certain health status. To activate a component only upon
healthiness of a certain tag/name use the following code:
+ @Reference(target="(osgi.condition.id=felix.hc.dbavail)")
+ volatile Condition healthy;
+
+ public void mymethod() {
+ if (healthy == null) {
+ // not healthy
+ } else {
+ // healthy
+ }
+ }
+}
+```
+
+
+### Marker Service to depend on a health status in Declarative Service
Components
+
+It is possible to use OSGi service references to depend on the health status
of a certain tag or name. For that to work, a `HealthCheckMonitor` needs to be
configured for the relevant tag or name. To depend on a health status in a
component, use a `@Reference` to one of the marker services `Healthy`,
`Unhealthy` and `SystemReady` - this will then automatically
activate/deactivate the component based on the certain health status. To
activate a component only upon healthiness of a certain [...]
```
@Reference(target="(tag=dbavail)")
Healthy healthy;
-
+
@Reference(target="(name=My Health Check)")
Healthy healthy;
```
@@ -302,12 +335,11 @@ For the special tag `systemready`, there is a convenience
marker interface avail
```
It is also possible to depend on a unhealthy state (e.g. for fallback
functionality or self-healing):
-```
+```
@Reference(target="(tag=dbavail)")
Unhealthy unhealthy;
```
-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 and updates
@@ -318,13 +350,13 @@ OSGi events with topic `org/apache/felix/health/*` are
sent for tags/names that
All events sent generally carry the properties `executionResult`, `status` and
`previousStatus`.
-| Example | Description
+| 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:
+Event listener example:
```
@Component(property = { EventConstants.EVENT_TOPIC +
"=org/apache/felix/health/*"})
@@ -346,9 +378,9 @@ public class HealthEventHandler implements EventHandler {
### Service Unavailable Filter
-For health states of the system that mean that requests can only fail it is
possible to configure a Service Unavailable Filter that will cut off all
requests if certain tags are in a `CRITICAL` or `TEMPORARILY_UNAVAILABLE`
status. Typical usecases are startup/shutdown and deployments. Other scenarios
include maintenance processes that require request processing of certain
servlets to be stalled (the filter can be configured to be active on arbitrary
paths). It is possible to configure a [...]
+For health states of the system that mean that requests can only fail it is
possible to configure a Service Unavailable Filter that will cut off all
requests if certain tags are in a `CRITICAL` or `TEMPORARILY_UNAVAILABLE`
status. Typical usecases are startup/shutdown and deployments. Other scenarios
include maintenance processes that require request processing of certain
servlets to be stalled (the filter can be configured to be active on arbitrary
paths). It is possible to configure a [...]
-Configure the factory configuration with PID
+Configure the factory configuration with PID
`org.apache.felix.hc.core.impl.filter.ServiceUnavailableFilter` with specific
parameters to activate the Service Unavailable Filter:
| Name | Default/Required | Description |
@@ -371,7 +403,7 @@ Configure the factory configuration with PID
For certain scenarios it is useful to add a health check dynamically for a
specific tag durign request processing, e.g. it can be useful during deployment
requests (the tag(s) being added can be queried by e.g. load balancer or
Service Unavailable Filter.
-To achieve this configure the factory configuration with PID
+To achieve this configure the factory configuration with PID
`org.apache.felix.hc.core.impl.filter.AdhocResultDuringRequestProcessingFilter`
with specific parameters:
| Name | Default/Required | Description |
diff --git a/healthcheck/core/pom.xml b/healthcheck/core/pom.xml
index b1ecca7027..35f8252507 100644
--- a/healthcheck/core/pom.xml
+++ b/healthcheck/core/pom.xml
@@ -7,9 +7,9 @@
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
@@ -23,7 +23,7 @@
<parent>
<groupId>org.apache.felix</groupId>
<artifactId>felix-parent</artifactId>
- <version>7</version>
+ <version>9</version>
<relativePath />
</parent>
@@ -38,7 +38,7 @@
</description>
<properties>
- <felix.java.version>8</felix.java.version>
+ <felix.java.version>11</felix.java.version>
<pax-exam.version>4.13.4</pax-exam.version>
<pax-link.version>2.6.7</pax-link.version>
<org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
@@ -52,8 +52,7 @@
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
- <tag>org.apache.felix.healthcheck.core-2.2.0</tag>
- </scm>
+ </scm>
<build>
<plugins>
@@ -61,7 +60,7 @@
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
- <version>5.3.0</version>
+ <version>6.4.0</version>
<executions>
<execution>
<goals>
@@ -111,7 +110,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
- <version>3.0.0-M5</version>
+ <version>3.5.3</version>
<executions>
<execution>
<goals>
@@ -136,7 +135,7 @@
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
- <version>6.0.0</version>
+ <version>8.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -225,7 +224,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>3.11.2</version>
+ <version>5.17.0</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git
a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
index 3f2c3452db..d399c3c476 100644
---
a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
+++
b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
@@ -18,6 +18,7 @@
package org.apache.felix.hc.core.impl.monitor;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Dictionary;
@@ -40,13 +41,14 @@ import org.apache.felix.hc.core.impl.util.lang.StringUtils;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.condition.Condition;
import org.osgi.service.event.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class HealthState {
private static final Logger LOG =
LoggerFactory.getLogger(HealthState.class);
-
+
public static final String TAG_SYSTEMREADY = "systemready";
public static final String EVENT_TOPIC_PREFIX = "org/apache/felix/health";
@@ -57,15 +59,16 @@ class HealthState {
public static final String EVENT_PROP_STATUS = "status";
public static final String EVENT_PROP_PREVIOUS_STATUS = "previousStatus";
- static final Healthy MARKER_SERVICE_HEALTHY = new Healthy() {
- };
+ static final class HealthyCondition implements Condition, Healthy {};
+ static final class SystemReadyCondition implements Condition, SystemReady
{};
+
+ static final Healthy MARKER_SERVICE_HEALTHY = new HealthyCondition();
static final Unhealthy MARKER_SERVICE_UNHEALTHY = new Unhealthy() {
};
- static final SystemReady MARKER_SERVICE_SYSTEMREADY = new SystemReady() {
- };
-
+ static final SystemReady MARKER_SERVICE_SYSTEMREADY = new
SystemReadyCondition();
+
private final HealthCheckMonitor monitor;
-
+
private final String tagOrName;
private final ServiceReference<HealthCheck> healthCheckRef;
private final boolean isTag;
@@ -140,7 +143,7 @@ class HealthState {
update(result);
}
-
+
synchronized void update(HealthCheckExecutionResult executionResult) {
if(!isLive) {
LOG.trace("Not live anymore, skipping result update for {}", this);
@@ -177,20 +180,24 @@ class HealthState {
private void registerHealthyService() {
if (healthyRegistration == null) {
+ final boolean isSystemReady = TAG_SYSTEMREADY.equals(tagOrName);
LOG.debug("HealthCheckMonitor: registerHealthyService() {} ",
tagOrName);
Dictionary<String, String> registrationProps = new Hashtable<>();
registrationProps.put(propertyName, tagOrName);
registrationProps.put("activated", new
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+ registrationProps.put(Condition.CONDITION_ID,
"felix.hc.".concat(tagOrName));
- if (TAG_SYSTEMREADY.equals(tagOrName)) {
+ if (isSystemReady) {
LOG.debug("HealthCheckMonitor: SYSTEM READY");
- healthyRegistration =
monitor.getBundleContext().registerService(
- new String[] { SystemReady.class.getName(),
Healthy.class.getName() },
- MARKER_SERVICE_SYSTEMREADY, registrationProps);
- } else {
- healthyRegistration =
monitor.getBundleContext().registerService(Healthy.class,
MARKER_SERVICE_HEALTHY,
- registrationProps);
}
+ final List<String> services = new ArrayList<>();
+ services.add(Healthy.class.getName());
+ services.add(Condition.class.getName());
+ if (isSystemReady) {
+ services.add(SystemReady.class.getName());
+ }
+ final Object service = isSystemReady ? MARKER_SERVICE_SYSTEMREADY
: MARKER_SERVICE_HEALTHY;
+ healthyRegistration =
monitor.getBundleContext().registerService(services.toArray(new String[0]),
service, registrationProps);
LOG.debug("HealthCheckMonitor: Healthy service for {} '{}'
registered", propertyName, tagOrName);
}
}
@@ -224,7 +231,7 @@ class HealthState {
private void sendEvents(HealthCheckExecutionResult executionResult,
Result.Status previousStatus) {
ChangeType sendEventsConfig = monitor.getSendEvents();
- if (sendEventsConfig == ChangeType.ALL
+ if (sendEventsConfig == ChangeType.ALL
|| (statusChanged && (sendEventsConfig ==
ChangeType.STATUS_CHANGES || sendEventsConfig ==
ChangeType.STATUS_CHANGES_OR_NOT_OK))
|| (!executionResult.getHealthCheckResult().isOk() &&
sendEventsConfig == ChangeType.STATUS_CHANGES_OR_NOT_OK)) {
diff --git
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
index 6c11dd89ee..933b81220a 100644
---
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
+++
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
@@ -27,9 +27,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
+import java.util.Dictionary;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import org.apache.felix.hc.api.HealthCheck;
import org.apache.felix.hc.api.Result;
@@ -43,7 +43,6 @@ import org.apache.felix.hc.core.impl.util.HealthCheckFilter;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
-import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -66,7 +65,7 @@ public class CompositeHealthCheckTest {
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
compositeHealthCheck.setHealthCheckExecutor(healthCheckExecutor);
compositeHealthCheck.setFilterTags(new String[] {});
compositeHealthCheck.setComponentContext(componentContext);
@@ -75,8 +74,7 @@ public class CompositeHealthCheckTest {
@Test
public void testExecution() {
- doReturn((Result)
null).when(compositeHealthCheck).checkForRecursion(Matchers.<ServiceReference>
any(),
- Matchers.<Set<String>> any());
+ doReturn((Result)
null).when(compositeHealthCheck).checkForRecursion(any(), any());
String[] testTags = new String[] { "tag1" };
compositeHealthCheck.setFilterTags(testTags);
@@ -287,5 +285,16 @@ public class CompositeHealthCheckTest {
throw new UnsupportedOperationException();
}
+ @Override
+ public Object adapt(Class type) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Dictionary getProperties() {
+ // TODO Auto-generated method stub
+ return null;
+ }
}
}
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 47f69b5e7b..69d7df0a4b 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
@@ -18,6 +18,7 @@
package org.apache.felix.hc.core.impl.monitor;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -67,6 +68,7 @@ import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
+import org.osgi.service.condition.Condition;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
@@ -85,7 +87,7 @@ public class HealthCheckMonitorTest {
@Mock
private ComponentContext componentContext;
-
+
@Mock
private EventAdmin eventAdmin;
@@ -97,38 +99,42 @@ public class HealthCheckMonitorTest {
@Mock
private ExtendedHealthCheckExecutor healthCheckExecutor;
-
+
@Mock
private HealthCheckMetadata healthCheckMetadata;
@Mock
private ServiceReference<HealthCheck> healthCheckServiceRef;
-
+
@Captor
private ArgumentCaptor<Event> postedEventsCaptor1;
@Captor
private ArgumentCaptor<Event> postedEventsCaptor2;
-
+
+ @Captor
+ private ArgumentCaptor<Dictionary<String, Object>> propsCaptor;
+
+ @SuppressWarnings("rawtypes")
@Mock
- private ServiceRegistration<? extends Healthy> healthyRegistration;
-
+ private ServiceRegistration healthyRegistration;
+
@Mock
private ServiceRegistration<Unhealthy> unhealthyRegistration;
-
+
@Mock
private ResultTxtVerboseSerializer resultTxtVerboseSerializer;
-
+
@Before
public void before() throws ReflectiveOperationException {
for (Method m : HealthCheckMonitor.Config.class.getDeclaredMethods()) {
when(m.invoke(config)).thenReturn(m.getDefaultValue());
}
-
+
when(config.intervalInSec()).thenReturn(1000L);
when(config.tags()).thenReturn(new String[] { TEST_TAG });
-
+
when(healthCheckMetadata.getServiceReference()).thenReturn(healthCheckServiceRef);
when(healthCheckMetadata.getTitle()).thenReturn("Test Check");
@@ -152,15 +158,14 @@ public class HealthCheckMonitorTest {
assertEquals(1, healthCheckMonitor.healthStates.size());
assertEquals("[HealthState tagOrName=test-tag, isTag=true,
status=null, isHealthy=false, statusChanged=false]",
healthCheckMonitor.healthStates.get(TEST_TAG).toString());
-
+
healthCheckMonitor.deactivate();
assertEquals(0, healthCheckMonitor.healthStates.size());
-
+
}
@Test
public void testRunRegisterMarkerServices() throws InvalidSyntaxException {
-
when(config.registerHealthyMarkerService()).thenReturn(true);
when(config.registerUnhealthyMarkerService()).thenReturn(true);
healthCheckMonitor.activate(bundleContext, config, componentContext);
@@ -170,10 +175,16 @@ public class HealthCheckMonitorTest {
setHcResult(Result.Status.OK);
healthCheckMonitor.run();
-
+
verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
-
- verify(bundleContext).registerService(eq(Healthy.class),
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+
+ verify(bundleContext).registerService(eq(new String[]
{Healthy.class.getName(), Condition.class.getName()}),
+ eq(HealthState.MARKER_SERVICE_HEALTHY), propsCaptor.capture());
+ final Dictionary<String, Object> capturedProps1 =
propsCaptor.getValue();
+ assertEquals("test-tag", capturedProps1.get("tag"));
+ assertEquals("felix.hc.test-tag",
capturedProps1.get("osgi.condition.id"));
+ assertNotNull(capturedProps1.get("activated"));
+
verify(bundleContext, never()).registerService(eq(Unhealthy.class),
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
verifyNoInteractions(healthyRegistration, unhealthyRegistration);
@@ -181,30 +192,39 @@ public class HealthCheckMonitorTest {
healthCheckMonitor.run();
// no status change, no interaction
verifyNoInteractions(bundleContext, healthyRegistration,
unhealthyRegistration);
-
+
// change, unhealthy should be registered
resetMarkerServicesContext();
setHcResult(Result.Status.TEMPORARILY_UNAVAILABLE);
healthCheckMonitor.run();
-
- verify(bundleContext, never()).registerService(eq(Healthy.class),
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+
+ verify(bundleContext, never()).registerService(eq(new String[]
{Healthy.class.getName(), Condition.class.getName()}),
+ eq(HealthState.MARKER_SERVICE_HEALTHY), any());
verify(bundleContext).registerService(eq(Unhealthy.class),
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
verify(healthyRegistration).unregister();
verifyNoInteractions(unhealthyRegistration);
-
+
// change, health should be registered
resetMarkerServicesContext();
setHcResult(Result.Status.WARN); // WARN is healthy by default config
healthCheckMonitor.run();
- verify(bundleContext).registerService(eq(Healthy.class),
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+ verify(bundleContext).registerService(eq(new String[]
{Healthy.class.getName(), Condition.class.getName()}),
+ eq(HealthState.MARKER_SERVICE_HEALTHY), propsCaptor.capture());
+ final Dictionary<String, Object> capturedProps2 =
propsCaptor.getValue();
+ assertEquals("test-tag", capturedProps2.get("tag"));
+ assertEquals("felix.hc.test-tag",
capturedProps2.get("osgi.condition.id"));
+ assertNotNull(capturedProps2.get("activated"));
+
verify(bundleContext, never()).registerService(eq(Unhealthy.class),
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
verify(unhealthyRegistration).unregister();
verifyNoInteractions(healthyRegistration);
}
+ @SuppressWarnings("unchecked")
private void resetMarkerServicesContext() {
reset(bundleContext, healthyRegistration, unhealthyRegistration);
- when(bundleContext.registerService(eq(Healthy.class),
eq(HealthState.MARKER_SERVICE_HEALTHY),
any())).thenReturn((ServiceRegistration<Healthy>) healthyRegistration);
+ when(bundleContext.registerService(eq(new String[]
{Healthy.class.getName(), Condition.class.getName()}),
+ eq(HealthState.MARKER_SERVICE_HEALTHY),
any())).thenReturn(healthyRegistration);
lenient().when(bundleContext.registerService(eq(Unhealthy.class),
eq(HealthState.MARKER_SERVICE_UNHEALTHY),
any())).thenReturn(unhealthyRegistration);
}
@@ -219,9 +239,9 @@ public class HealthCheckMonitorTest {
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());
@@ -234,7 +254,7 @@ public class HealthCheckMonitorTest {
healthCheckMonitor.run();
// no event
verifyNoInteractions(eventAdmin);
-
+
setHcResult(Result.Status.CRITICAL);
reset(eventAdmin);
// with status change
@@ -246,7 +266,7 @@ public class HealthCheckMonitorTest {
assertEquals(Result.Status.CRITICAL,
postedEvents.get(0).getProperty(HealthState.EVENT_PROP_STATUS));
assertEquals(Result.Status.OK,
postedEvents.get(0).getProperty(HealthState.EVENT_PROP_PREVIOUS_STATUS));
assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/STATUS_CHANGED",
postedEvents.get(1).getTopic());
-
+
reset(eventAdmin);
// without status change
healthCheckMonitor.run();
@@ -262,13 +282,13 @@ public class HealthCheckMonitorTest {
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());
@@ -319,7 +339,7 @@ public class HealthCheckMonitorTest {
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
verify(healthCheckMonitor).logResultItem(eq(false),
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-
+
reset(healthCheckMonitor);
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
@@ -367,7 +387,7 @@ public class HealthCheckMonitorTest {
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
verify(healthCheckMonitor).logResultItem(eq(false),
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-
+
reset(healthCheckMonitor);
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
@@ -414,7 +434,7 @@ public class HealthCheckMonitorTest {
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
verify(healthCheckMonitor).logResultItem(eq(false),
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-
+
reset(healthCheckMonitor);
setHcResult(Result.Status.CRITICAL);
healthCheckMonitor.run();
@@ -452,7 +472,7 @@ public class HealthCheckMonitorTest {
verify(healthCheckMonitor, never()).logResultItem(anyBoolean(),
anyString());
}
-
+
private void prepareLoggingTest(HealthCheckMonitor.ChangeType
loggingChangeType) throws InvalidSyntaxException {
when(config.sendEvents()).thenReturn(HealthCheckMonitor.ChangeType.NONE);
when(config.logResults()).thenReturn(loggingChangeType);
@@ -467,7 +487,7 @@ public class HealthCheckMonitorTest {
}
});
}
-
+
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)));