You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cs...@apache.org on 2020/03/02 08:55:48 UTC

[felix-dev] branch master updated: FELIX-6231 - Restore systemready directory for bug fixes and documentation

This is an automated email from the ASF dual-hosted git repository.

cschneider pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new feae368  FELIX-6231 - Restore systemready directory for bug fixes and documentation
feae368 is described below

commit feae368e93a10083a9d31acc89cd54a57b8fd7c8
Author: Christian Schneider <cs...@adobe.com>
AuthorDate: Mon Mar 2 09:49:02 2020 +0100

    FELIX-6231 - Restore systemready directory for bug fixes and documentation
---
 systemready/.travis.yml                            |   1 +
 systemready/LICENSE                                | 201 ++++++++++++++++++
 systemready/README.md                              |  28 ++-
 systemready/bnd.bnd                                |   4 +
 systemready/docs/README.md                         | 125 +++++++++++
 systemready/docs/why_systemready.md                |  33 +++
 systemready/pom.xml                                | 230 +++++++++++++++++++++
 .../org/apache/felix/systemready/CheckStatus.java  |  80 +++++++
 .../org/apache/felix/systemready/StateType.java    |  23 +++
 .../org/apache/felix/systemready/SystemReady.java  |  26 +++
 .../apache/felix/systemready/SystemReadyCheck.java |  44 ++++
 .../felix/systemready/SystemReadyMonitor.java      |  33 +++
 .../org/apache/felix/systemready/SystemStatus.java |  44 ++++
 .../felix/systemready/impl/ComponentsCheck.java    | 113 ++++++++++
 .../systemready/impl/FrameworkStartCheck.java      | 124 +++++++++++
 .../felix/systemready/impl/ServicesCheck.java      | 134 ++++++++++++
 .../systemready/impl/SystemReadyMonitorImpl.java   | 156 ++++++++++++++
 .../org/apache/felix/systemready/impl/Tracker.java |  53 +++++
 .../systemready/impl/servlet/StatusReporter.java   |  51 +++++
 .../systemready/impl/servlet/StatusWriterJson.java |  55 +++++
 .../impl/servlet/SystemAliveServlet.java           |  96 +++++++++
 .../impl/servlet/SystemReadyServlet.java           |  96 +++++++++
 .../org/apache/felix/systemready/package-info.java |  22 ++
 .../org/apache/felix/systemready/StateTest.java    |  39 ++++
 .../systemready/osgi/ComponentsCheckTest.java      |  79 +++++++
 .../systemready/osgi/FrameworkStartTestGreen.java  |  58 ++++++
 .../systemready/osgi/FrameworkStartTestYellow.java |  59 ++++++
 .../felix/systemready/osgi/ServicesCheckTest.java  |  68 ++++++
 .../apache/felix/systemready/osgi/ServletTest.java | 123 +++++++++++
 .../systemready/osgi/SystemReadyMonitorTest.java   | 119 +++++++++++
 .../osgi/examples/CompWithoutService.java          |  31 +++
 .../osgi/examples/CompWithoutService2.java         |  31 +++
 .../osgi/examples/TestSystemReadyCheck.java        |  61 ++++++
 .../felix/systemready/osgi/util/BaseTest.java      | 166 +++++++++++++++
 .../felix/systemready/osgi/util/BndDSOptions.java  |  43 ++++
 systemready/src/test/resources/exam.properties     |  17 ++
 systemready/src/test/resources/logback.xml         |  30 +++
 37 files changed, 2695 insertions(+), 1 deletion(-)

diff --git a/systemready/.travis.yml b/systemready/.travis.yml
new file mode 100644
index 0000000..dff5f3a
--- /dev/null
+++ b/systemready/.travis.yml
@@ -0,0 +1 @@
+language: java
diff --git a/systemready/LICENSE b/systemready/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/systemready/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/systemready/README.md b/systemready/README.md
index 40147be..3c9afb2 100644
--- a/systemready/README.md
+++ b/systemready/README.md
@@ -1,3 +1,29 @@
 # OSGi system ready check framework
 
