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 2018/12/18 22:51:49 UTC
svn commit: r1849243 [2/5] - in /felix/trunk: api/ api/src/ api/src/main/
api/src/main/java/ api/src/main/java/org/ api/src/main/java/org/apache/
api/src/main/java/org/apache/felix/ api/src/main/java/org/apache/felix/hc/
api/src/main/java/org/apache/fe...
Added: felix/trunk/core/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/core/pom.xml?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/pom.xml (added)
+++ felix/trunk/core/pom.xml Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java Tue Dec 18 22:51:48 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/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java Tue Dec 18 22:51:48 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;
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,145 @@
+/*
+ * 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.util.FormattingResultLog.msHumanReadable;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.commons.lang3.reflect.MethodUtils;
+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.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Future to be able to schedule a health check for parallel execution. */
+public class HealthCheckFuture extends FutureTask<ExecutionResult> {
+
+ public interface Callback {
+ public void finished(final HealthCheckExecutionResult result);
+ }
+
+ private final static Logger LOG = LoggerFactory.getLogger(HealthCheckFuture.class);
+
+ private final HealthCheckMetadata metadata;
+ private final Date createdTime;
+
+ public HealthCheckFuture(final HealthCheckMetadata metadata, final BundleContext bundleContext, final Callback callback) {
+ super(new Callable<ExecutionResult>() {
+ @Override
+ public ExecutionResult call() throws Exception {
+ Thread.currentThread().setName("HealthCheck " + metadata.getTitle());
+ LOG.debug("Starting check {}", metadata);
+
+ final StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+ Result resultFromHealthCheck = null;
+ ExecutionResult executionResult = null;
+
+ Object healthCheck = bundleContext.getService(metadata.getServiceReference());
+ try {
+ if (healthCheck != null) {
+ if ((healthCheck instanceof HealthCheck)) {
+ resultFromHealthCheck = ((HealthCheck) healthCheck).execute();
+ } else {
+ resultFromHealthCheck = executeLegacyHc(healthCheck);
+ }
+ } else {
+ throw new IllegalStateException("Service for " + metadata + " is gone");
+ }
+
+ } catch (final Exception e) {
+ resultFromHealthCheck = new Result(Result.Status.CRITICAL,
+ "Exception during execution of '" + metadata.getName() + "': " + e, e);
+ } finally {
+ // unget service ref
+ bundleContext.ungetService(metadata.getServiceReference());
+
+ // update result with information about this run
+ stopWatch.stop();
+ long elapsedTime = stopWatch.getTime();
+ if (resultFromHealthCheck != null) {
+ // wrap the result in an execution result
+ executionResult = new ExecutionResult(metadata, resultFromHealthCheck, elapsedTime);
+ }
+ LOG.debug("Time consumed for {}: {}", metadata, msHumanReadable(elapsedTime));
+ }
+
+ callback.finished(executionResult);
+ Thread.currentThread().setName("HealthCheck-idle");
+ return executionResult;
+ }
+
+ });
+ this.createdTime = new Date();
+ this.metadata = metadata;
+
+ }
+
+ Date getCreatedTime() {
+ return this.createdTime;
+ }
+
+ public HealthCheckMetadata getHealthCheckMetadata() {
+ return metadata;
+ }
+
+ @Override
+ public String toString() {
+ return "[Future for " + this.metadata + ", createdTime=" + this.createdTime + "]";
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static Result executeLegacyHc(Object healthCheck) {
+
+ FormattingResultLog log = new FormattingResultLog();
+ log.debug("Running legacy HC {}, please convert to new interface org.apache.felix.hc.api.HealthCheck!",
+ healthCheck.getClass().getName());
+ try {
+ Object result = MethodUtils.invokeMethod(healthCheck, "execute");
+ Object resultLog = FieldUtils.readField(result, "resultLog", true);
+
+ List entries = (List) FieldUtils.readField(resultLog, "entries", true);
+ for (Object object : entries) {
+ String statusLegacy = String.valueOf(FieldUtils.readField(object, "status", true));
+ String message = (String) FieldUtils.readField(object, "message", true);
+ Exception exception = (Exception) FieldUtils.readField(object, "exception", true);
+ if(statusLegacy.equals("DEBUG")) {
+ log.add(new ResultLog.Entry(message, true, exception));
+ } else {
+ statusLegacy = statusLegacy.replace("INFO", "OK");
+ log.add(new ResultLog.Entry(Result.Status.valueOf(statusLegacy), message, exception));
+ }
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ log.healthCheckError("Could call and convert Sling HC {} for Felix Runtime", healthCheck.getClass().getName());
+ }
+ return new Result(log);
+ }
+}