Added: felix/trunk/healthcheck/core/bnd.bnd URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/bnd.bnd?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/bnd.bnd (added) +++ felix/trunk/healthcheck/core/bnd.bnd Tue Dec 18 22:58:15 2018 @@ -0,0 +1,17 @@ +Bundle-Category: felix + +Bundle-Description: ${project.description} + +Bundle-DocURL: https://felix.apache.org + +Bundle-License: Apache License, Version 2.0 + +Bundle-Vendor: The Apache Software Foundation + +Import-Package: org.quartz*;resolution:="optional", * + +Conditional-Package: org.apache.felix.utils.* + +-removeheaders:\ + Include-Resource,\ + Private-Package
Added: felix/trunk/healthcheck/core/pom.xml URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/pom.xml?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/pom.xml (added) +++ felix/trunk/healthcheck/core/pom.xml Tue Dec 18 22:58:15 2018 @@ -0,0 +1,264 @@ +<?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. +--> +<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.healthcheck.core</artifactId> + <version>2.0.0-SNAPSHOT</version> + + <name>Apache Felix Health Check Core</name> + <inceptionYear>2013</inceptionYear> + + <description> + The Felix Health Check Core + </description> + + <properties> + <pax-exam.version>4.11.0</pax-exam.version> + <pax-link.version>2.4.3</pax-link.version> + <org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level> + <felix.shell>false</felix.shell> + <project.bundle.file>${project.build.directory}/${project.build.finalName}.jar</project.bundle.file> + </properties> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/healthcheck/core</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/healthcheck/core</developerConnection> + <url>http://svn.apache.org/viewvc/felix/trunk/http/core/</url> + </scm> + + <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>biz.aQute.bnd</groupId> + <artifactId>bnd-baseline-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent-integration</id> + <goals> + <goal>prepare-agent-integration</goal> + </goals> + <configuration> + <propertyName>coverage.command</propertyName> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.servicemix.tooling</groupId> + <artifactId>depends-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>generate-depends-file</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <systemProperties> + <org.ops4j.pax.logging.DefaultServiceLog.level>${org.ops4j.pax.logging.DefaultServiceLog.level}</org.ops4j.pax.logging.DefaultServiceLog.level> + <felix.shell>${felix.shell}</felix.shell> + <coverage.command>${coverage.command}</coverage.command> + <project.bundle.file>${project.bundle.file}</project.bundle.file> + </systemProperties> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + <version>6.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.cmpn</artifactId> + <version>6.0.0</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.healthcheck.api</artifactId> + <version>2.0.0-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.6</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.servicemix.bundles</groupId> + <artifactId>org.apache.servicemix.bundles.quartz</artifactId> + <version>2.3.0_2</version> + <scope>provided</scope> + <!-- if not present in runtime, there is no support for async health checks --> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.4</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>3.1.0</version> + <scope>provided</scope> + </dependency> + + <!-- used for JSONWriter, included in bundle via Conditional-Package --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.utils</artifactId> + <version>1.11.0</version> + <scope>provided</scope> + </dependency> + + + <!-- START test scope dependencies --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>rhino</groupId> + <artifactId>js</artifactId> + <version>1.6R6</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.7.6</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-container-forked</artifactId> + <version>${pax-exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-junit4</artifactId> + <version>${pax-exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-link-mvn</artifactId> + <version>${pax-exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.url</groupId> + <artifactId>pax-url-aether</artifactId> + <version>${pax-link.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.url</groupId> + <artifactId>pax-url-wrap</artifactId> + <version>${pax-link.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-atinject_1.0_spec</artifactId> + <version>1.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.framework</artifactId> + <version>5.6.10</version> + <scope>test</scope> + </dependency> + <!-- END test scope dependencies --> + + </dependencies> +</project> Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,189 @@ +/* + * 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 SF 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.core.impl; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.api.Result.Status; +import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions; +import org.apache.felix.hc.api.execution.HealthCheckExecutionResult; +import org.apache.felix.hc.api.execution.HealthCheckExecutor; +import org.apache.felix.hc.api.execution.HealthCheckSelector; +import org.apache.felix.hc.util.FormattingResultLog; +import org.apache.felix.hc.util.HealthCheckFilter; +import org.apache.felix.hc.util.HealthCheckMetadata; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentConstants; +import org.osgi.service.component.ComponentContext; +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.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** {@link HealthCheck} that executes a number of other HealthChecks, selected by their tags, and merges their Results. */ + +@Component(service = HealthCheck.class, configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = CompositeHealthCheckConfiguration.class, factory = true) +public class CompositeHealthCheck implements HealthCheck { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + static final String PROP_FILTER_TAGS = "filter.tags"; + + private String[] filterTags; + + private boolean combineTagsWithOr; + + @Reference + private HealthCheckExecutor healthCheckExecutor; + + private BundleContext bundleContext; + private HealthCheckFilter healthCheckFilter; + + private volatile ComponentContext componentContext; + + @Activate + protected void activate(final CompositeHealthCheckConfiguration configuration, final ComponentContext ctx) { + bundleContext = ctx.getBundleContext(); + componentContext = ctx; + healthCheckFilter = new HealthCheckFilter(bundleContext); + + filterTags = configuration.filter_tags(); + combineTagsWithOr = configuration.filter_combineTagsWithOr(); + log.debug("Activated, will select HealthCheck having tags {} {}", Arrays.asList(filterTags), + combineTagsWithOr ? "using OR" : "using AND"); + } + + @Deactivate + protected void deactivate() { + bundleContext = null; + healthCheckFilter = null; + componentContext = null; + } + + @Override + public Result execute() { + final ComponentContext localCtx = this.componentContext; + final ServiceReference referenceToThis = localCtx == null ? null : localCtx.getServiceReference(); + Result result = referenceToThis == null ? null : checkForRecursion(referenceToThis, new HashSet<String>()); + if (result != null) { + // return recursion error + return result; + } + + FormattingResultLog resultLog = new FormattingResultLog(); + HealthCheckExecutionOptions options = new HealthCheckExecutionOptions(); + options.setCombineTagsWithOr(combineTagsWithOr); + List<HealthCheckExecutionResult> executionResults = healthCheckExecutor.execute(HealthCheckSelector.tags(filterTags), options); + resultLog.debug("Executing {} HealthChecks selected by tags {}", executionResults.size(), Arrays.asList(filterTags)); + result = new CompositeResult(resultLog, executionResults); + + return result; + } + + Result checkForRecursion(ServiceReference hcReference, Set<String> alreadyBannedTags) { + + HealthCheckMetadata thisCheckMetadata = new HealthCheckMetadata(hcReference); + + Set<String> bannedTagsForThisCompositeCheck = new HashSet<String>(); + bannedTagsForThisCompositeCheck.addAll(alreadyBannedTags); + bannedTagsForThisCompositeCheck.addAll(thisCheckMetadata.getTags()); + + String[] tagsForIncludedChecksArr = toStringArray(hcReference.getProperty(PROP_FILTER_TAGS), new String[0]); + Set<String> tagsForIncludedChecks = new HashSet<String>(Arrays.asList(tagsForIncludedChecksArr)); + + log.debug("HC {} has banned tags {}", thisCheckMetadata.getName(), bannedTagsForThisCompositeCheck); + log.debug("tagsForIncludedChecks {}", tagsForIncludedChecks); + + // is this HC ok? + Set<String> intersection = new HashSet<String>(); + intersection.addAll(bannedTagsForThisCompositeCheck); + intersection.retainAll(tagsForIncludedChecks); + + if (!intersection.isEmpty()) { + return new Result(Status.HEALTH_CHECK_ERROR, + "INVALID CONFIGURATION: Cycle detected in composite health check hierarchy. Health check '" + + thisCheckMetadata.getName() + + "' (" + hcReference.getProperty(Constants.SERVICE_ID) + ") must not have tag(s) " + intersection + + " as a composite check in the hierarchy is itself already tagged alike (tags assigned to composite checks: " + + bannedTagsForThisCompositeCheck + ")"); + } + + // check each sub composite check + ServiceReference[] hcRefsOfCompositeCheck = healthCheckFilter + .getHealthCheckServiceReferences(HealthCheckSelector.tags(tagsForIncludedChecksArr), combineTagsWithOr); + for (ServiceReference hcRefOfCompositeCheck : hcRefsOfCompositeCheck) { + if (CompositeHealthCheck.class.getName().equals(hcRefOfCompositeCheck.getProperty(ComponentConstants.COMPONENT_NAME))) { + log.debug("Checking sub composite HC {}, {}", hcRefOfCompositeCheck, + hcRefOfCompositeCheck.getProperty(ComponentConstants.COMPONENT_NAME)); + Result result = checkForRecursion(hcRefOfCompositeCheck, bannedTagsForThisCompositeCheck); + if (result != null) { + // found recursion + return result; + } + } + + } + + // no recursion detected + return null; + + } + + public static String[] toStringArray(Object propValue, String[] defaultArray) { + if (propValue instanceof String[]) { + return (String[]) propValue; + } else if (propValue instanceof String) { + return new String[] { (String) propValue }; + } else if (propValue == null) { + return defaultArray; + } + return defaultArray; + } + + void setHealthCheckFilter(HealthCheckFilter healthCheckFilter) { + this.healthCheckFilter = healthCheckFilter; + } + + void setFilterTags(String[] filterTags) { + this.filterTags = filterTags; + } + + void setCombineTagsWithOr(boolean combineTagsWithOr) { + this.combineTagsWithOr = combineTagsWithOr; + } + + void setHealthCheckExecutor(HealthCheckExecutor healthCheckExecutor) { + this.healthCheckExecutor = healthCheckExecutor; + } + + void setComponentContext(ComponentContext ctx) { + this.componentContext = ctx; + } +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java Tue Dec 18 22:58:15 2018 @@ -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 SF 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.core.impl; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(name = "Apache Felix Composite Health Check", description = "Executes a set of health checks, selected by tags.") +@interface CompositeHealthCheckConfiguration { + + @AttributeDefinition(name = "Name", description = "Name of this health check.") + String hc_name() default ""; + + @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 {}; + + @AttributeDefinition(name = "MBean Name", description = "Name of the MBean to create for this health check. If empty, no MBean is registered.") + String hc_mbean_name() default ""; + + // + + @AttributeDefinition(name = "Filter Tags", description = "Tags used to select which health checks the composite health check executes.") + String[] filter_tags() default {}; + + @AttributeDefinition(name = "Combine Tags With Or", description = "Tags used to select which health checks the composite health check executes.") + boolean filter_combineTagsWithOr() default false; + +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,47 @@ +/* + * 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 SF 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.core.impl; + +import static org.apache.felix.hc.util.FormattingResultLog.msHumanReadable; + +import java.util.List; + +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.api.ResultLog; +import org.apache.felix.hc.api.ResultLog.Entry; +import org.apache.felix.hc.api.execution.HealthCheckExecutionResult; +import org.apache.felix.hc.util.HealthCheckMetadata; + +public class CompositeResult extends Result { + + public CompositeResult(ResultLog log, List<HealthCheckExecutionResult> executionResults) { + super(log); + + for (HealthCheckExecutionResult executionResult : executionResults) { + HealthCheckMetadata healthCheckMetadata = executionResult.getHealthCheckMetadata(); + Result healthCheckResult = executionResult.getHealthCheckResult(); + for (Entry entry : healthCheckResult) { + resultLog.add(new ResultLog.Entry(entry.getStatus(), healthCheckMetadata.getName() + ": " + entry.getMessage(), + entry.getException())); + } + resultLog.add(new ResultLog.Entry(healthCheckMetadata.getName() + " finished after " + + msHumanReadable(executionResult.getElapsedTimeInMs()) + (executionResult.hasTimedOut() ? " (timed out)" : ""), true)); + } + } + +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,251 @@ +/* + * 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 SF 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.core.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ReflectionException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.api.Result.Status; +import org.apache.felix.hc.api.ResultLog.Entry; +import org.apache.felix.hc.util.FormattingResultLog; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Allows to dynamically add a health check that returns WARN or CRITICAL for certain tags for testing purposes or go-live sequences. Uses an MBean to + * add/remove the DynamicTestingHealthCheck dynamically. */ +@Component +public class JmxAdjustableStatusHealthCheck { + private static final Logger LOG = LoggerFactory.getLogger(JmxAdjustableStatusHealthCheck.class); + + public static final String OBJECT_NAME = "org.apache.felix.healthcheck:type=AdjustableStatusHealthCheck"; + + private BundleContext bundleContext; + + private ServiceRegistration mbeanRegistration = null; + private ServiceRegistration healthCheckRegistration = null; + + @Activate + protected final void activate(final ComponentContext context) { + this.bundleContext = context.getBundleContext(); + registerMbean(); + } + + @Deactivate + protected final void deactivate(final ComponentContext context) { + unregisterMbean(); + unregisterDynamicTestingHealthCheck(); + } + + private void registerMbean() { + final Dictionary<String, String> mbeanProps = new Hashtable<String, String>(); + mbeanProps.put("jmx.objectname", OBJECT_NAME); + AdjustableHealthCheckStatusMBean adjustableHealthCheckStatusMBean = new AdjustableHealthCheckStatusMBean(); + this.mbeanRegistration = bundleContext.registerService(DynamicMBean.class.getName(), adjustableHealthCheckStatusMBean, mbeanProps); + LOG.debug("Registered mbean {} as {}", adjustableHealthCheckStatusMBean, OBJECT_NAME); + } + + private void unregisterMbean() { + if (this.mbeanRegistration != null) { + this.mbeanRegistration.unregister(); + this.mbeanRegistration = null; + LOG.debug("Unregistered mbean AdjustableHealthCheckStatusMBean"); + } + } + + /* synchronized as potentially multiple users can run JMX operations */ + private synchronized void registerDynamicTestingHealthCheck(Result.Status status, String[] tags) { + unregisterDynamicTestingHealthCheck(); + HealthCheck healthCheck = new DynamicTestingHealthCheck(status); + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(HealthCheck.NAME, "JMX-adjustable Check"); + props.put(HealthCheck.TAGS, tags); + + healthCheckRegistration = bundleContext.registerService(HealthCheck.class.getName(), healthCheck, props); + + } + + /* synchronized as potentially multiple users can run JMX operations */ + private synchronized void unregisterDynamicTestingHealthCheck() { + if (this.healthCheckRegistration != null) { + this.healthCheckRegistration.unregister(); + this.healthCheckRegistration = null; + LOG.debug("Unregistered DynamicTestingHealthCheck"); + } + } + + class DynamicTestingHealthCheck implements HealthCheck { + + private final Result.Status status; + + DynamicTestingHealthCheck(Result.Status status) { + this.status = status; + } + + @Override + public Result execute() { + FormattingResultLog resultLog = new FormattingResultLog(); + resultLog.add( + new Entry(status, "Set dynamically via JMX bean " + OBJECT_NAME)); + return new Result(resultLog); + } + + } + + private class AdjustableHealthCheckStatusMBean implements DynamicMBean { + + private static final String OP_RESET = "reset"; + private static final String OP_ADD_WARN_RESULT_FOR_TAGS = "addWarnResultForTags"; + private static final String OP_ADD_CRITICAL_RESULT_FOR_TAGS = "addCriticalResultForTags"; + + private static final String ATT_TAGS = "tags"; + private static final String ATT_STATUS = "status"; + + private static final String STATUS_INACTIVE = "INACTIVE"; + + /** The mbean info. */ + private final MBeanInfo mbeanInfo; + + private List<String> tags = new ArrayList<String>(); + private String status = STATUS_INACTIVE; + + public AdjustableHealthCheckStatusMBean() { + this.mbeanInfo = this.createMBeanInfo(); + } + + @Override + public Object getAttribute(final String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { + + if (ATT_TAGS.equals(attribute)) { + return StringUtils.join(tags, ","); + } else if (ATT_STATUS.equals(attribute)) { + return status.toString(); + } else { + throw new AttributeNotFoundException("Attribute " + attribute + " not found."); + } + } + + @Override + public AttributeList getAttributes(final String[] attributes) { + final AttributeList result = new AttributeList(); + for (String att : attributes) { + try { + result.add(new Attribute(att, getAttribute(att))); + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + return result; + } + + private MBeanInfo createMBeanInfo() { + final List<MBeanAttributeInfo> attrs = new ArrayList<MBeanAttributeInfo>(); + + attrs.add(new MBeanAttributeInfo(ATT_TAGS, String.class.getName(), "Tags", true, false, false)); + attrs.add(new MBeanAttributeInfo(ATT_STATUS, String.class.getName(), "Status", true, false, false)); + + MBeanParameterInfo[] params = new MBeanParameterInfo[] { + new MBeanParameterInfo(ATT_TAGS, "java.lang.String", "Comma separated list of tags") }; + + final List<MBeanOperationInfo> ops = new ArrayList<MBeanOperationInfo>(); + ops.add(new MBeanOperationInfo(OP_RESET, "Resets this testing mechanism and removes the failing HC", + new MBeanParameterInfo[0], "java.lang.String", MBeanOperationInfo.ACTION)); + ops.add(new MBeanOperationInfo(OP_ADD_CRITICAL_RESULT_FOR_TAGS, "Adds a critical result for the given tags", + params, "java.lang.String", MBeanOperationInfo.ACTION)); + ops.add(new MBeanOperationInfo(OP_ADD_WARN_RESULT_FOR_TAGS, "Adds a warn result for the given tags", + params, "java.lang.String", MBeanOperationInfo.ACTION)); + + return new MBeanInfo(this.getClass().getName(), + "Adjustable Health Check", attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null, + ops.toArray(new MBeanOperationInfo[ops.size()]), null); + } + + @Override + public MBeanInfo getMBeanInfo() { + return this.mbeanInfo; + } + + @Override + public Object invoke(final String actionName, final Object[] params, final String[] signature) + throws MBeanException, ReflectionException { + if (OP_RESET.equals(actionName)) { + tags = Arrays.asList(""); + status = STATUS_INACTIVE; + unregisterDynamicTestingHealthCheck(); + LOG.info("JMX-adjustable Health Check for testing was reset"); + return "Reset successful"; + } else if (OP_ADD_CRITICAL_RESULT_FOR_TAGS.equals(actionName)) { + String[] newTags = params[0].toString().split("[,; ]+"); + tags = Arrays.asList(newTags); + Status critical = Result.Status.CRITICAL; + status = critical.toString(); + registerDynamicTestingHealthCheck(critical, newTags); + LOG.info("Activated JMX-adjustable Health Check with status CRITICAL and tags " + StringUtils.join(tags, ",")); + return "Added check with result CRITICAL"; + } else if (OP_ADD_WARN_RESULT_FOR_TAGS.equals(actionName)) { + String[] newTags = params[0].toString().split("[,; ]+"); + tags = Arrays.asList(newTags); + Status warn = Result.Status.WARN; + status = warn.toString(); + registerDynamicTestingHealthCheck(warn, newTags); + LOG.info("Activated JMX-adjustable Health Check with status WARN and tags " + StringUtils.join(tags, ",")); + return "Added check with result WARN"; + } else { + throw new MBeanException( + new UnsupportedOperationException(getClass().getSimpleName() + " does not support operation " + actionName)); + } + } + + @Override + public void setAttribute(final Attribute attribute) + throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, + ReflectionException { + throw new MBeanException( + new UnsupportedOperationException(getClass().getSimpleName() + " does not support setting attributes.")); + } + + @Override + public AttributeList setAttributes(final AttributeList attributes) { + return new AttributeList(); + } + } + +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,77 @@ +/* + * 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 SF 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.core.impl; + +import java.lang.management.ManagementFactory; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.util.FormattingResultLog; +import org.apache.felix.hc.util.SimpleConstraintChecker; +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.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** {@link HealthCheck} that checks a single JMX attribute */ +@Component(service = HealthCheck.class, configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = JmxAttributeHealthCheckConfiguration.class, factory = true) +public class JmxAttributeHealthCheck implements HealthCheck { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private String mbeanName; + private String attributeName; + private String constraint; + + @Activate + protected void activate(final JmxAttributeHealthCheckConfiguration configuration) { + mbeanName = configuration.mbean_name(); + attributeName = configuration.attribute_name(); + constraint = configuration.attribute_value_constraint(); + + log.debug("Activated with HealthCheck name={}, objectName={}, attribute={}, constraint={}", + new Object[] { configuration.hc_name(), mbeanName, attributeName, constraint }); + } + + @Override + public Result execute() { + final FormattingResultLog resultLog = new FormattingResultLog(); + resultLog.debug("Checking {} / {} with constraint {}", mbeanName, attributeName, constraint); + try { + final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer(); + final ObjectName objectName = new ObjectName(mbeanName); + if (jmxServer.queryNames(objectName, null).size() == 0) { + resultLog.warn("MBean not found: {}", objectName); + } else { + final Object value = jmxServer.getAttribute(objectName, attributeName); + resultLog.debug("{} {} returns {}", mbeanName, attributeName, value); + new SimpleConstraintChecker().check(value, constraint, resultLog); + } + } catch (final Exception e) { + log.warn("JMX attribute {}/{} check failed: {}", new Object[] { mbeanName, attributeName, e }); + resultLog.healthCheckError("JMX attribute check failed: {}", e); + } + return new Result(resultLog); + } +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,46 @@ +/* + * 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 SF 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.core.impl; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(name = "Apache Felix JMX Attribute Health Check", description = "Checks the value of a single JMX attribute.") +@interface JmxAttributeHealthCheckConfiguration { + + @AttributeDefinition(name = "Name", description = "Name of this health check.") + String hc_name() default ""; + + @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 {}; + + @AttributeDefinition(name = "MBean Name", description = "Name of the MBean to create for this health check. If empty, no MBean is registered.") + String hc_mbean_name() default ""; + + // + + @AttributeDefinition(name = "Check MBean Name", description = "The name of the MBean to check by this health check.") + String mbean_name() default ""; + + @AttributeDefinition(name = "Check Attribute Name", description = "The name of the MBean attribute to check by this health check.") + String attribute_name() default ""; + + @AttributeDefinition(name = "Check Attribute Constraint", description = "Constraint on the MBean attribute value.") + String attribute_value_constraint() default ""; + +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,116 @@ +/* + * 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 SF 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.core.impl.executor; + +import java.text.Collator; +import java.util.Date; + +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.api.execution.HealthCheckExecutionResult; +import org.apache.felix.hc.util.HealthCheckMetadata; + +/** The result of executing a {@link HealthCheck}. */ +public class ExecutionResult implements Comparable<ExecutionResult>, HealthCheckExecutionResult { + + private final Result resultFromHC; + + private final HealthCheckMetadata metaData; + + private final Date finishedAt; + + private final long elapsedTimeInMs; + + private final boolean timedOut; + + /** Full constructor */ + public ExecutionResult(final HealthCheckMetadata metadata, + final Result simpleResult, + final long elapsedTimeInMs, + final boolean timedout) { + this.metaData = metadata; + this.resultFromHC = simpleResult; + this.finishedAt = new Date(); + this.timedOut = timedout; + this.elapsedTimeInMs = elapsedTimeInMs; + } + + /** Shortcut constructor for a result */ + public ExecutionResult(final HealthCheckMetadata metadata, + final Result simpleResult, + final long elapsedTimeInMs) { + this(metadata, simpleResult, elapsedTimeInMs, false); + } + + /** Shortcut constructor to create error/timed out result. */ + public ExecutionResult(final HealthCheckMetadata metadata, + final Result.Status status, + final String errorMessage, + final long elapsedTime, boolean timedOut) { + this(metadata, new Result(status, errorMessage), elapsedTime, timedOut); + } + + @Override + public Result getHealthCheckResult() { + return this.resultFromHC; + } + + @Override + public String toString() { + return "ExecutionResult [status=" + this.resultFromHC.getStatus() + + ", finishedAt=" + finishedAt + + ", elapsedTimeInMs=" + elapsedTimeInMs + + ", timedOut=" + timedOut + + "]"; + } + + @Override + public long getElapsedTimeInMs() { + return elapsedTimeInMs; + } + + @Override + public HealthCheckMetadata getHealthCheckMetadata() { + return this.metaData; + } + + @Override + public Date getFinishedAt() { + return finishedAt; + } + + @Override + public boolean hasTimedOut() { + return this.timedOut; + } + + /** Natural order of results (failed results are sorted before ok results). */ + @Override + public int compareTo(ExecutionResult otherResult) { + int retVal = otherResult.getHealthCheckResult().getStatus().compareTo(this.getHealthCheckResult().getStatus()); + if (retVal == 0) { + retVal = Collator.getInstance().compare(this.getHealthCheckMetadata().getTitle(), + otherResult.getHealthCheckMetadata().getTitle()); + } + return retVal; + } + + long getServiceId() { + return this.metaData.getServiceId(); + } +} \ No newline at end of file Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,29 @@ +/* + * 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 SF 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.core.impl.executor; + +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.execution.HealthCheckExecutionResult; +import org.apache.felix.hc.api.execution.HealthCheckExecutor; +import org.osgi.framework.ServiceReference; + +/** Internal service used by the JMX stuff */ +public interface ExtendedHealthCheckExecutor extends HealthCheckExecutor { + + HealthCheckExecutionResult execute(ServiceReference<HealthCheck> ref); +} \ No newline at end of file Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,467 @@ +/* + * 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 SF 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.core.impl.executor; + +import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS; +import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.RESULT_CACHE_TTL_DEFAULT_MS; +import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.TIMEOUT_DEFAULT_MS; +import static org.apache.felix.hc.util.FormattingResultLog.msHumanReadable; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.felix.hc.api.HealthCheck; +import org.apache.felix.hc.api.Result; +import org.apache.felix.hc.api.ResultLog; +import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions; +import org.apache.felix.hc.api.execution.HealthCheckExecutionResult; +import org.apache.felix.hc.api.execution.HealthCheckExecutor; +import org.apache.felix.hc.api.execution.HealthCheckSelector; +import org.apache.felix.hc.core.impl.executor.async.AsyncHealthCheckExecutor; +import org.apache.felix.hc.util.FormattingResultLog; +import org.apache.felix.hc.util.HealthCheckFilter; +import org.apache.felix.hc.util.HealthCheckMetadata; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +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.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Runs health checks for a given list of tags in parallel. */ +@Component(service = { HealthCheckExecutor.class, ExtendedHealthCheckExecutor.class }, immediate = true // immediate = true to keep the + // cache! +) +@Designate(ocd = HealthCheckExecutorImplConfiguration.class) +public class HealthCheckExecutorImpl implements ExtendedHealthCheckExecutor, ServiceListener { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private long timeoutInMs; + + private long longRunningFutureThresholdForRedMs; + + private long resultCacheTtlInMs; + + private HealthCheckResultCache healthCheckResultCache = new HealthCheckResultCache(); + + private final Map<HealthCheckMetadata, HealthCheckFuture> stillRunningFutures = new HashMap<HealthCheckMetadata, HealthCheckFuture>(); + + // optional dependency on quartz - if not present checks are just executed synchronously + @Reference + private AsyncHealthCheckExecutor asyncHealthCheckExecutor; + + @Reference + HealthCheckExecutorThreadPool healthCheckExecutorThreadPool; + + private BundleContext bundleContext; + + @Activate + protected final void activate(final HealthCheckExecutorImplConfiguration configuration, final BundleContext bundleContext) { + this.bundleContext = bundleContext; + + configure(configuration); + + try { + this.bundleContext.addServiceListener(this, "(" + + Constants.OBJECTCLASS + "=" + HealthCheck.class.getName() + ")"); + } catch (final InvalidSyntaxException ise) { + // this should really never happen as the expression above is constant + throw new RuntimeException("Unexpected exception occured.", ise); + } + } + + @Modified + protected final void modified(final HealthCheckExecutorImplConfiguration configuration) { + configure(configuration); + } + + @Deactivate + protected final void deactivate() { + this.bundleContext.removeServiceListener(this); + this.bundleContext = null; + this.healthCheckResultCache.clear(); + } + + protected final void configure(final HealthCheckExecutorImplConfiguration configuration) { + this.timeoutInMs = configuration.timeoutInMs(); + if (this.timeoutInMs <= 0L) { + this.timeoutInMs = TIMEOUT_DEFAULT_MS; + } + + this.longRunningFutureThresholdForRedMs = configuration.longRunningFutureThresholdForCriticalMs(); + if (this.longRunningFutureThresholdForRedMs <= 0L) { + this.longRunningFutureThresholdForRedMs = LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS; + } + + this.resultCacheTtlInMs = configuration.resultCacheTtlInMs(); + if (this.resultCacheTtlInMs <= 0L) { + this.resultCacheTtlInMs = RESULT_CACHE_TTL_DEFAULT_MS; + } + + } + + @Override + public void serviceChanged(final ServiceEvent event) { + if (event.getType() == ServiceEvent.UNREGISTERING) { + final Long serviceId = (Long) event.getServiceReference().getProperty(Constants.SERVICE_ID); + this.healthCheckResultCache.removeCachedResult(serviceId); + } + } + + @Override + public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector) { + return execute(selector, new HealthCheckExecutionOptions()); + } + + @Override + public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector, HealthCheckExecutionOptions options) { + logger.debug("Starting executing checks for filter selector {} and execution options {}", selector, options); + + final HealthCheckFilter filter = new HealthCheckFilter(this.bundleContext); + try { + final ServiceReference<HealthCheck>[] healthCheckReferences = filter.getHealthCheckServiceReferences(selector, + options.isCombineTagsWithOr()); + + return this.execute(healthCheckReferences, options); + } finally { + filter.dispose(); + } + } + + /** @see org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor#execute(org.osgi.framework.ServiceReference) */ + @Override + public HealthCheckExecutionResult execute(final ServiceReference<HealthCheck> ref) { + final HealthCheckMetadata metadata = this.getHealthCheckMetadata(ref); + return createResultsForDescriptor(metadata); + } + + /** Execute a set of health checks */ + private List<HealthCheckExecutionResult> execute(final ServiceReference<HealthCheck>[] healthCheckReferences, + HealthCheckExecutionOptions options) { + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + final List<HealthCheckExecutionResult> results = new ArrayList<HealthCheckExecutionResult>(); + final List<HealthCheckMetadata> healthCheckDescriptors = getHealthCheckMetadata(healthCheckReferences); + + createResultsForDescriptors(healthCheckDescriptors, results, options); + + stopWatch.stop(); + if (logger.isDebugEnabled()) { + logger.debug("Time consumed for all checks: {}", msHumanReadable(stopWatch.getTime())); + } + // sort result + Collections.sort(results, new Comparator<HealthCheckExecutionResult>() { + + @Override + public int compare(final HealthCheckExecutionResult arg0, + final HealthCheckExecutionResult arg1) { + return ((ExecutionResult) arg0).compareTo((ExecutionResult) arg1); + } + + }); + return results; + } + + private void createResultsForDescriptors(final List<HealthCheckMetadata> healthCheckDescriptors, + final List<HealthCheckExecutionResult> results, HealthCheckExecutionOptions options) { + // -- All methods below check if they can transform a healthCheckDescriptor into a result + // -- if yes the descriptor is removed from the list and the result added + + // get async results + if (!options.isForceInstantExecution()) { + if (asyncHealthCheckExecutor != null) { + asyncHealthCheckExecutor.collectAsyncResults(healthCheckDescriptors, results, healthCheckResultCache); + } else { + for (HealthCheckMetadata hcDescriptior : healthCheckDescriptors) { + if (StringUtils.isNotBlank(hcDescriptior.getAsyncCronExpression())) { + logger.warn( + "Health check '{}' is configured for asynchronous execution, but async executor is not available (quartz bundle missing)", + hcDescriptior.getName()); + } + } + } + } + + // reuse cached results where possible + if (!options.isForceInstantExecution()) { + healthCheckResultCache.useValidCacheResults(healthCheckDescriptors, results, resultCacheTtlInMs); + } + + // everything else is executed in parallel via futures + List<HealthCheckFuture> futures = createOrReuseFutures(healthCheckDescriptors); + + // wait for futures at most until timeout (but will return earlier if all futures are finished) + waitForFuturesRespectingTimeout(futures, options); + collectResultsFromFutures(futures, results); + + // respect sticky results if configured via HealthCheck.WARNINGS_STICK_FOR_MINUTES + appendStickyResultLogIfConfigured(results); + + } + + private void appendStickyResultLogIfConfigured(List<HealthCheckExecutionResult> results) { + ListIterator<HealthCheckExecutionResult> resultsIt = results.listIterator(); + while (resultsIt.hasNext()) { + HealthCheckExecutionResult result = resultsIt.next(); + Long warningsStickForMinutes = result.getHealthCheckMetadata().getWarningsStickForMinutes(); + if (warningsStickForMinutes != null && warningsStickForMinutes > 0) { + result = healthCheckResultCache.createExecutionResultWithStickyResults(result); + resultsIt.set(result); + } + } + } + + private HealthCheckExecutionResult createResultsForDescriptor(final HealthCheckMetadata metadata) { + // create result for a single descriptor + + // reuse cached results where possible + HealthCheckExecutionResult result; + + result = healthCheckResultCache.getValidCacheResult(metadata, resultCacheTtlInMs); + + if (result == null) { + final HealthCheckFuture future; + synchronized (this.stillRunningFutures) { + future = createOrReuseFuture(metadata); + } + + // wait for futures at most until timeout (but will return earlier if all futures are finished) + waitForFuturesRespectingTimeout(Collections.singletonList(future), null); + result = collectResultFromFuture(future); + } + + return result; + } + + /** Create the health check meta data */ + private List<HealthCheckMetadata> getHealthCheckMetadata(final ServiceReference... healthCheckReferences) { + final List<HealthCheckMetadata> descriptors = new LinkedList<HealthCheckMetadata>(); + for (final ServiceReference serviceReference : healthCheckReferences) { + final HealthCheckMetadata descriptor = getHealthCheckMetadata(serviceReference); + + descriptors.add(descriptor); + } + + return descriptors; + } + + /** Create the health check meta data */ + private HealthCheckMetadata getHealthCheckMetadata(final ServiceReference healthCheckReference) { + final HealthCheckMetadata descriptor = new HealthCheckMetadata(healthCheckReference); + return descriptor; + } + + /** Create or reuse future for the list of health checks */ + private List<HealthCheckFuture> createOrReuseFutures(final List<HealthCheckMetadata> healthCheckDescriptors) { + final List<HealthCheckFuture> futuresForResultOfThisCall = new LinkedList<HealthCheckFuture>(); + + synchronized (this.stillRunningFutures) { + for (final HealthCheckMetadata md : healthCheckDescriptors) { + + futuresForResultOfThisCall.add(createOrReuseFuture(md)); + + } + } + return futuresForResultOfThisCall; + } + + /** Create or reuse future for the health check This method must be synchronized by the caller(!) on stillRunningFutures */ + private HealthCheckFuture createOrReuseFuture(final HealthCheckMetadata metadata) { + HealthCheckFuture future = this.stillRunningFutures.get(metadata); + if (future != null) { + logger.debug("Found a future that is still running for {}", metadata); + } else { + logger.debug("Creating future for {}", metadata); + future = new HealthCheckFuture(metadata, bundleContext, new HealthCheckFuture.Callback() { + + @Override + public void finished(final HealthCheckExecutionResult result) { + healthCheckResultCache.updateWith(result); + asyncHealthCheckExecutor.updateWith(result); + synchronized (stillRunningFutures) { + stillRunningFutures.remove(metadata); + } + } + }); + this.stillRunningFutures.put(metadata, future); + + final HealthCheckFuture newFuture = future; + + healthCheckExecutorThreadPool.execute(new Runnable() { + @Override + public void run() { + newFuture.run(); + synchronized (stillRunningFutures) { + // notify executor threads that newFuture is finished. Wrapping it in another runnable + // ensures that newFuture.isDone() will return true (if e.g. done in callback above, there are + // still a few lines of code until the future is really done and hence then the executor thread + // is sometime notified a bit too early, still receives the result isDone()=false and then waits + // for another 50ms, even though the future was about to be done one ms later) + stillRunningFutures.notifyAll(); + } + } + }); + } + + return future; + } + + /** Wait for the futures until the timeout is reached */ + private void waitForFuturesRespectingTimeout(final List<HealthCheckFuture> futuresForResultOfThisCall, + HealthCheckExecutionOptions options) { + final StopWatch callExcutionTimeStopWatch = new StopWatch(); + callExcutionTimeStopWatch.start(); + boolean allFuturesDone; + + long effectiveTimeout = this.timeoutInMs; + if (options != null && options.getOverrideGlobalTimeout() > 0) { + effectiveTimeout = options.getOverrideGlobalTimeout(); + } + + if (futuresForResultOfThisCall.isEmpty()) { + return; // nothing to wait for (usually because of cached results) + } + + do { + try { + synchronized (stillRunningFutures) { + stillRunningFutures.wait(50); // wait for notifications of callbacks of HealthCheckFutures + } + } catch (final InterruptedException ie) { + logger.warn("Unexpected InterruptedException while waiting for healthCheckContributors", ie); + } + + allFuturesDone = true; + for (final HealthCheckFuture healthCheckFuture : futuresForResultOfThisCall) { + allFuturesDone &= healthCheckFuture.isDone(); + } + } while (!allFuturesDone && callExcutionTimeStopWatch.getTime() < effectiveTimeout); + } + + /** Collect the results from all futures + * + * @param futuresForResultOfThisCall The list of futures + * @param results The result collection */ + void collectResultsFromFutures(final List<HealthCheckFuture> futuresForResultOfThisCall, + final Collection<HealthCheckExecutionResult> results) { + + final Set<HealthCheckExecutionResult> resultsFromFutures = new HashSet<HealthCheckExecutionResult>(); + + final Iterator<HealthCheckFuture> futuresIt = futuresForResultOfThisCall.iterator(); + while (futuresIt.hasNext()) { + final HealthCheckFuture future = futuresIt.next(); + final HealthCheckExecutionResult result = this.collectResultFromFuture(future); + + resultsFromFutures.add(result); + futuresIt.remove(); + } + + logger.debug("Adding {} results from futures", resultsFromFutures.size()); + results.addAll(resultsFromFutures); + } + + /** Collect the result from a single future + * + * @param future The future + * @return The execution result or a result for a reached timeout */ + HealthCheckExecutionResult collectResultFromFuture(final HealthCheckFuture future) { + + HealthCheckExecutionResult result; + HealthCheckMetadata hcMetadata = future.getHealthCheckMetadata(); + if (future.isDone()) { + logger.debug("Health Check is done: {}", hcMetadata); + + try { + result = future.get(); + } catch (final Exception e) { + logger.warn("Unexpected Exception during future.get(): " + e, e); + long futureElapsedTimeMs = new Date().getTime() - future.getCreatedTime().getTime(); + result = new ExecutionResult(hcMetadata, Result.Status.HEALTH_CHECK_ERROR, + "Unexpected Exception during future.get(): " + e, futureElapsedTimeMs, false); + } + + } else { + logger.debug("Health Check timed out: {}", hcMetadata); + // Futures must not be cancelled as interrupting a health check might leave the system in invalid state + // (worst case could be a corrupted repository index if using write operations) + + // normally we turn the check into WARN (normal timeout), but if the threshold time for CRITICAL is reached for a certain + // future we turn the result CRITICAL + long futureElapsedTimeMs = new Date().getTime() - future.getCreatedTime().getTime(); + FormattingResultLog resultLog = new FormattingResultLog(); + if (futureElapsedTimeMs < this.longRunningFutureThresholdForRedMs) { + resultLog.warn("Timeout: Check still running after " + msHumanReadable(futureElapsedTimeMs)); + } else { + resultLog.critical("Timeout: Check still running after " + msHumanReadable(futureElapsedTimeMs) + + " (exceeding the configured threshold for CRITICAL: " + + msHumanReadable(this.longRunningFutureThresholdForRedMs) + ")"); + } + + // add logs from previous, cached result if exists (using a 1 year TTL) + HealthCheckExecutionResult lastCachedResult = healthCheckResultCache.getValidCacheResult(hcMetadata, 1000 * 60 * 60 * 24 * 365); + if (lastCachedResult != null) { + DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS"); + resultLog.info("*** Result log of last execution finished at {} after {} ***", + df.format(lastCachedResult.getFinishedAt()), + FormattingResultLog.msHumanReadable(lastCachedResult.getElapsedTimeInMs())); + for (ResultLog.Entry entry : lastCachedResult.getHealthCheckResult()) { + resultLog.add(entry); + } + } + + result = new ExecutionResult(hcMetadata, new Result(resultLog), futureElapsedTimeMs, true); + + } + + return result; + } + + public void setTimeoutInMs(final long timeoutInMs) { + this.timeoutInMs = timeoutInMs; + } + + public void setLongRunningFutureThresholdForRedMs( + final long longRunningFutureThresholdForRedMs) { + this.longRunningFutureThresholdForRedMs = longRunningFutureThresholdForRedMs; + } +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,40 @@ +/* + * 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 SF 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.core.impl.executor; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(name = "Apache Felix Health Check Executor", description = "Runs health checks for a given list of tags in parallel.") +@interface HealthCheckExecutorImplConfiguration { + + long TIMEOUT_DEFAULT_MS = 2000L; + + long LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS = 1000L * 60 * 5; + + long RESULT_CACHE_TTL_DEFAULT_MS = 1000L * 2; + + @AttributeDefinition(name = "Timeout", description = "Timeout in ms until a check is marked as timed out") + long timeoutInMs() default TIMEOUT_DEFAULT_MS; + + @AttributeDefinition(name = "Timeout threshold for CRITICAL", description = "Threshold in ms until a check is marked as 'exceedingly' timed out and will marked CRITICAL instead of WARN only") + long longRunningFutureThresholdForCriticalMs() default LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS; + + @AttributeDefinition(name = "Results Cache TTL in Ms", description = "Result Cache time to live - results will be cached for the given time") + long resultCacheTtlInMs() default RESULT_CACHE_TTL_DEFAULT_MS; +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java Tue Dec 18 22:58:15 2018 @@ -0,0 +1,106 @@ +/* + * 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 SF 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.core.impl.executor; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +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.Deactivate; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Creates a thread pool via standard java.util.concurrent package to be used for parallel execution of health checks in + * HealthCheckExecutorImpl and AsyncHealthCheckExecutor */ +@Component(service = { HealthCheckExecutorThreadPool.class }) +@Designate(ocd = HealthCheckExecutorThreadPoolConfiguration.class) +public class HealthCheckExecutorThreadPool { + private final static Logger LOG = LoggerFactory.getLogger(HealthCheckExecutorThreadPool.class); + + private int threadPoolSize; + + private ScheduledThreadPoolExecutor executor; + + @Activate + protected final void activate(final HealthCheckExecutorThreadPoolConfiguration configuration, final BundleContext bundleContext) { + + this.threadPoolSize = configuration.threadPoolSize(); + + executor = new ScheduledThreadPoolExecutor(threadPoolSize, new HcThreadFactory(), new HcRejectedExecutionHandler()); + + LOG.info("Created HC Thread Pool: threadPoolSize={}", threadPoolSize); + + } + + @Deactivate + protected final void deactivate() { + executor.shutdown(); + } + + // Method called by HealthCheckExecutorImpl (regular synchronous checks) + public void execute(final Runnable job) { + this.executor.execute(job); + } + + // used for interval execution (asynchronous checks) + public ScheduledFuture<?> scheduleAtFixedRate(final Runnable job, long intervalInSec) { + ScheduledFuture<?> scheduleFuture = executor.scheduleAtFixedRate(job, 0, intervalInSec, TimeUnit.SECONDS); + return scheduleFuture; + } + + // methods below are used by AsyncHealthCheckExecutor.QuartzThreadPool + public int getPoolSize() { + return this.executor.getPoolSize(); + } + + public int getMaxCurrentlyAvailableThreads() { + return this.threadPoolSize - executor.getQueue().size(); + } + + static class HcThreadFactory implements ThreadFactory { + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + + HcThreadFactory() { + group = Thread.currentThread().getThreadGroup(); + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, "hc-thread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); // using daemon thread to not delay JVM shutdown (HC status is non-transactional and only in memory) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } + + private final class HcRejectedExecutionHandler implements RejectedExecutionHandler { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + LOG.warn("Thread Pool {} rejected to run runnable {}", executor, r); + } + } + +} Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java?rev=1849246&view=auto ============================================================================== --- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java (added) +++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java Tue Dec 18 22:58:15 2018 @@ -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 SF 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.core.impl.executor; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(name = "Apache Felix Health Check Executor Thread Pool", description = "Runs health checks for a given list of tags in parallel.") +@interface HealthCheckExecutorThreadPoolConfiguration { + + int THREAD_POOL_SIZE_DEFAULT = 25; + + @AttributeDefinition(name = "Thread Pool Size", description = "Number of threads to be used for parallel health check execution") + int threadPoolSize() default THREAD_POOL_SIZE_DEFAULT; + +}