-Felix System Ready is obsolete and superseded by [Apache Felix Health Checks](https://felix.apache.org/documentation/subprojects/apache-felix-healthchecks.html). Use the out-of-the-box health checks in bundle [org.apache.felix.healthcheck.generalchecks](https://github.com/apache/felix/blob/trunk/healthcheck/README.md#general-purpose-health-checks-available-out-of-the-box).
\ No newline at end of file
+[![Build Status](https://builds.apache.org/buildStatus/icon?job=Felix%20Systemready)](https://builds.apache.org/job/Felix%20Systemready/)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.felix/org.apache.felix.systemready/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.felix%22%20a%3A%22org.apache.felix.systemready%22)
+
+In OSGi there is always the question of when a system is fully operational after startup. This project provides a framework to configure and create so called system checks and signal the ready state of an OSGi based system. In addition to the framework, we also provide some generic checks that give a solid basis, like a check waiting for the startup of bundles to finish, as well as certain OSGi services being present. Additionally, root cause analysis in case of error states is convenien [...]
+
+See [why system ready for some more background on why to use this project](docs/why_systemready.md).
+
+## Usage
+
+See [reference documentation](docs/README.md).
+
+## Build
+
+    mvn clean install
+
+Also check the [Jenkins build](https://builds.apache.org/job/Felix%20Systemready/)
+
+## Issue reporting
+
+We currently have these [open issues](https://issues.apache.org/jira/issues/?jql=project%20%3D%20FELIX%20AND%20component%20%3D%20%22System%20Ready%22%20AND%20resolution%20%3D%20Unresolved). Please report issues using project`Felix` and Component `System Ready`.
+
+## Working with the code
+
+As contributor the easiest way is to fork the [Felix project on github](https://github.com/apache/felix/tree/trunk/systemready) and do a pull request. We do not actively monitor pull requests so please also open an issue.
+
+When creating a PR it is a good practice to first create a branch named like the jira issue id. The commit message should also refer to the issue id. Like `FELIX-1234 My message`.
+
diff --git a/systemready/bnd.bnd b/systemready/bnd.bnd
new file mode 100644
index 0000000..d069e07
--- /dev/null
+++ b/systemready/bnd.bnd
@@ -0,0 +1,4 @@
+
+Import-Package: \
+	javax.servlet*;resolution:=optional,\
+	*
diff --git a/systemready/docs/README.md b/systemready/docs/README.md
new file mode 100644
index 0000000..ee518a9
--- /dev/null
+++ b/systemready/docs/README.md
@@ -0,0 +1,125 @@
+# Reference Documentation
+
+This project provides a framework to configure and create so called _system ready checks_ and report the _ready_ of an application on top of an OSGi system.
+
+## Requirements:
+
+* Configuration Admin Service
+* Service Component Runtime
+
+See below for a hands on example in Apache Karaf.
+
+## FrameworkStartCheck
+
+Checks that the system reaches a certain start level. By default this is the default bundle start level + 1.
+The check reports yellow while starting, green on startup finished and red if startup did not finish after given timeout.
+
+Configuration pid: `org.apache.felix.systemready.impl.FrameworkStartCheck`
+
+Name                         | Default | Description
+-----------------------------|---------|----------------
+target.start.level           |         | Start level for the system to reach to be considered started
+timeout                      | 1000    | After this number of seconds the startup is considered to have failed
+target_start_level_prop_name |         | Alternatively the start level can be read from a system property with this name
+
+## Services Check
+
+Ready check that is shipped in the core bundle and checks for the presence of listed services by interface name or filter.
+The check reports GREEN when all services are currently present and YELLOW if at least one service is missing.
+
+In the details the check reports all missing services. If a service is backed by a DS component then automatically a root cause analysis is executed. If such a service is missing then unresolved references are shown in a tree with detailed information about each component. At the leafs of the tree the root causes can be found.
+
+Mandatory configuration with pid: `org.apache.felix.systemready.impl.ServicesCheck`
+
+Name                         | Default | Description
+-----------------------------|---------|----------------
+services.list                |         | `List<String>` of service interfaces or filters to check
+
+## Component Check
+
+Ready check that is shipped in the core bundle and checks for the presence of listed DS components by name.
+The check reports GREEN when all components are satisfied. It also provides root cause analysis.
+The main difference to th Service Check is that the checked component does not need to offer an OSGi service.
+
+Mandatory configuration with pid: `org.apache.felix.systemready.impl.ComponentsCheck`
+
+Name                         | Default | Description
+-----------------------------|---------|----------------
+components.list              |         | `List<String>` of component names to check
+
+## Providing additional custom checks
+
+Implement the org.apache.felix.systemready.core.SystemReadyCheck interface and register
+your instance as a service. The SystemReadyMonitor will pick up your service automatically.
+
+Your service should avoid references to other services that come up late, as a late appearing check could
+make the aggregated state oscilate during startup. One option to avoid this is to refer to a late service using an optional dependency and return a YELLOW state until it is up.
+
+## System Ready Monitor service
+
+The service org.apache.felix.systemready.core.SystemReadyMonitor tracks all SystemReadyCheck services and periodically checks them. It creates an aggregated status and detailed report of the status of the system.
+
+This report can be queried by calling the service. Additionally the system ready servlet can provide this status over http.
+
+For an example see the [test case](../src/test/java/org/apache/felix/systemready/core/osgi/SystemReadyMonitorTest.java).
+
+## Ready and alive servlets
+
+The Ready servlet provides the aggregated state of the system over http in json format.
+It is registered on the path `/systemready`.
+
+The alive servlet provides the aggregated alive state of the system and is registered by default on `/systemalive`.
+
+HTTP return codes
+
+code | in case of
+-----|-----------
+200  | system status is GREEN
+503  | system status is YELLOW or RED
+404  | servlet is not yet present
+
+This is an example of a ready system with just the services check.
+```
+{
+  "systemStatus": "GREEN",
+  "checks": [
+    { "check": "Services Check", "status": "GREEN", "details": "" },
+  ]
+}
+```
+
+Optional configuration pid for ready servlet `org.apache.felix.systemready.impl.servlet.SystemReadyServlet`.
+Optional configuration pid for alive servlet `org.apache.felix.systemready.impl.servlet.SystemAliveServlet`.
+
+Name                                 | Default      | Description
+-------------------------------------|--------------|----------------
+osgi.http.whiteboard.servlet.pattern | /systemready | Path for the servlet
+osgi.http.whiteboard.context.select  |              | OSGi service filter for the whiteboard context
+
+The default context select filter works for Apache Karaf.
+When using the servlet in Apache Felix Http Whiteboard or Adobe AEM make sure you set the servlet context select to:
+
+    osgi.http.whiteboard.context.select =(osgi.http.whiteboard.context.name=org.osgi.service.http)
+
+## Example of using the system ready service framework in Apache Karaf
+
+Download, install and run Apache Karaf 4.1.x. Inside the karaf shell execute this:
+
+```
+feature:install scr http-whiteboard
+config:property-set --pid ServicesCheck services.list org.osgi.service.log.LogService
+config:property-set --pid SystemReadyServlet osgi.http.whiteboard.context.select "(osgi.http.whiteboard.context.name=default)"
+install -s mvn:org.apache.felix/org.apache.felix.rootcause/0.1.0-SNAPSHOT
+install -s mvn:org.apache.felix/org.apache.felix.systemready/0.5.0-SNAPSHOT
+```
+
+Point your browser to http://localhost:8181/system/console/ready .
+
+Check the status of a DS component:
+
+```
+rootcause SystemReadyMonitor
+ Component SystemReadyMonitor statisfied
+```
+
+Try this with some of your own components.
diff --git a/systemready/docs/why_systemready.md b/systemready/docs/why_systemready.md
new file mode 100644
index 0000000..368ff58
--- /dev/null
+++ b/systemready/docs/why_systemready.md
@@ -0,0 +1,33 @@
+# Why you should use the system ready project
+
+## Error reporting and root cause analysis
+
+When a system fails to become ready after a certain amount of time, the follow up question is : "What is wrong?". So the framework also provides a way for each system check to report back information about errors.
+
+One typical error is that a DS component does not come up because some mandatory dependency is not present. This dependency can be a configuration or maybe a service reference. In many cases, a trivial error like a missing configuration for a low level component can cause a lot of other components to fail to register. The root cause analysis allows traversing the trees of components and to narrow the reason for the system not being ready to a minimal number of possible causes for the fai [...]
+
+## Use cases
+
+Typical use cases for the system ready check ordered by the phase of the development cycle.
+
+### Development
+* Coarse check of a pax exam based setup before letting the actual junit tests work on the system
+* Determining the root cause of a pax exam based setup not starting
+
+### QA
+* Starting system tests as soon as a system is ready for the tests to proceed
+* Determining the root cause for an OSGi based system not starting up
+
+### Deployment
+* Signaling when a system is ready after an upgrade so that it can receive traffic in the case of blue / green deployments
+
+### Production
+* Periodic _Ready health checks_ for system and signaling this state to monitoring systems
+* Container automation and driving _autopilot container_ deployments.
+
+## Added value when using the system ready framework
+
+* Increased deployment resilience as deployments are checked before being exposed
+* Faster deployments / upgrades by replacing fixed, error-prone waits with ready based waits
+* Lower bug tracking efforts in tests as root causes are easier to find
+* Configurability of _when_ a certain deployment is considered ready (e.g. after content sync with an external service)
diff --git a/systemready/pom.xml b/systemready/pom.xml
new file mode 100644
index 0000000..31bf50d
--- /dev/null
+++ b/systemready/pom.xml
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+        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.
+    -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>6</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.felix.systemready</artifactId>
+    <version>0.4.3-SNAPSHOT</version>
+    <name>Apache Felix - System Ready</name>
+    
+    <properties>
+        <felix.java.version>8</felix.java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <exam.version>4.11.0</exam.version>
+    </properties>
+    
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/systemready</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/systemready</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/systemready</url>
+    </scm>
+    
+    <repositories>
+        <repository>
+            <id>apache.snapshots</id>
+            <name>snapshot plugins</name>
+            <url>http://repository.apache.org/snapshots</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
+                <version>4.0.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>bnd-process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <attach>true</attach>
+                    <quiet>true</quiet>
+                    <encoding>UTF-8</encoding>
+                    <additionalparam>-Xdoclint:none</additionalparam>
+                </configuration>
+            </plugin>
+            <plugin>
+	            <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+    			    <systemPropertyVariables>
+            			<org.ops4j.pax.url.mvn.localRepository>${settings.localRepository}</org.ops4j.pax.url.mvn.localRepository>
+        			</systemPropertyVariables>
+    			</configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+
+    <dependencies>
+    	<dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.rootcause</artifactId>
+            <version>0.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.annotation</artifactId>
+            <version>7.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.6</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <!-- Test dependencies -->
+        
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.hamcrest</artifactId>
+            <version>1.3_1</version>
+            <scope>test</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-native</artifactId>
+            <version>${exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+ 
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>${exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-cm</artifactId>
+            <version>${exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+ 
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-link-mvn</artifactId>
+            <version>${exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+		    <artifactId>pax-url-aether</artifactId>
+		    <version>2.5.4</version>
+		</dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>5.6.10</version>
+            <scope>test</scope>
+        </dependency>
+ 
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.0.13</version>
+            <scope>test</scope>
+        </dependency>
+ 
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.0.13</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.tinybundles</groupId>
+            <artifactId>tinybundles</artifactId>
+            <version>3.0.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <version>3.1.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/systemready/src/main/java/org/apache/felix/systemready/CheckStatus.java b/systemready/src/main/java/org/apache/felix/systemready/CheckStatus.java
new file mode 100644
index 0000000..ff42169
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/CheckStatus.java
@@ -0,0 +1,80 @@
+/*
+ * 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.felix.systemready;
+
+import static java.util.stream.Collectors.minBy;
+
+import java.util.stream.Stream;
+
+public final class CheckStatus {
+    public enum State { 
+    	// Be aware that the order of the enum declarations matters for the Comparator
+    	RED, YELLOW, GREEN;
+
+    	public static State fromBoolean(boolean ready) {
+            return (ready) ? State.GREEN : State.YELLOW;
+        }
+        
+        public static State worstOf(Stream<State> states) {
+            return states.collect(minBy(State::compareTo)).orElse(State.GREEN);
+        }
+    }
+    
+    private final String checkName;
+    
+    private final StateType type;
+
+    private final State state;
+
+    private final String details;
+    
+    public CheckStatus(String checkName, StateType type, State state, String details) {
+		this.checkName = checkName;
+		this.type = type;
+		this.state = state;
+        this.details = details;
+    }
+    
+    public String getCheckName() {
+		return checkName;
+	}
+    
+    
+    public StateType getType() {
+		return type;
+	}
+    
+    public State getState() {
+        return state;
+    }
+    
+    public String getDetails() {
+        return details;
+    }
+
+    @Override
+    public String toString() {
+        return "CheckStatus{" +
+                "state=" + state +
+                ", details='" + details + '\'' +
+                '}';
+    }
+    
+    
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/StateType.java b/systemready/src/main/java/org/apache/felix/systemready/StateType.java
new file mode 100644
index 0000000..f44a177
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/StateType.java
@@ -0,0 +1,23 @@
+/*
+ * 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.felix.systemready;
+
+public enum StateType {
+	ALIVE, READY;
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/SystemReady.java b/systemready/src/main/java/org/apache/felix/systemready/SystemReady.java
new file mode 100644
index 0000000..34443da
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/SystemReady.java
@@ -0,0 +1,26 @@
+/*
+ * 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.felix.systemready;
+
+/**
+ * Marker service that is registered when all system checks succeed.
+ */
+public interface SystemReady {
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/SystemReadyCheck.java b/systemready/src/main/java/org/apache/felix/systemready/SystemReadyCheck.java
new file mode 100644
index 0000000..7850cea
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/SystemReadyCheck.java
@@ -0,0 +1,44 @@
+/*
+ * 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.felix.systemready;
+
+/**
+ * Ready check services provide custom logic for signaling
+ * that particular criteria are met for when an instance is considered "ready".
+ *
+ * Examples: An asynchronous integration with another instance or a third-party service
+ *
+ * {@see SystemReadyMonitor}
+ *
+ */
+public interface SystemReadyCheck {
+
+    /**
+     *
+     * @return the name of this check. E.g. component name
+     */
+    String getName();
+
+    /**
+     *
+     * @return the state of the system
+     */
+    CheckStatus getStatus();
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/SystemReadyMonitor.java b/systemready/src/main/java/org/apache/felix/systemready/SystemReadyMonitor.java
new file mode 100644
index 0000000..c5bb42f
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/SystemReadyMonitor.java
@@ -0,0 +1,33 @@
+/*
+ * 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.felix.systemready;
+
+/**
+ * Checks that all registered ready and builds an aggregated state of the system.
+ * The aggregated state is the worst state of all checks.
+ */
+public interface SystemReadyMonitor  {
+
+    String PID = "org.apache.felix.systemready.SystemReadyMonitor";
+
+    /**
+     * @return detailed system state
+     */
+    SystemStatus getStatus(StateType stateType);
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/SystemStatus.java b/systemready/src/main/java/org/apache/felix/systemready/SystemStatus.java
new file mode 100644
index 0000000..460f6d7
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/SystemStatus.java
@@ -0,0 +1,44 @@
+/*
+ * 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.felix.systemready;
+
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import org.apache.felix.systemready.CheckStatus.State;
+
+public class SystemStatus {
+    private Collection<CheckStatus> checkStates;
+	private State state;
+    
+    public SystemStatus(Collection<CheckStatus> checkStates) {
+        this.checkStates = checkStates;
+        Stream<State> states = checkStates.stream()
+        		.map(status -> status.getState());
+        this.state = CheckStatus.State.worstOf(states);
+    }
+
+    public CheckStatus.State getState() {
+        return this.state;
+    }
+    
+    public Collection<CheckStatus> getCheckStates() {
+        return checkStates;
+    }
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/ComponentsCheck.java b/systemready/src/main/java/org/apache/felix/systemready/impl/ComponentsCheck.java
new file mode 100644
index 0000000..513813c
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/ComponentsCheck.java
@@ -0,0 +1,113 @@
+/*
+ * 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.felix.systemready.impl;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.felix.rootcause.DSComp;
+import org.apache.felix.rootcause.DSRootCause;
+import org.apache.felix.rootcause.RootCausePrinter;
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(
+        name = ComponentsCheck.PID,
+        configurationPolicy = ConfigurationPolicy.REQUIRE
+)
+@Designate(ocd=ComponentsCheck.Config.class)
+public class ComponentsCheck implements SystemReadyCheck {
+
+    public static final String PID = "org.apache.felix.systemready.impl.ComponentsCheck";
+
+    @ObjectClassDefinition(
+            name="DS Components System Ready Check",
+            description="System ready check that checks a list of DS components"
+                + "and provides root cause analysis in case of errors"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Components list", description = "The components that need to come up before this check reports GREEN")
+        String[] components_list();
+        
+        @AttributeDefinition(name = "Check type") 
+        StateType type() default StateType.ALIVE;
+
+    }
+
+    private List<String> componentsList;
+    
+    private DSRootCause analyzer;
+
+    private StateType type;
+    
+    @Reference
+    ServiceComponentRuntime scr;
+
+
+    @Activate
+    public void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        this.analyzer = new DSRootCause(scr);
+        this.type = config.type();
+        componentsList = Arrays.asList(config.components_list());
+    }
+
+    @Override
+    public String getName() {
+        return "Components Check " + componentsList;
+    }
+
+    @Override
+    public CheckStatus getStatus() {
+        StringBuilder details = new StringBuilder();
+        List<DSComp> watchedComps = scr.getComponentDescriptionDTOs().stream()
+            .filter(desc -> componentsList.contains(desc.name))
+            .map(analyzer::getRootCause)
+            .collect(Collectors.toList());
+        if (watchedComps.size() < componentsList.size()) {
+            throw new IllegalStateException("Not all named components could be found");
+        };
+        watchedComps.stream().forEach(dsComp -> addDetails(dsComp, details));
+        final CheckStatus.State state = CheckStatus.State.worstOf(watchedComps.stream().map(this::status));
+        return new CheckStatus(getName(), type, state, details.toString());
+    }
+    
+    private CheckStatus.State status(DSComp component) {
+        boolean missingConfig = component.config == null && "require".equals(component.desc.configurationPolicy);
+        boolean unsatisfied = !component.unsatisfied.isEmpty();
+        return (missingConfig || unsatisfied) ? CheckStatus.State.YELLOW : CheckStatus.State.GREEN;
+    }
+
+    private void addDetails(DSComp component, StringBuilder details) {
+        RootCausePrinter printer = new RootCausePrinter(st -> details.append(st + "\n"));
+        printer.print(component);
+    }
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/FrameworkStartCheck.java b/systemready/src/main/java/org/apache/felix/systemready/impl/FrameworkStartCheck.java
new file mode 100644
index 0000000..57d0eb8
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/FrameworkStartCheck.java
@@ -0,0 +1,124 @@
+/*
+ * 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.felix.systemready.impl;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        name = FrameworkStartCheck.PID,
+        immediate=true,
+        configurationPolicy = ConfigurationPolicy.OPTIONAL
+)
+@Designate(ocd=FrameworkStartCheck.Config.class)
+public class FrameworkStartCheck implements SystemReadyCheck {
+
+    public static final String PID = "org.apache.felix.systemready.impl.FrameworkStartCheck";
+    public static final String FRAMEWORK_STARTED = "Framework started. ";
+    public static final String FRAMEWORK_NOT_STARTED = "Framework NOT started. ";
+    public static final String FRAMEWORK_START_CHECK_NAME = "Framework Start Ready Check";
+
+    @ObjectClassDefinition(
+            name=FRAMEWORK_START_CHECK_NAME,
+            description="System ready that waits for the system bundle to be active"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Timeout (seconds)", description = "Number of seconds after which this is considered a failure")
+        long timeout() default 1000;
+
+        @AttributeDefinition(name = "Target start level", description = "The target start level at which the Framework " +
+                "is considered started. If zero or negative, it will default to the default bundle start level")
+        int target_start_level() default 0;
+
+        @AttributeDefinition(name = "Target start level OSGi property name",
+                description = "The name of the OSGi property which holds the " + "\"Target start level\". " +
+                        "It takes precedence over the target.start.level config. " +
+                        "If the startlevel cannot be derived from the osgi property, this config attribute is ignored.")
+        String target_start_level_prop_name() default "";
+        
+        @AttributeDefinition(name = "Check type") 
+        StateType type() default StateType.ALIVE;
+
+    }
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private BundleContext bundleContext;
+    private long targetStartLevel;
+	private StateType type;
+
+    @Activate
+    protected void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        this.bundleContext = ctx;
+        this.targetStartLevel = getTargetStartLevel(config);
+        this.type = config.type();
+        log.info("Activated");
+    }
+
+	private long getTargetStartLevel(final Config config) {
+		final FrameworkStartLevel fsl = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_ID).adapt(FrameworkStartLevel.class);
+        final long initial = fsl.getInitialBundleStartLevel();
+        // get the configured target start level, otherwise use the initial bundle start level
+        long tStartLevel = config.target_start_level() > 0 ? config.target_start_level() : initial;
+
+        // overwrite with the value from #target_start_level_prop_name if present
+        final String targetStartLevelKey = config.target_start_level_prop_name();
+        if (null != targetStartLevelKey && !targetStartLevelKey.trim().isEmpty()) {
+            try {
+                tStartLevel = Long.valueOf(bundleContext.getProperty(targetStartLevelKey));
+            } catch (NumberFormatException e) {
+                log.info("Ignoring {} as it can't be parsed: {}", targetStartLevelKey, e.getMessage());
+            }
+        }
+        return tStartLevel;
+	}
+
+    @Override
+    public String getName() {
+        return FRAMEWORK_START_CHECK_NAME;
+    }
+
+    @Override
+    public CheckStatus getStatus() {
+        Bundle systemBundle = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_ID);
+        FrameworkStartLevel fsl = systemBundle.adapt(FrameworkStartLevel.class);
+        String message = String.format("Start level: %d; Target start level: %d; Framework state: %d",
+                fsl.getStartLevel(), targetStartLevel, fsl.getBundle().getState());
+        boolean started = (systemBundle.getState() == Bundle.ACTIVE) && (fsl.getStartLevel() >= targetStartLevel);
+        if (started) {
+            return new CheckStatus(getName(), type, CheckStatus.State.GREEN, FRAMEWORK_STARTED + message);
+        } else {
+            return new CheckStatus(getName(), type, CheckStatus.State.YELLOW, FRAMEWORK_NOT_STARTED + message);
+        }
+    }
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/ServicesCheck.java b/systemready/src/main/java/org/apache/felix/systemready/impl/ServicesCheck.java
new file mode 100644
index 0000000..0884ef3
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/ServicesCheck.java
@@ -0,0 +1,134 @@
+/*
+ * 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.felix.systemready.impl;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.felix.rootcause.DSComp;
+import org.apache.felix.rootcause.DSRootCause;
+import org.apache.felix.rootcause.RootCausePrinter;
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(
+        name = ServicesCheck.PID,
+        configurationPolicy = ConfigurationPolicy.REQUIRE
+)
+@Designate(ocd=ServicesCheck.Config.class)
+public class ServicesCheck implements SystemReadyCheck {
+
+    public static final String PID = "org.apache.felix.systemready.impl.ServicesCheck";
+
+    @ObjectClassDefinition(
+            name="Services Registered System Ready Check",
+            description="System ready check that waits for a list of services to be registered"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Services list", description = "The services that need to be registered for the check to pass")
+        String[] services_list();
+
+        @AttributeDefinition(name = "Check type") 
+        StateType type() default StateType.ALIVE;
+    }
+
+    private List<String> servicesList;
+
+    private Map<String, Tracker> trackers;
+    
+    private DSRootCause analyzer;
+
+    private StateType type;
+
+    @Reference
+    private ServiceComponentRuntime scr;
+
+
+    @Activate
+    public void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        this.analyzer = new DSRootCause(scr);
+        this.servicesList = Arrays.asList(config.services_list());
+        this.trackers = this.servicesList.stream()
+        	.collect(toMap(identity(), serviceName -> new Tracker(ctx, serviceName)));
+        this.type = config.type();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        trackers.values().stream().forEach(Tracker::close);
+        trackers.clear();
+    }
+
+
+    @Override
+    public String getName() {
+        return "Services Check";
+    }
+
+    @Override
+    public CheckStatus getStatus() {
+        boolean allPresent = trackers.values().stream().allMatch(Tracker::present);
+        // TODO: RED on timeouts
+        final CheckStatus.State state = State.fromBoolean(allPresent);
+        return new CheckStatus(getName(), type, state, getDetails()); // TODO: out of sync? do we care?
+    }
+
+    private String getDetails() {
+        List<String> missing = getMissing();
+        StringBuilder missingSt = new StringBuilder();
+        RootCausePrinter printer = new RootCausePrinter(st -> missingSt.append(st + "\n"));
+        for (String iface : missing) {
+            Optional<DSComp> rootCause = analyzer.getRootCause(iface);
+            if (rootCause.isPresent()) {
+                printer.print(rootCause.get());
+            } else {
+                missingSt.append("Missing service without matching DS component: " + iface);
+            }
+        }
+        return missingSt.toString();
+    }
+
+    private List<String> getMissing() {
+        List<String> missing = trackers.entrySet().stream()
+                .filter(entry -> !entry.getValue().present())
+                .map(entry -> entry.getKey())
+                .collect(toList());
+        return missing;
+    }
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/SystemReadyMonitorImpl.java b/systemready/src/main/java/org/apache/felix/systemready/impl/SystemReadyMonitorImpl.java
new file mode 100644
index 0000000..3471df9
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/SystemReadyMonitorImpl.java
@@ -0,0 +1,156 @@
+/*
+ * 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.felix.systemready.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReady;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.apache.felix.systemready.SystemStatus;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        name = SystemReadyMonitor.PID
+)
+@Designate(ocd = SystemReadyMonitorImpl.Config.class)
+public class SystemReadyMonitorImpl implements SystemReadyMonitor {
+
+    @ObjectClassDefinition(
+            name = "System Ready Monitor",
+            description = "System ready monitor for System Ready Checks"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Poll interval",
+                description = "Number of milliseconds between subsequents updates of all the checks")
+        long poll_interval() default 5000;
+
+    }
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(policyOption = ReferencePolicyOption.GREEDY, policy = ReferencePolicy.DYNAMIC)
+    private volatile List<SystemReadyCheck> checks;
+
+    private BundleContext context;
+
+    private ServiceRegistration<SystemReady> sreg;
+
+    private ScheduledExecutorService executor;
+    
+    private AtomicReference<Collection<CheckStatus>> curStates;
+
+    public SystemReadyMonitorImpl() {
+    	CheckStatus checkStatus = new CheckStatus("dummy", StateType.READY, State.YELLOW, "");
+        this.curStates = new AtomicReference<>(Collections.singleton(checkStatus));
+    }
+
+    @Activate
+    public void activate(BundleContext context, final Config config) {
+        this.context = context;
+        this.executor = Executors.newSingleThreadScheduledExecutor();
+        this.executor.scheduleAtFixedRate(this::check, 0, config.poll_interval(), TimeUnit.MILLISECONDS);
+        log.info("Activated. Running checks every {} ms.", config.poll_interval());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        executor.shutdown();
+    }
+
+    @Override
+    /**
+     * Returns a map of the statuses of all the checks
+     */
+    public SystemStatus getStatus(StateType stateType) {
+    	Collection<CheckStatus> filtered = stateType == StateType.READY ? curStates.get() :
+    		curStates.get().stream()
+    			.filter(status -> status.getType() == StateType.ALIVE).collect(Collectors.toList());
+        return new SystemStatus(filtered);
+    }
+
+    private void check() {
+        try {
+            CheckStatus.State prevState = getStatus(StateType.READY).getState();
+            List<SystemReadyCheck> currentChecks = new ArrayList<>(checks);
+            List<String> checkNames = currentChecks.stream().map(check -> check.getName()).collect(Collectors.toList());
+            log.debug("Running system checks {}", checkNames);
+            List<CheckStatus> statuses = evaluateAllChecks(currentChecks);
+            this.curStates.set(statuses);
+            State currState = getStatus(StateType.READY).getState();
+            if (currState != prevState) {
+                manageMarkerService(currState);
+            }
+            log.debug("Checks finished");
+        } catch (Exception e) {
+            log.warn("Exception when running checks", e);
+        }
+    }
+
+    private List<CheckStatus> evaluateAllChecks(List<SystemReadyCheck> currentChecks) {
+        return currentChecks.stream()
+                .map(SystemReadyMonitorImpl::getStatus)
+                .sorted(Comparator.comparing(CheckStatus::getCheckName))
+                .collect(Collectors.toList());
+    }
+    
+    private void manageMarkerService(CheckStatus.State currState) {
+        if (currState == CheckStatus.State.GREEN) {
+            SystemReady readyService = new SystemReady() {
+            };
+            sreg = context.registerService(SystemReady.class, readyService, null);
+        } else if (sreg != null) {
+            sreg.unregister();
+        }
+    }
+
+    private static final CheckStatus getStatus(SystemReadyCheck c) {
+        try {
+            return c.getStatus();
+        } catch (Throwable e) {
+            return new CheckStatus(c.getName(), StateType.READY, CheckStatus.State.RED, e.getMessage());
+        }
+    }
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/Tracker.java b/systemready/src/main/java/org/apache/felix/systemready/impl/Tracker.java
new file mode 100644
index 0000000..67972cc
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/Tracker.java
@@ -0,0 +1,53 @@
+/*
+ * 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.felix.systemready.impl;
+
+import java.io.Closeable;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.util.tracker.ServiceTracker;
+
+class Tracker implements Closeable {
+    private ServiceTracker<?,?> stracker;
+
+    public Tracker(BundleContext context, String nameOrFilter) {
+        String filterSt = nameOrFilter.startsWith("(") ? nameOrFilter : String.format("(objectClass=%s)", nameOrFilter);
+        Filter filter;
+        try {
+            filter = FrameworkUtil.createFilter(filterSt);
+        } catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Error creating filter for " + nameOrFilter);
+        }
+        this.stracker = new ServiceTracker<>(context, filter, null);
+        this.stracker.open();
+    }
+    
+    public boolean present() {
+        return this.stracker.getTrackingCount() > 0;
+    }
+
+    @Override
+    public void close() {
+        stracker.close();
+    }
+    
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusReporter.java b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusReporter.java
new file mode 100644
index 0000000..96bcfc5
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusReporter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.felix.systemready.impl.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.apache.felix.systemready.SystemStatus;
+
+class StatusReporter {
+	private SystemReadyMonitor monitor;
+	private StateType type;
+		
+	public StatusReporter(SystemReadyMonitor monitor, StateType type) {
+		super();
+		this.monitor = monitor;
+		this.type = type;
+	}
+
+	public void reportState(HttpServletResponse response) throws IOException {
+		response.setContentType("application/json");
+		response.setCharacterEncoding("UTF-8");
+		SystemStatus systemState = this.monitor.getStatus(type);
+        if (! (systemState.getState() == CheckStatus.State.GREEN)) {
+            response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+        }
+        PrintWriter writer = response.getWriter();
+        new StatusWriterJson(writer).write(systemState);
+	}
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusWriterJson.java b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusWriterJson.java
new file mode 100644
index 0000000..0127c87
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/StatusWriterJson.java
@@ -0,0 +1,55 @@
+/*
+ * 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.felix.systemready.impl.servlet;
+
+import java.io.PrintWriter;
+import java.util.stream.Collectors;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.SystemStatus;
+
+class StatusWriterJson {
+
+    private PrintWriter writer;
+
+    public StatusWriterJson(PrintWriter writer) {
+        this.writer = writer;
+    }
+    
+    public void write(SystemStatus systemState) {
+        writer.println("{");
+        writer.println(String.format("  \"systemStatus\": \"%s\", ", systemState.getState().name()));
+        writer.println("  \"checks\": [");
+        String states = systemState.getCheckStates().stream()
+                .map(this:: getStatus)
+                .collect(Collectors.joining(",\n"));
+        writer.println(states);
+        writer.println("  ]");
+        writer.println("}");
+    }
+
+    private String getStatus(CheckStatus status) {
+        return String.format(
+                "    { \"check\": \"%s\", \"status\": \"%s\", \"details\": \"%s\" }", 
+                status.getCheckName(),
+                status.getState().name(), 
+                status.getDetails().replace("\n", "\\n"));
+    }
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemAliveServlet.java b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemAliveServlet.java
new file mode 100644
index 0000000..1c5f7a1
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemAliveServlet.java
@@ -0,0 +1,96 @@
+/*
+ * 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.felix.systemready.impl.servlet;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provide aggregated alive information using a servlet
+ */
+@Component(
+        name = SystemAliveServlet.PID,
+        service = Servlet.class,
+        property = {
+                HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN + "=" + SystemAliveServlet.DEFAULT_PATH,
+        }
+)
+@Designate(ocd=SystemAliveServlet.Config.class)
+public class SystemAliveServlet extends HttpServlet {
+    public static final String PID = "org.apache.felix.systemready.impl.servlet.SystemAliveServlet";
+    public static final String DEFAULT_PATH = "/systemalive";
+
+    private static final Logger LOG = LoggerFactory.getLogger(SystemAliveServlet.class);
+
+    private static final long serialVersionUID = 1L;
+
+    @ObjectClassDefinition(
+            name ="System Alive Servlet",
+            description="Servlet exposing a http endpoint for retrieving the alive status"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Servlet Path")
+        String osgi_http_whiteboard_servlet_pattern() default SystemAliveServlet.DEFAULT_PATH;
+        
+        @AttributeDefinition(name = "Servlet Context select")
+        String osgi_http_whiteboard_context_select();
+
+    }
+
+    @Reference
+    private SystemReadyMonitor monitor;
+    
+	private StatusReporter reporter;
+
+    @Activate
+    protected void activate(final BundleContext ctx, final Map<String, Object> properties, final Config config) {
+        final String path = config.osgi_http_whiteboard_servlet_pattern();
+        LOG.info("Registered servlet to listen on {}", path);
+        reporter = new StatusReporter(monitor, StateType.ALIVE);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        reporter.reportState(response);
+    }
+
+
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemReadyServlet.java b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemReadyServlet.java
new file mode 100644
index 0000000..d5843f1
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/impl/servlet/SystemReadyServlet.java
@@ -0,0 +1,96 @@
+/*
+ * 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.felix.systemready.impl.servlet;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provide aggregated ready information using a servlet
+ */
+@Component(
+        name = SystemReadyServlet.PID,
+        service = Servlet.class,
+        property = {
+                HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN + "=" + SystemReadyServlet.DEFAULT_PATH,
+        }
+)
+@Designate(ocd=SystemReadyServlet.Config.class)
+public class SystemReadyServlet extends HttpServlet {
+    public static final String PID = "org.apache.felix.systemready.impl.servlet.SystemReadyServlet";
+    public static final String DEFAULT_PATH = "/systemready";
+
+    private static final Logger LOG = LoggerFactory.getLogger(SystemReadyServlet.class);
+
+    private static final long serialVersionUID = 1L;
+
+    @ObjectClassDefinition(
+            name ="System Ready Servlet",
+            description="Servlet exposing a http endpoint for retrieving the ready status"
+    )
+    public @interface Config {
+
+        @AttributeDefinition(name = "Servlet Path")
+        String osgi_http_whiteboard_servlet_pattern() default SystemReadyServlet.DEFAULT_PATH;
+        
+        @AttributeDefinition(name = "Servlet Context select")
+        String osgi_http_whiteboard_context_select();
+
+    }
+
+    @Reference
+    private SystemReadyMonitor monitor;
+    
+	private StatusReporter reporter;
+
+    @Activate
+    protected void activate(final BundleContext ctx, final Map<String, Object> properties, final Config config) {
+        final String path = config.osgi_http_whiteboard_servlet_pattern();
+        LOG.info("Registered servlet to listen on {}", path);
+        reporter = new StatusReporter(monitor, StateType.READY);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        reporter.reportState(response);
+    }
+
+
+
+}
diff --git a/systemready/src/main/java/org/apache/felix/systemready/package-info.java b/systemready/src/main/java/org/apache/felix/systemready/package-info.java
new file mode 100644
index 0000000..88db1a7
--- /dev/null
+++ b/systemready/src/main/java/org/apache/felix/systemready/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("0.2.0")
+@org.osgi.annotation.bundle.Export
+package org.apache.felix.systemready;
diff --git a/systemready/src/test/java/org/apache/felix/systemready/StateTest.java b/systemready/src/test/java/org/apache/felix/systemready/StateTest.java
new file mode 100644
index 0000000..4af1f74
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/StateTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.felix.systemready;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class StateTest {
+    
+    @Test
+    public void testCompare() {
+        assertThat(worstOf(CheckStatus.State.GREEN, CheckStatus.State.YELLOW), equalTo(CheckStatus.State.YELLOW));
+        assertThat(worstOf(CheckStatus.State.GREEN, CheckStatus.State.YELLOW, CheckStatus.State.RED), equalTo(CheckStatus.State.RED));
+    }
+
+    private CheckStatus.State worstOf(CheckStatus.State...states) {
+        return CheckStatus.State.worstOf(Arrays.asList(states).stream());
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/ComponentsCheckTest.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/ComponentsCheckTest.java
new file mode 100644
index 0000000..12c78e4
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/ComponentsCheckTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.ops4j.pax.tinybundles.core.TinyBundles.bundle;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.impl.ComponentsCheck;
+import org.apache.felix.systemready.osgi.examples.CompWithoutService;
+import org.apache.felix.systemready.osgi.examples.CompWithoutService2;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.apache.felix.systemready.osgi.util.BndDSOptions;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.util.Filter;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+@RunWith(PaxExam.class)
+public class ComponentsCheckTest extends BaseTest {
+
+    @Inject
+    @Filter("(component.name=" + ComponentsCheck.PID + ")")
+    SystemReadyCheck check;
+    
+    @Inject
+    ConfigurationAdmin configAdmin;
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[] {
+                baseConfiguration(),
+                componentsCheckConfig("CompWithoutService", "CompWithoutService2"),
+                BndDSOptions.dsBundle("test", bundle()
+                        .add(CompWithoutService.class)
+                        .add(CompWithoutService2.class)
+                        )
+        };
+    }
+
+    @Test
+    public void test() throws IOException {
+        CheckStatus status = check.getStatus();
+        assertThat(status.getState(),  Matchers.is(CheckStatus.State.YELLOW));
+        assertThat(status.getDetails(), containsString("unsatisfied references"));
+        //configAdmin.getConfiguration("CompWithoutService").update();
+        context.registerService(Runnable.class, () -> {}, null);
+        CheckStatus status2 = check.getStatus();
+        System.out.println(status2);
+        assertThat(status2.getState(),  Matchers.is(CheckStatus.State.GREEN));
+        assertThat(status2.getDetails(), containsString(" satisfied"));
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestGreen.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestGreen.java
new file mode 100644
index 0000000..1e46eca
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestGreen.java
@@ -0,0 +1,58 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.impl.FrameworkStartCheck;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.util.Filter;
+
+@RunWith(PaxExam.class)
+public class FrameworkStartTestGreen extends BaseTest {
+
+    @Inject
+    @Filter("(component.name=" + FrameworkStartCheck.PID + ")")
+    SystemReadyCheck check;
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[] {
+                baseConfiguration(),
+                newConfiguration(FrameworkStartCheck.PID)
+                        .asOption()
+        };
+    }
+
+    @Test
+    public void test() {
+        CheckStatus status = check.getStatus();
+        Assert.assertEquals(CheckStatus.State.GREEN, status.getState());
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestYellow.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestYellow.java
new file mode 100644
index 0000000..671b044
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/FrameworkStartTestYellow.java
@@ -0,0 +1,59 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.impl.FrameworkStartCheck;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.util.Filter;
+
+import javax.inject.Inject;
+
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+@RunWith(PaxExam.class)
+public class FrameworkStartTestYellow extends BaseTest {
+
+    @Inject
+    @Filter("(component.name=" + FrameworkStartCheck.PID + ")")
+    SystemReadyCheck check;
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[] {
+                baseConfiguration(),
+                newConfiguration(FrameworkStartCheck.PID)
+                        .put("target.start.level", 100)
+                        .asOption()
+        };
+    }
+
+    @Test
+    public void test() {
+        CheckStatus status = check.getStatus();
+        Assert.assertEquals(CheckStatus.State.YELLOW, status.getState());
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/ServicesCheckTest.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/ServicesCheckTest.java
new file mode 100644
index 0000000..4f18e5b
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/ServicesCheckTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.impl.ServicesCheck;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.util.Filter;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+
+@RunWith(PaxExam.class)
+public class ServicesCheckTest extends BaseTest {
+
+    @Inject
+    @Filter("(component.name=" + ServicesCheck.PID + ")")
+    SystemReadyCheck check;
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[] {
+                baseConfiguration(),
+                servicesCheckConfig(StateType.ALIVE, Runnable.class.getName(), ServiceComponentRuntime.class.getName()),
+        };
+    }
+
+    @Test
+    public void test() {
+        CheckStatus status = check.getStatus();
+        assertThat(status.getState(),  is(State.YELLOW));
+        assertThat(status.getDetails(), containsString("Missing service without matching DS component: java.lang.Runnable"));
+        context.registerService(Runnable.class, () -> {}, null);
+        CheckStatus status2 = check.getStatus();
+        System.out.println(status2);
+        assertThat(status2.getState(),  is(State.GREEN));
+        assertThat(status2.getDetails(), equalTo(""));
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/ServletTest.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/ServletTest.java
new file mode 100644
index 0000000..0e1ba8a
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/ServletTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertThat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.awaitility.Awaitility;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerMethod;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class ServletTest extends BaseTest {
+    public static final String READY_SERVLET_PATH = "/readyservlet/path";
+	private static final String ALIVE_SERVLET_PATH = "/aliveservlet/path";
+    @Inject
+    SystemReadyMonitor monitor;
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[] {
+                baseConfiguration(),
+                readyServletConfig(READY_SERVLET_PATH),
+                aliveServletConfig(ALIVE_SERVLET_PATH),
+                httpService(),
+                monitorConfig(),
+                servicesCheckConfig(StateType.ALIVE, Runnable.class.getName()),
+                servicesCheckConfig(StateType.READY, Consumer.class.getName())
+        };
+    }
+
+    @Test
+    public void testServlets() throws IOException, InterruptedException {
+        disableFrameworkStartCheck();
+
+        Awaitility.pollInSameThread();
+        
+        waitState(StateType.READY, CheckStatus.State.YELLOW);
+        await().until(() -> readFromUrl(getUrl(ALIVE_SERVLET_PATH), 503), containsString("\"systemStatus\": \"YELLOW\""));
+        await().until(() -> readFromUrl(getUrl(READY_SERVLET_PATH), 503), containsString("\"systemStatus\": \"YELLOW\""));
+        context.registerService(Runnable.class, () -> {}, null);
+        waitState(StateType.ALIVE, State.GREEN);
+        waitState(StateType.READY, State.YELLOW);
+
+        await().until(() -> readFromUrl(getUrl(ALIVE_SERVLET_PATH), 200), containsString("\"systemStatus\": \"GREEN\""));
+        await().until(() -> readFromUrl(getUrl(READY_SERVLET_PATH), 503), containsString("\"systemStatus\": \"YELLOW\""));
+
+        context.registerService(Consumer.class, input -> {}, null);
+        
+        waitState(StateType.ALIVE, State.GREEN);
+        waitState(StateType.READY, State.GREEN);
+        
+        await().until(() -> readFromUrl(getUrl(ALIVE_SERVLET_PATH), 200), containsString("\"systemStatus\": \"GREEN\""));
+        await().until(() -> readFromUrl(getUrl(READY_SERVLET_PATH), 200), containsString("\"systemStatus\": \"GREEN\""));
+    }
+    
+    
+
+	private void waitState(StateType type, State expectedState) {
+		Awaitility.await().until(() -> monitor.getStatus(type).getState(), is(expectedState));
+	}
+    
+    private String getUrl(String path) {
+        return URI.create("http://localhost:8080").resolve(path).toString();
+    }
+
+    private String readFromUrl(String address, int expectedCode) throws MalformedURLException, IOException {
+        URL url = new URL(address);   
+        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+        int code = urlConnection.getResponseCode();
+        assertThat(code, equalTo(expectedCode));
+        InputStream in = code == 200 ? urlConnection.getInputStream(): urlConnection.getErrorStream();
+        InputStreamReader reader = new InputStreamReader(in);
+        BufferedReader buffered = new BufferedReader(reader);
+        String content = buffered.lines().collect(Collectors.joining("\n"));
+        buffered.close();
+        return content;
+    }
+
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/SystemReadyMonitorTest.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/SystemReadyMonitorTest.java
new file mode 100644
index 0000000..17077e8
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/SystemReadyMonitorTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.felix.systemready.osgi;
+
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.net.MalformedURLException;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyCheck;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.apache.felix.systemready.osgi.examples.TestSystemReadyCheck;
+import org.apache.felix.systemready.osgi.util.BaseTest;
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionFactory;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+
+@RunWith(PaxExam.class)
+public class SystemReadyMonitorTest extends BaseTest {
+
+    @Inject
+    SystemReadyMonitor monitor;
+
+    private final ConditionFactory wait = await();
+
+    @Configuration
+    public Option[] configuration() throws MalformedURLException {
+        return new Option[] {
+                baseConfiguration(),
+                monitorConfig()
+        };
+    }
+
+    @Test
+    public void test() throws InterruptedException {
+        disableFrameworkStartCheck();
+
+        Awaitility.setDefaultPollDelay(0, TimeUnit.MILLISECONDS);
+        assertNumChecks(0);
+        wait.until(this::getState, is(State.GREEN));
+
+        TestSystemReadyCheck check = new TestSystemReadyCheck();
+        context.registerService(SystemReadyCheck.class, check, null);
+        assertNumChecks(1);
+        wait.until(this::getState, is(State.YELLOW));
+
+        // make the status green
+        check.setInternalState(CheckStatus.State.GREEN);
+        wait.until(this::getState, is(State.GREEN));
+
+        // make the status fail and check that the monitor handles that
+        check.exception();
+        wait.until(this::getState, is(State.RED));
+        assertNumChecks(1);
+
+        CheckStatus status = monitor.getStatus(StateType.READY).getCheckStates().iterator().next();
+        assertThat(status.getCheckName(), is(check.getName()));
+        assertThat(status.getState(), Matchers.is(CheckStatus.State.RED));
+        assertThat(status.getDetails(), containsString("Failure"));
+
+        check.setInternalState(CheckStatus.State.RED);
+        assertNumChecks(1);
+        wait.until(this::getState, is(State.RED));
+
+        // register a second check
+        TestSystemReadyCheck check2 = new TestSystemReadyCheck();
+        context.registerService(SystemReadyCheck.class, check2, null);
+        assertNumChecks(2);
+        wait.until(this::getState, is(State.RED));
+
+        check2.setInternalState(CheckStatus.State.GREEN);
+        wait.until(this::getState, is(State.RED));
+
+        check.setInternalState(CheckStatus.State.GREEN);
+        wait.until(this::getState, is(State.GREEN));
+
+    }
+    
+    private State getState() {
+    	return monitor.getStatus(StateType.READY).getState();
+    }
+
+    private void assertNumChecks(int expectedNum) {
+        wait.until(this::numChecks, is(expectedNum));
+    }
+
+    private int numChecks() {
+        return monitor.getStatus(StateType.READY).getCheckStates().size();
+    }
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService.java
new file mode 100644
index 0000000..a8eba08
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.felix.systemready.osgi.examples;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(
+        name = "CompWithoutService"
+        )
+public class CompWithoutService {
+
+    @Reference
+    Runnable dummy;
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService2.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService2.java
new file mode 100644
index 0000000..760cd54
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/CompWithoutService2.java
@@ -0,0 +1,31 @@
+/*
+ * 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.felix.systemready.osgi.examples;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(
+        name = "CompWithoutService2"
+        )
+public class CompWithoutService2 {
+
+    @Reference
+    Runnable dummy;
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/TestSystemReadyCheck.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/TestSystemReadyCheck.java
new file mode 100644
index 0000000..6dd147a
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/examples/TestSystemReadyCheck.java
@@ -0,0 +1,61 @@
+/*
+ * 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.felix.systemready.osgi.examples;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.felix.systemready.CheckStatus;
+import org.apache.felix.systemready.CheckStatus.State;
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyCheck;
+
+public class TestSystemReadyCheck implements SystemReadyCheck {
+
+    private State state;
+    private AtomicReference<RuntimeException> ex = new AtomicReference<>(null);
+
+    public TestSystemReadyCheck() {
+        this.state = State.YELLOW;
+    }
+
+    @Override
+    public String getName() {
+        return "Test Check";
+    }
+
+    @Override
+    public CheckStatus getStatus() {
+        if (null == ex.get()) {
+            return new CheckStatus(getName(), StateType.READY, state, state.name());
+        } else {
+            throw ex.get();
+        }
+    }
+
+    public void setInternalState(State state) {
+        this.ex.set(null);
+        this.state = state;
+    }
+
+    public void exception() {
+        this.ex.set(new RuntimeException("Failure"));
+        this.state = State.RED;
+    }
+
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BaseTest.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BaseTest.java
new file mode 100644
index 0000000..c8e4ff2
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BaseTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.felix.systemready.osgi.util;
+
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.when;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.StateType;
+import org.apache.felix.systemready.SystemReadyMonitor;
+import org.apache.felix.systemready.impl.ComponentsCheck;
+import org.apache.felix.systemready.impl.FrameworkStartCheck;
+import org.apache.felix.systemready.impl.ServicesCheck;
+import org.apache.felix.systemready.impl.servlet.SystemAliveServlet;
+import org.apache.felix.systemready.impl.servlet.SystemReadyServlet;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.cm.ConfigurationAdminOptions;
+import org.ops4j.pax.exam.options.OptionalCompositeOption;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
+import org.osgi.util.promise.Promise;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class BaseTest {
+    private Logger log = LoggerFactory.getLogger(this.getClass());
+    
+    @Inject
+    public BundleContext context;
+
+    @Inject
+    public ServiceComponentRuntime scr;
+
+    public Option baseConfiguration() {
+    	String localRepo = System.getProperty("maven.repo.local");
+        if (localRepo != null) {
+            System.setProperty("org.ops4j.pax.url.mvn.localRepository", localRepo);
+        }
+        return CoreOptions.composite(
+        		
+                systemProperty("pax.exam.invoker").value("junit"),
+                systemProperty("pax.exam.osgi.unresolved.fail").value("true"),
+                systemProperty("logback.configurationFile")
+                    .value("src/test/resources/logback.xml"),
+                mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").version("1.7.6"),
+                mavenBundle().groupId("ch.qos.logback").artifactId("logback-core").version("1.0.13"),
+                mavenBundle().groupId("ch.qos.logback").artifactId("logback-classic").version("1.0.13"),
+                
+                bundle("link:classpath:META-INF/links/org.ops4j.pax.tipi.junit.link"),
+                bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link"),
+                mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.hamcrest").version("1.3_1"),
+                mavenBundle().groupId("org.awaitility").artifactId("awaitility").version("3.1.0"),
+
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").version("2.0.14"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").version("1.8.16"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.rootcause").version("0.1.0-SNAPSHOT"),
+                bundle("reference:file:target/classes/")
+
+        );
+    }
+    
+    protected static OptionalCompositeOption localRepo() {
+        String localRepo = System.getProperty("maven.repo.local", "");
+        return when(localRepo.length() > 0)
+        	.useOptions(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepo));
+    }
+    
+    public Option servicesCheckConfig(StateType type, String... services) {
+        return ConfigurationAdminOptions.factoryConfiguration(ServicesCheck.PID)
+                .put("services.list", services)
+                .put("type", type.name())
+                .asOption();
+    }
+    
+    public Option componentsCheckConfig(String... components) {
+        return newConfiguration(ComponentsCheck.PID)
+                .put("components.list", components)
+                .asOption();
+    }
+    
+    public Option monitorConfig() {
+        return newConfiguration(SystemReadyMonitor.PID)
+                .put("poll.interval", 100)
+                .asOption();
+    }
+    
+    public Option httpService() {
+        return CoreOptions.composite(
+                mavenBundle("org.apache.felix", "org.apache.felix.http.servlet-api", "1.1.2"),
+                mavenBundle("org.apache.felix", "org.apache.felix.http.jetty", "3.4.8")
+                );
+    }
+
+    public Option readyServletConfig(String path) {
+        return newConfiguration(SystemReadyServlet.PID)
+                .put("osgi.http.whiteboard.servlet.pattern", path)
+                .asOption();
+    }
+    
+    public Option aliveServletConfig(String path) {
+        return newConfiguration(SystemAliveServlet.PID)
+                .put("osgi.http.whiteboard.servlet.pattern", path)
+                .asOption();
+    }
+
+    public ComponentDescriptionDTO getComponentDesc(String compName) {
+        return getComponentDesc(desc -> desc.name.equals(compName), compName);
+    }
+
+    public ComponentDescriptionDTO getComponentDesc(Class<?> compClass) {
+        return getComponentDesc(desc -> desc.implementationClass.equals(compClass.getName()), compClass.getName());
+    }
+
+    public ComponentDescriptionDTO getComponentDesc(Predicate<ComponentDescriptionDTO> predicate, String label) {
+        Optional<ComponentDescriptionDTO> result = scr.getComponentDescriptionDTOs().stream()
+                .filter(predicate)
+                .findFirst();
+        if (result.isPresent()) {
+            return result.get();
+        } else {
+            throw new RuntimeException("Component " + label + " not found");
+        }
+    }
+
+    public void disableComponent(String name) {
+        ComponentDescriptionDTO desc = getComponentDesc(name);
+        log.info("Deactivating component {}", desc.name);
+        Promise<Void> promise = scr.disableComponent(desc);
+        try {
+            promise.getValue();
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    public void disableFrameworkStartCheck() {
+        disableComponent(FrameworkStartCheck.PID);
+    }
+
+}
diff --git a/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BndDSOptions.java b/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BndDSOptions.java
new file mode 100644
index 0000000..5f628a3
--- /dev/null
+++ b/systemready/src/test/java/org/apache/felix/systemready/osgi/util/BndDSOptions.java
@@ -0,0 +1,43 @@
+/*
+ * 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.felix.systemready.osgi.util;
+
+import static org.ops4j.pax.exam.CoreOptions.streamBundle;
+import static org.ops4j.pax.tinybundles.core.TinyBundles.withBnd;
+
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.tinybundles.core.TinyBundle;
+
+/**
+ * This must be in its own bundle and static to avoid that TinyBundles has to be deployed in OSGi
+ */
+public class BndDSOptions {
+
+    private BndDSOptions() {
+    }
+
+    /**
+     * Create a bundle with DS support and automatically generated exports and imports
+     */
+    public static Option dsBundle(String symbolicName, TinyBundle bundleDef) {
+        return streamBundle(bundleDef
+                .symbolicName(symbolicName)
+                .build(withBnd()));
+    }
+}
diff --git a/systemready/src/test/resources/exam.properties b/systemready/src/test/resources/exam.properties
new file mode 100644
index 0000000..f97d3f6
--- /dev/null
+++ b/systemready/src/test/resources/exam.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+pax.exam.logging = none
diff --git a/systemready/src/test/resources/logback.xml b/systemready/src/test/resources/logback.xml
new file mode 100644
index 0000000..636d591
--- /dev/null
+++ b/systemready/src/test/resources/logback.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<configuration>
+  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%date %level [%thread] %logger{20} [%file : %line] %msg - %mdc %n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="console"/>
+  </root>
+</configuration>