You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gh...@apache.org on 2019/03/27 20:53:28 UTC

svn commit: r1856437 - in /felix/trunk/healthcheck: ./ generalchecks/ generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/

Author: ghenzler
Date: Wed Mar 27 20:53:28 2019
New Revision: 1856437

URL: http://svn.apache.org/viewvc?rev=1856437&view=rev
Log:
FELIX-6014 Migrated system ready checks to health checks (general checks bundle)

Added:
    felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java   (with props)
    felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java   (with props)
    felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java   (with props)
    felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/
    felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java   (with props)
Modified:
    felix/trunk/healthcheck/README.md
    felix/trunk/healthcheck/generalchecks/bnd.bnd
    felix/trunk/healthcheck/generalchecks/pom.xml

Modified: felix/trunk/healthcheck/README.md
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/README.md?rev=1856437&r1=1856436&r2=1856437&view=diff
==============================================================================
--- felix/trunk/healthcheck/README.md (original)
+++ felix/trunk/healthcheck/README.md Wed Mar 27 20:53:28 2019
@@ -1,13 +1,12 @@
 
 # Felix Health Checks
 
-Based on simple `HealthCheck` OSGi services, the Felix Health Check Tools ("hc" in short form) are used to 
-check the health of live Felix systems, based on inputs like  OSGi framework status, JMX MBean attribute values, or any context information retrieved via any API.
+Based on simple `HealthCheck` OSGi services, the Felix Health Check Tools ("hc" in short form) are used to check the health of live Felix systems, based on inputs like  OSGi framework status, JMX MBean attribute values, or any context information retrieved via any API.
 
 Health checks are easily extensible either by configuring the supplied default `HealthCheck` services, or 
 by implementing your own `HealthCheck` services to cater for project specific requirements.
 
-However for simple setups, the out of the box health checks are often sufficient. [Executing Health Checks](#executing-health-checks)
+However for simple setups, the out of the box health checks (bundle general checks) are often sufficient. [Executing Health Checks](#executing-health-checks)
 is a good starting point to run existing checks and to get familiar with how health checks work.
 
 See also:
@@ -116,13 +115,16 @@ All service properties are optional.
 
 The following checks are contained in bundle `org.apache.felix.healthcheck.generalchecks` and can be activated by simple configuration:
 
-Default Name | PID | Factory | Description  
+Check | PID | Factory | Description  
 --- | --- | --- | ---
+Framework Startlevel | org.apache.felix.hc.generalchecks.FrameworkStartCheck | no | Checks the OSGi framework startlevel - `targetStartLevel` allows to configure a target start level, `targetStartLevel.propName` can be used to read it from the framework/system properties. 
+Services Ready | org.apache.felix.hc.generalchecks.ServicesCheck | yes | Checks for the existance of the given services. `services.list` can contain simple service names or filter expressions 
+Components Ready | org.apache.felix.hc.generalchecks.DsComponentsCheck | yes | Checks for the existance of the given components. Use `components.list` to list required active components (use component names) 
+Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes | Checks for started bundles - `includesRegex` and `excludesRegex` control what bundles are checked. 
 Disk Space | org.apache.felix.hc.generalchecks.DiskSpaceCheck | yes | Checks for disk space usage at the given paths `diskPaths` and checks them against thresholds `diskUsedThresholdWarn` (default 90%) and diskUsedThresholdCritical (default 97%)
 Memory | org.apache.felix.hc.generalchecks.MemoryCheck | no | Checks for Memory usage - `heapUsedPercentageThresholdWarn` (default 90%) and `heapUsedPercentageThresholdCritical` (default 99%) can be set to control what memory usage produces status `WARN` and `CRITICAL`
 CPU | org.apache.felix.hc.generalchecks.CpuCheck | no | Checks for CPU usage - `cpuPercentageThresholdWarn` (default 95%) can be set to control what CPU usage produces status `WARN` (check never results in `CRITICAL`)
 Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no | Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses the CPU usage of each thread via a configurable time period (`samplePeriodInMs` defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN` about high thread utilisation.   
-Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes | Checks for started bundles - `includesRegex` and `excludesRegex` control what bundles are checked. 
 JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheck | yes | Allows to check an arbitrary JMX attribute (using the configured mbean `mbean.name`'s attribute `attribute.name`) against a given constraint `attribute.value.constraint`. Can check multiple attributes by providing additional config properties with numbers:  `mbean2.name`' `attribute2.name` and `attribute2.value.constraint`.
 HttpRequestsCheck | org.apache.felix.hc.generalchecks.HttpRequestsCheck | yes | Allows to check a list of URLs against response code, response headers, timing, response content (plain content via RegEx or JSON via path expression).
 

Modified: felix/trunk/healthcheck/generalchecks/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/bnd.bnd?rev=1856437&r1=1856436&r2=1856437&view=diff
==============================================================================
--- felix/trunk/healthcheck/generalchecks/bnd.bnd (original)
+++ felix/trunk/healthcheck/generalchecks/bnd.bnd Wed Mar 27 20:53:28 2019
@@ -11,3 +11,5 @@ Bundle-Vendor: The Apache Software Found
 Conditional-Package: org.apache.commons.cli.*,org.apache.felix.utils.*
 
 Export-Package: org.apache.felix.hc.generalchecks.util
+
+Import-Package: org.apache.felix.rootcause*;resolution:="optional", *
\ No newline at end of file

Modified: felix/trunk/healthcheck/generalchecks/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/pom.xml?rev=1856437&r1=1856436&r2=1856437&view=diff
==============================================================================
--- felix/trunk/healthcheck/generalchecks/pom.xml (original)
+++ felix/trunk/healthcheck/generalchecks/pom.xml Wed Mar 27 20:53:28 2019
@@ -140,6 +140,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.rootcause</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.7.6</version>

Added: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java?rev=1856437&view=auto
==============================================================================
--- felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java (added)
+++ felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java Wed Mar 27 20:53:28 2019
@@ -0,0 +1,178 @@
+/*
+ * 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.hc.generalchecks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.annotation.HealthCheckService;
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.generalchecks.scrutil.DsRootCauseAnalyzer;
+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.annotations.ReferenceCardinality;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
+import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@HealthCheckService(name = DsComponentsCheck.HC_NAME, tags = { DsComponentsCheck.HC_DEFAULT_TAG })
+@Designate(ocd = DsComponentsCheck.Config.class, factory = true)
+public class DsComponentsCheck implements HealthCheck {
+
+    public static final String HC_NAME = "DS Components Ready Check";
+    public static final String HC_DEFAULT_TAG = "systemalive";
+
+    @ObjectClassDefinition(name = "Health Check: "
+            + HC_NAME, 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 = "Name", description = "Name of this health check")
+        String hc_name() default HC_NAME;
+
+        @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+        String[] hc_tags() default { HC_DEFAULT_TAG };
+
+        @AttributeDefinition(name = "Required Component Names", description = "The components that are required to be enabled")
+        String[] components_list();
+
+        @AttributeDefinition(name = "Status for missing component", description = "Status in case components are missing enabled components")
+        Result.Status statusForMissing() default Result.Status.TEMPORARILY_UNAVAILABLE;
+
+        @AttributeDefinition
+        String webconsole_configurationFactory_nameHint() default "{hc.name}: {components.list} / missing -> {statusForMissing}";
+    }
+
+    private List<String> componentsList;
+    private Result.Status statusForMissing;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL)
+    private DsRootCauseAnalyzer analyzer;
+
+    @Reference
+    ServiceComponentRuntime scr;
+
+    @Activate
+    public void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        componentsList = Arrays.asList(config.components_list());
+        statusForMissing = config.statusForMissing();
+    }
+
+    @Override
+    public Result execute() {
+
+        Collection<ComponentDescriptionDTO> componentDescriptionDTOs = scr.getComponentDescriptionDTOs();
+        List<ComponentDescriptionDTO> watchedComps = new LinkedList<ComponentDescriptionDTO>();
+        FormattingResultLog log = new FormattingResultLog();
+        List<String> missingComponents = new LinkedList<String>(componentsList);
+        for (ComponentDescriptionDTO desc : componentDescriptionDTOs) {
+            if (componentsList.contains(desc.name)) {
+                watchedComps.add(desc);
+                missingComponents.remove(desc.name);
+            }
+        }
+        for (String missingComp : missingComponents) {
+            log.temporarilyUnavailable("Not found {}", missingComp);
+        }
+
+        int countEnabled = 0;
+        int countDisabled = 0;
+        for (ComponentDescriptionDTO dsComp : watchedComps) {
+
+            boolean isActive;
+
+            boolean componentEnabled = scr.isComponentEnabled(dsComp);
+            if (componentEnabled) {
+
+                Collection<ComponentConfigurationDTO> componentConfigurationDTOs = scr.getComponentConfigurationDTOs(dsComp);
+                List<String> idStateTuples = new ArrayList<>();
+                boolean foundActiveOrSatisfiedConfig = false;
+                for (ComponentConfigurationDTO configDto : componentConfigurationDTOs) {
+                    idStateTuples.add("id " + configDto.id + ":" + toStateString(configDto.state));
+                    if (configDto.state == ComponentConfigurationDTO.ACTIVE || configDto.state == ComponentConfigurationDTO.SATISFIED) {
+                        foundActiveOrSatisfiedConfig = true;
+                    }
+                }
+                log.debug(dsComp.name + " (" + StringUtils.join(idStateTuples, ",") + ")");
+
+                if (componentConfigurationDTOs.isEmpty() || foundActiveOrSatisfiedConfig) {
+                    countEnabled++;
+                    isActive = true;
+                } else {
+                    countDisabled++;
+                    isActive = false;
+                }
+
+            } else {
+                countDisabled++;
+                isActive = false;
+            }
+
+            if (!isActive) {
+                if (analyzer != null) {
+                    analyzer.logNotEnabledComponent(log, dsComp, statusForMissing);
+                } else {
+                    log.add(new Entry(statusForMissing, "Not active: " + dsComp.name));
+                }
+            }
+
+        }
+
+        if (countDisabled > 0) {
+            log.temporarilyUnavailable("{} required components are not active", countDisabled);
+        }
+        log.info("{} required components are active", countEnabled);
+
+        return new Result(log);
+    }
+
+    static final String toStateString(int state) {
+
+        final int FAILED_ACTIVATION = 16; // not yet available in r6, but dependency should be left on r6 for max compatibility
+
+        switch (state) {
+        case ComponentConfigurationDTO.ACTIVE:
+            return "active";
+        case ComponentConfigurationDTO.SATISFIED:
+            return "satisfied";
+        case ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION:
+            return "unsatisfied (configuration)";
+        case ComponentConfigurationDTO.UNSATISFIED_REFERENCE:
+            return "unsatisfied (reference)";
+        case FAILED_ACTIVATION:
+            return "failed activation";
+        default:
+            return String.valueOf(state);
+        }
+    }
+
+}

Propchange: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java?rev=1856437&view=auto
==============================================================================
--- felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java (added)
+++ felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java Wed Mar 27 20:53:28 2019
@@ -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.hc.generalchecks;
+
+import org.apache.felix.hc.annotation.HealthCheckService;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+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(immediate = true, configurationPolicy = ConfigurationPolicy.OPTIONAL)
+@HealthCheckService(name = FrameworkStartCheck.HC_NAME, tags = { FrameworkStartCheck.HC_DEFAULT_TAG })
+@Designate(ocd = FrameworkStartCheck.Config.class)
+public class FrameworkStartCheck implements HealthCheck {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FrameworkStartCheck.class);
+
+    public static final String HC_NAME = "OSGi Framework Ready Check";
+    public static final String HC_DEFAULT_TAG = "systemalive";
+
+    public static final String FRAMEWORK_STARTED = "Framework started. ";
+    public static final String FRAMEWORK_NOT_STARTED = "Framework NOT started. ";
+
+    @ObjectClassDefinition(name = "Health Check: " + HC_NAME, description = "System ready that waits for the system bundle to be active")
+    public @interface Config {
+
+        @AttributeDefinition(name = "Name", description = "Name of this health check")
+        String hc_name() default HC_NAME;
+
+        @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+        String[] hc_tags() default { HC_DEFAULT_TAG };
+
+        @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 targetStartLevel() 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 'targetStartLevel' config. " +
+                "If the startlevel cannot be derived from the osgi property, this config attribute is ignored.")
+        String targetStartLevel_propName() default "";
+
+    }
+
+    private BundleContext bundleContext;
+    private long targetStartLevel;
+
+    @Activate
+    protected void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        this.bundleContext = ctx;
+        this.targetStartLevel = getTargetStartLevel(config);
+        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.targetStartLevel() > 0 ? config.targetStartLevel() : initial;
+
+        // overwrite with the value from #targetStartLevel_propName if present
+        final String targetStartLevelKey = config.targetStartLevel_propName();
+        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 Result execute() {
+        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 Result(Result.Status.OK, FRAMEWORK_STARTED + message);
+        } else {
+            return new Result(Result.Status.TEMPORARILY_UNAVAILABLE, FRAMEWORK_NOT_STARTED + message);
+        }
+    }
+
+}

Propchange: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/FrameworkStartCheck.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java?rev=1856437&view=auto
==============================================================================
--- felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java (added)
+++ felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java Wed Mar 27 20:53:28 2019
@@ -0,0 +1,174 @@
+/*
+ * 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.hc.generalchecks;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.annotation.HealthCheckService;
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.generalchecks.scrutil.DsRootCauseAnalyzer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+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.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+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;
+import org.osgi.util.tracker.ServiceTracker;
+
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@HealthCheckService(name = ServicesCheck.HC_NAME, tags = { ServicesCheck.HC_DEFAULT_TAG })
+@Designate(ocd = ServicesCheck.Config.class, factory = true)
+public class ServicesCheck implements HealthCheck {
+
+    public static final String HC_NAME = "Services Ready Check";
+    public static final String HC_DEFAULT_TAG = "systemalive";
+
+    @ObjectClassDefinition(name = "Health Check: " + HC_NAME, 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 = "Name", description = "Name of this health check")
+        String hc_name() default HC_NAME;
+
+        @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+        String[] hc_tags() default { HC_DEFAULT_TAG };
+
+        @AttributeDefinition(name = "Services list", description = "The services that need to be registered for the check to pass. This can be either the service name (objectClass) or an arbitrary filter expression if the expression starts with '(' (for that case at least one service for the filter needs to be available)")
+        String[] services_list();
+
+        @AttributeDefinition(name = "Status for missing services", description = "Status in case services are missing")
+        Result.Status statusForMissing() default Result.Status.TEMPORARILY_UNAVAILABLE;
+
+        @AttributeDefinition
+        String webconsole_configurationFactory_nameHint() default "{hc.name}: {services.list} / missing -> {statusForMissing}";
+    }
+
+    private List<String> servicesList;
+    private Result.Status statusForMissing;
+
+    private Map<String, Tracker> trackers;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL)
+    private DsRootCauseAnalyzer analyzer;
+
+    @Reference
+    private ServiceComponentRuntime scr;
+
+    @Activate
+    public void activate(final BundleContext ctx, final Config config) throws InterruptedException {
+        this.servicesList = Arrays.asList(config.services_list());
+        this.trackers = this.servicesList.stream().collect(toMap(identity(), serviceName -> new Tracker(ctx, serviceName)));
+        statusForMissing = config.statusForMissing();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        trackers.values().stream().forEach(Tracker::close);
+        trackers.clear();
+    }
+
+    @Override
+    public Result execute() {
+        FormattingResultLog log = new FormattingResultLog();
+        List<String> missingServiceNames = getMissingServiceNames(log);
+
+
+        for (String missingServiceName : missingServiceNames) {
+            if (analyzer != null && !missingServiceName.startsWith("(")) {
+                analyzer.logMissingService(log, missingServiceName, statusForMissing);
+            } else {
+                log.info("Service '{}' is missing", missingServiceName);
+            }
+        }
+
+        if (missingServiceNames.isEmpty()) {
+            log.info("All {} required services are available", servicesList.size());
+        } else {
+            log.add(new Entry(statusForMissing, "Not all required services are available ("+missingServiceNames.size()+" are missing)"));
+        }
+        
+        return new Result(log);
+    }
+
+    private List<String> getMissingServiceNames(FormattingResultLog log) {
+        List<String> missingServicesNames = new LinkedList<>();
+
+        for(Map.Entry<String, Tracker> entry: trackers.entrySet()) {
+            if(!entry.getValue().present()) {
+                missingServicesNames.add(entry.getKey());
+            } else {
+                log.debug("Found {} services for '{}'", entry.getValue().getTrackingCount(), entry.getKey());
+            }
+        }
+        return missingServicesNames;
+    }
+
+    public 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 getTrackingCount() > 0;
+        }
+        
+        public int getTrackingCount() {
+            return this.stracker.getTrackingCount();
+        }
+        
+        @Override
+        public void close() {
+            stracker.close();
+        }
+
+    }
+
+}

Propchange: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java?rev=1856437&view=auto
==============================================================================
--- felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java (added)
+++ felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java Wed Mar 27 20:53:28 2019
@@ -0,0 +1,75 @@
+/*
+ * 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.hc.generalchecks.scrutil;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.Result.Status;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.rootcause.DSComp;
+import org.apache.felix.rootcause.DSRootCause;
+import org.apache.felix.rootcause.RootCausePrinter;
+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.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
+
+/** Minimal bridge to root cause in order to allow making that dependency optional. */
+@Component(service = DsRootCauseAnalyzer.class)
+public class DsRootCauseAnalyzer {
+
+    private DSRootCause analyzer;
+
+    @Reference
+    private ServiceComponentRuntime scr;
+
+    
+    @Activate
+    public void activate() throws InterruptedException {
+        this.analyzer = new DSRootCause(scr);
+    }
+
+    public void logMissingService(FormattingResultLog log, String missingServiceName, Status status) {
+        Optional<DSComp> rootCauseOptional = analyzer.getRootCause(missingServiceName);
+        if (rootCauseOptional.isPresent()) {
+            logRootCause(log,rootCauseOptional.get(), status);
+        } else {
+            log.add(new Entry(status, "Missing service without matching DS component: " + missingServiceName));
+        }
+    }
+
+    public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc, Status status) {
+        DSComp component = analyzer.getRootCause(desc);
+        logRootCause(log, component, status);
+    }
+
+    private void logRootCause(FormattingResultLog log, DSComp component, Status status) {
+        new RootCausePrinter(new Consumer<String>() {
+            private boolean firstLineLogged = false;
+            @Override
+            public void accept(String str) {
+                log.add(new Entry(!firstLineLogged ? status : Status.OK, str.replaceFirst("    ", "-- ").replaceFirst("  ", "- ")));
+                firstLineLogged = true;
+            }
+        }).print(component);
+    }
+}

Propchange: felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain