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:58:17 UTC
svn commit: r1849246 [4/6] - in /felix/trunk/healthcheck: ./ 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/jav...
Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,138 @@
+/*
+ * 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.servlet;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.text.WordUtils;
+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.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Serializes health check results into a verbose text message. */
+@Component(service = ResultTxtVerboseSerializer.class)
+public class ResultTxtVerboseSerializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResultTxtVerboseSerializer.class);
+
+ private static final String NEWLINE = "\n"; // not using system prop 'line.separator' as not the local but the calling system is
+ // relevant.
+
+ private int totalWidth;
+
+ private int colWidthName;
+
+ private int colWidthResult;
+
+ private int colWidthTiming;
+
+ private int colWidthWithoutLog;
+ private int colWidthLog;
+
+ @Activate
+ protected final void activate(final ResultTxtVerboseSerializerConfiguration configuration) {
+ this.totalWidth = configuration.totalWidth();
+ this.colWidthName = configuration.colWidthName();
+ this.colWidthResult = configuration.colWidthResult();
+ this.colWidthTiming = configuration.colWidthTiming();
+ colWidthWithoutLog = colWidthName + colWidthResult + colWidthTiming;
+ colWidthLog = totalWidth - colWidthWithoutLog;
+ }
+
+ public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) {
+
+ LOG.debug("Sending verbose txt response... ");
+
+ StringBuilder resultStr = new StringBuilder();
+
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.center("Overall Health Result: " + overallResult.getStatus().toString(), totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.rightPad("Name", colWidthName));
+ resultStr.append(StringUtils.rightPad("Result", colWidthResult));
+ resultStr.append(StringUtils.rightPad("Timing", colWidthTiming));
+ resultStr.append("Logs" + NEWLINE);
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+
+ final DateFormat dfShort = new SimpleDateFormat("HH:mm:ss.SSS");
+
+ for (HealthCheckExecutionResult healthCheckResult : executionResults) {
+ appendVerboseTxtForResult(resultStr, healthCheckResult, includeDebug, dfShort);
+ }
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+
+ return resultStr.toString();
+
+ }
+
+ private void appendVerboseTxtForResult(StringBuilder resultStr, HealthCheckExecutionResult healthCheckResult, boolean includeDebug,
+ DateFormat dfShort) {
+
+ String wrappedName = WordUtils.wrap(healthCheckResult.getHealthCheckMetadata().getName(), colWidthName);
+
+ String relevantNameStringForPadding = StringUtils.contains(wrappedName, "\n") ? StringUtils.substringAfterLast(wrappedName, "\n")
+ : wrappedName;
+ int paddingSize = colWidthName - relevantNameStringForPadding.length();
+
+ resultStr.append(wrappedName + StringUtils.repeat(" ", paddingSize));
+ resultStr.append(StringUtils.rightPad(healthCheckResult.getHealthCheckResult().getStatus().toString(), colWidthResult));
+ resultStr.append(StringUtils.rightPad("[" + dfShort.format(healthCheckResult.getFinishedAt())
+ + "|" + FormattingResultLog.msHumanReadable(healthCheckResult.getElapsedTimeInMs()) + "]", colWidthTiming));
+
+ boolean isFirst = true;
+ for (ResultLog.Entry logEntry : healthCheckResult.getHealthCheckResult()) {
+ if (!includeDebug && logEntry.isDebug()) {
+ continue;
+ }
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ resultStr.append(StringUtils.repeat(" ", colWidthWithoutLog));
+ }
+
+ String oneLineMessage = getStatusForTxtLog(logEntry) + logEntry.getMessage();
+ String messageToPrint = WordUtils.wrap(oneLineMessage, colWidthLog, "\n" + StringUtils.repeat(" ", colWidthWithoutLog), true);
+
+ resultStr.append(messageToPrint);
+ resultStr.append(NEWLINE);
+ }
+
+ if (isFirst) {
+ // no log entry exists, ensure newline
+ resultStr.append(NEWLINE);
+ }
+
+ }
+
+ private String getStatusForTxtLog(ResultLog.Entry logEntry) {
+ if (logEntry.getStatus() == Result.Status.OK) {
+ return "";
+ } else {
+ return logEntry.getStatus().toString() + " ";
+ }
+ }
+
+}
Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,38 @@
+/*
+ * 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.servlet;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Verbose Text Serializer", description = "Serializes health check results to a verbose text format")
+@interface ResultTxtVerboseSerializerConfiguration {
+
+ @AttributeDefinition(name = "Total Width", description = "Total width of all columns in verbose txt rendering (in characters)")
+ int totalWidth() default 140;
+
+ @AttributeDefinition(name = "Name Column Width", description = "Column width of health check name (in characters)")
+ int colWidthName() default 30;
+
+ @AttributeDefinition(name = "Result Column Width", description = "Column width of health check result (in characters)")
+ int colWidthResult() default 9;
+
+ @AttributeDefinition(name = "Timing Column Width", description = "Column width of health check timing (in characters)")
+ int colWidthTiming() default 22;
+
+}
Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBean.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBean.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBean.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBean.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,258 @@
+/*
+ * 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.jmx.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.ReflectionException;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+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.core.impl.executor.ExtendedHealthCheckExecutor;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/** A {@link DynamicMBean} used to execute a {@link HealthCheck} service */
+public class HealthCheckMBean implements DynamicMBean {
+
+ private static final String HC_OK_ATTRIBUTE_NAME = "ok";
+ private static final String HC_STATUS_ATTRIBUTE_NAME = "status";
+ private static final String HC_LOG_ATTRIBUTE_NAME = "log";
+ private static final String HC_TIMED_OUT_ATTRIBUTE_NAME = "timedOut";
+ private static final String HC_ELAPSED_TIMED_ATTRIBUTE_NAME = "elapsedTime";
+ private static final String HC_FINISHED_AT_ATTRIBUTE_NAME = "finishedAt";
+ private static CompositeType LOG_ROW_TYPE;
+ private static TabularType LOG_TABLE_TYPE;
+
+ private static final String INDEX_COLUMN = "index";
+ private static final String LEVEL_COLUMN = "level";
+ private static final String MESSAGE_COLUMN = "message";
+
+ /** The health check service to call. */
+ private final ServiceReference<HealthCheck> healthCheckRef;
+
+ /** The executor service. */
+ private final ExtendedHealthCheckExecutor executor;
+
+ /** The mbean info. */
+ private final MBeanInfo mbeanInfo;
+
+ /** The default attributes. */
+ private final Map<String, Object> defaultAttributes;
+
+ static {
+ try {
+ // Define the log row and table types
+ LOG_ROW_TYPE = new CompositeType(
+ "LogLine",
+ "A line in the result log",
+ new String[] { INDEX_COLUMN, LEVEL_COLUMN, MESSAGE_COLUMN },
+ new String[] { "log line index", "log level", "log message" },
+ new OpenType[] { SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING });
+ final String[] indexes = { INDEX_COLUMN };
+ LOG_TABLE_TYPE = new TabularType("LogTable", "Result log messages", LOG_ROW_TYPE, indexes);
+ } catch (Exception ignore) {
+ // row or table type will be null if this happens
+ }
+ }
+
+ public HealthCheckMBean(final ServiceReference<HealthCheck> ref, final ExtendedHealthCheckExecutor executor) {
+ this.healthCheckRef = ref;
+ this.executor = executor;
+ this.mbeanInfo = this.createMBeanInfo(ref);
+ this.defaultAttributes = this.createDefaultAttributes(ref);
+ }
+
+ @Override
+ public Object getAttribute(final String attribute)
+ throws AttributeNotFoundException, MBeanException, ReflectionException {
+ // we should call getAttributes - and not vice versa to have the result
+ // of a single check call - and not do a check call for each attribute
+ final AttributeList result = this.getAttributes(new String[] { attribute });
+ if (result.size() == 0) {
+ throw new AttributeNotFoundException(attribute);
+ }
+ final Attribute attr = (Attribute) result.get(0);
+ return attr.getValue();
+ }
+
+ private TabularData logData(final Result er) throws OpenDataException {
+ final TabularDataSupport result = new TabularDataSupport(LOG_TABLE_TYPE);
+ int i = 1;
+ for (final ResultLog.Entry e : er) {
+ final Map<String, Object> data = new HashMap<String, Object>();
+ data.put(INDEX_COLUMN, i++);
+ data.put(LEVEL_COLUMN, e.getStatus().toString());
+ data.put(MESSAGE_COLUMN, e.getMessage());
+
+ result.put(new CompositeDataSupport(LOG_ROW_TYPE, data));
+ }
+ return result;
+ }
+
+ @Override
+ public AttributeList getAttributes(final String[] attributes) {
+ final AttributeList result = new AttributeList();
+ if (attributes != null) {
+ HealthCheckExecutionResult hcResult = null;
+ for (final String key : attributes) {
+ final Object defaultValue = this.defaultAttributes.get(key);
+ if (defaultValue != null) {
+ result.add(new Attribute(key, defaultValue));
+ } else {
+ // we assume that a valid attribute name is used
+ // which is requesting a hc result
+ if (hcResult == null) {
+ hcResult = this.getHealthCheckResult();
+ }
+
+ if (HC_OK_ATTRIBUTE_NAME.equals(key)) {
+ result.add(new Attribute(key, hcResult.getHealthCheckResult().isOk()));
+ } else if (HC_LOG_ATTRIBUTE_NAME.equals(key)) {
+ try {
+ result.add(new Attribute(key, logData(hcResult.getHealthCheckResult())));
+ } catch (final OpenDataException ignore) {
+ // we ignore this and simply don't add the attribute
+ }
+ } else if (HC_STATUS_ATTRIBUTE_NAME.equals(key)) {
+ result.add(new Attribute(key, hcResult.getHealthCheckResult().getStatus().toString()));
+ } else if (HC_ELAPSED_TIMED_ATTRIBUTE_NAME.equals(key)) {
+ result.add(new Attribute(key, hcResult.getElapsedTimeInMs()));
+ } else if (HC_FINISHED_AT_ATTRIBUTE_NAME.equals(key)) {
+ result.add(new Attribute(key, hcResult.getFinishedAt()));
+ } else if (HC_TIMED_OUT_ATTRIBUTE_NAME.equals(key)) {
+ result.add(new Attribute(key, hcResult.hasTimedOut()));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /** Create the mbean info */
+ private MBeanInfo createMBeanInfo(final ServiceReference<HealthCheck> serviceReference) {
+ final List<MBeanAttributeInfo> attrs = new ArrayList<MBeanAttributeInfo>();
+
+ // add relevant service properties
+ if (serviceReference.getProperty(HealthCheck.NAME) != null) {
+ attrs.add(new MBeanAttributeInfo(HealthCheck.NAME, String.class.getName(), "The name of the health check service.", true, false,
+ false));
+ }
+ if (serviceReference.getProperty(HealthCheck.TAGS) != null) {
+ attrs.add(new MBeanAttributeInfo(HealthCheck.TAGS, String.class.getName(), "The tags of the health check service.", true, false,
+ false));
+ }
+
+ // add standard attributes
+ attrs.add(new MBeanAttributeInfo(HC_OK_ATTRIBUTE_NAME, Boolean.class.getName(), "The health check result", true, false, false));
+ attrs.add(new MBeanAttributeInfo(HC_STATUS_ATTRIBUTE_NAME, String.class.getName(), "The health check status", true, false, false));
+ attrs.add(new MBeanAttributeInfo(HC_ELAPSED_TIMED_ATTRIBUTE_NAME, Long.class.getName(), "The elapsed time in miliseconds", true,
+ false, false));
+ attrs.add(new MBeanAttributeInfo(HC_FINISHED_AT_ATTRIBUTE_NAME, Date.class.getName(), "The date when the execution finished", true,
+ false, false));
+ attrs.add(new MBeanAttributeInfo(HC_TIMED_OUT_ATTRIBUTE_NAME, Boolean.class.getName(), "Indicates of the execution timed out", true,
+ false, false));
+ attrs.add(new OpenMBeanAttributeInfoSupport(HC_LOG_ATTRIBUTE_NAME, "The health check result log", LOG_TABLE_TYPE, true, false,
+ false));
+
+ final String description;
+ if (serviceReference.getProperty(Constants.SERVICE_DESCRIPTION) != null) {
+ description = serviceReference.getProperty(Constants.SERVICE_DESCRIPTION).toString();
+ } else {
+ description = "Health check";
+ }
+ return new MBeanInfo(this.getClass().getName(),
+ description,
+ attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null, null, null);
+ }
+
+ /** Create the default attributes. */
+ private Map<String, Object> createDefaultAttributes(final ServiceReference<HealthCheck> serviceReference) {
+ final Map<String, Object> list = new HashMap<String, Object>();
+ if (serviceReference.getProperty(HealthCheck.NAME) != null) {
+ list.put(HealthCheck.NAME, serviceReference.getProperty(HealthCheck.NAME).toString());
+ }
+ if (serviceReference.getProperty(HealthCheck.TAGS) != null) {
+ final Object value = serviceReference.getProperty(HealthCheck.TAGS);
+ if (value instanceof String[]) {
+ list.put(HealthCheck.TAGS, Arrays.toString((String[]) value));
+ } else {
+ list.put(HealthCheck.TAGS, value.toString());
+ }
+ }
+
+ return list;
+ }
+
+ @Override
+ public MBeanInfo getMBeanInfo() {
+ return this.mbeanInfo;
+ }
+
+ @Override
+ public Object invoke(final String actionName, final Object[] params, final String[] signature)
+ throws MBeanException, ReflectionException {
+ throw new MBeanException(new UnsupportedOperationException(getClass().getSimpleName() + " does not support operations."));
+ }
+
+ @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();
+ }
+
+ @Override
+ public String toString() {
+ return "HealthCheckMBean [healthCheck=" + this.healthCheckRef + "]";
+ }
+
+ private HealthCheckExecutionResult getHealthCheckResult() {
+ return this.executor.execute(this.healthCheckRef);
+ }
+}
\ No newline at end of file
Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanCreator.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanCreator.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanCreator.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanCreator.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,185 @@
+/*
+ * 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.jmx.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.DynamicMBean;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Creates an {@link HealthCheckMBean} for every {@link HealthCheckMBean} service */
+@Component
+public class HealthCheckMBeanCreator {
+
+ private static final String JMX_TYPE_NAME = "HealthCheck";
+ private static final String JMX_DOMAIN = "org.apache.felix.healthcheck";
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final Map<ServiceReference<HealthCheck>, Registration> registeredServices = new HashMap<>();
+
+ private final Map<String, List<ServiceReference<HealthCheck>>> sortedRegistrations = new HashMap<>();
+
+ private ServiceTracker<HealthCheck, ?> hcTracker;
+
+ @Reference
+ private ExtendedHealthCheckExecutor executor;
+
+ @Activate
+ protected void activate(final BundleContext btx) {
+ this.hcTracker = new ServiceTracker<HealthCheck, Object>(btx, HealthCheck.class, null) {
+
+ @Override
+ public Object addingService(final ServiceReference<HealthCheck> reference) {
+ return registerHCMBean(btx, reference);
+ }
+
+ @Override
+ public void modifiedService(final ServiceReference<HealthCheck> reference,
+ final Object service) {
+ unregisterHCMBean(btx, reference);
+ registerHCMBean(btx, reference);
+ }
+
+ @Override
+ public void removedService(final ServiceReference<HealthCheck> reference,
+ final Object service) {
+ unregisterHCMBean(btx, reference);
+ }
+ };
+ this.hcTracker.open();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.hcTracker != null) {
+ this.hcTracker.close();
+ this.hcTracker = null;
+ }
+ }
+
+ /** Register an mbean for a health check service. The mbean is only registered if - the service has an mbean registration property - if
+ * there is no other service with the same name but a higher service ranking
+ *
+ * @param bundleContext The bundle context
+ * @param reference The service reference to the health check service
+ * @return The registered mbean or <code>null</code> */
+ private synchronized Object registerHCMBean(final BundleContext bundleContext, final ServiceReference<HealthCheck> reference) {
+ final Registration reg = getRegistration(reference);
+ if (reg != null) {
+ this.registeredServices.put(reference, reg);
+
+ List<ServiceReference<HealthCheck>> registered = this.sortedRegistrations.get(reg.name);
+ if (registered == null) {
+ registered = new ArrayList<>();
+ this.sortedRegistrations.put(reg.name, registered);
+ }
+ registered.add(reference);
+ // sort orders the references with lowest ranking first
+ // we want the highest!
+ Collections.sort(registered);
+ final int lastIndex = registered.size() - 1;
+ if (registered.get(lastIndex).equals(reference)) {
+ if (registered.size() > 1) {
+ final ServiceReference<HealthCheck> prevRef = registered.get(lastIndex - 1);
+ final Registration prevReg = this.registeredServices.get(prevRef);
+ prevReg.unregister();
+ }
+ reg.register(bundleContext);
+ }
+ }
+ return reg;
+ }
+
+ private synchronized void unregisterHCMBean(final BundleContext bundleContext, final ServiceReference<HealthCheck> ref) {
+ final Registration reg = registeredServices.remove(ref);
+ if (reg != null) {
+ final boolean registerFirst = reg.unregister();
+ final List<ServiceReference<HealthCheck>> registered = this.sortedRegistrations.get(reg.name);
+ registered.remove(ref);
+ if (registered.size() == 0) {
+ this.sortedRegistrations.remove(reg.name);
+ } else if (registerFirst) {
+ final ServiceReference<HealthCheck> newRef = registered.get(0);
+ final Registration newReg = this.registeredServices.get(newRef);
+ newReg.register(bundleContext);
+ }
+ bundleContext.ungetService(ref);
+ }
+ }
+
+ private final class Registration {
+ private final String name;
+ private final HealthCheckMBean mbean;
+
+ private final String objectName;
+
+ private ServiceRegistration<DynamicMBean> registration;
+
+ Registration(final String name, final HealthCheckMBean mbean) {
+ this.name = name;
+ this.mbean = mbean;
+ objectName = String.format("%s:type=%s,name=%s", JMX_DOMAIN, JMX_TYPE_NAME, name);
+ }
+
+ void register(final BundleContext btx) {
+ logger.debug("Registering health check mbean {} with name {}", mbean, objectName);
+ final Dictionary<String, String> mbeanProps = new Hashtable<String, String>();
+ mbeanProps.put("jmx.objectname", objectName);
+ this.registration = btx.registerService(DynamicMBean.class, this.mbean, mbeanProps);
+ }
+
+ boolean unregister() {
+ if (this.registration != null) {
+ logger.debug("Unregistering health check mbean {} with name {}", mbean, objectName);
+ this.registration.unregister();
+ this.registration = null;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private Registration getRegistration(final ServiceReference<HealthCheck> ref) {
+ final Object nameObj = ref.getProperty(HealthCheck.MBEAN_NAME);
+ if (nameObj != null) {
+ final HealthCheckMBean mbean = new HealthCheckMBean(ref, executor);
+ return new Registration(nameObj.toString().replace(',', '.'), mbean);
+ }
+ return null;
+ }
+
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,293 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+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.ExecutionResult;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.component.ComponentContext;
+
+public class CompositeHealthCheckTest {
+
+ @Spy
+ private CompositeHealthCheck compositeHealthCheck = new CompositeHealthCheck();
+
+ @Mock
+ private HealthCheckExecutor healthCheckExecutor;
+
+ @Mock
+ private ComponentContext componentContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ compositeHealthCheck.setHealthCheckExecutor(healthCheckExecutor);
+ compositeHealthCheck.setFilterTags(new String[] {});
+ compositeHealthCheck.setComponentContext(componentContext);
+ }
+
+ @Test
+ public void testExecution() {
+
+ doReturn((Result) null).when(compositeHealthCheck).checkForRecursion(Matchers.<ServiceReference> any(),
+ Matchers.<Set<String>> any());
+ String[] testTags = new String[] { "tag1" };
+ compositeHealthCheck.setFilterTags(testTags);
+
+ List<HealthCheckExecutionResult> executionResults = new LinkedList<HealthCheckExecutionResult>();
+ executionResults.add(createExecutionResult("Check 1", testTags, new Result(Result.Status.OK, "Good")));
+ executionResults.add(createExecutionResult("Check 2", testTags, new Result(Result.Status.CRITICAL, "Bad")));
+
+ when(healthCheckExecutor.execute(any(HealthCheckSelector.class), any(HealthCheckExecutionOptions.class)))
+ .thenReturn(executionResults);
+
+ Result result = compositeHealthCheck.execute();
+
+ verify(healthCheckExecutor, times(1)).execute(argThat(selectorWithTags(testTags)), argThat(andOptions));
+
+ assertEquals(Result.Status.CRITICAL, result.getStatus());
+
+ }
+
+ private Matcher<HealthCheckSelector> selectorWithTags(final String[] tags) {
+ return new TypeSafeMatcher<HealthCheckSelector>() {
+ @Override
+ protected boolean matchesSafely(HealthCheckSelector healthCheckSelector) {
+ return Arrays.equals(healthCheckSelector.tags(), tags) && healthCheckSelector.names() == null;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a select with tags (" + Arrays.toString(tags) + ") and no names.");
+ }
+ };
+ }
+
+ private HealthCheckExecutionResult createExecutionResult(String name, String[] testTags, Result result) {
+ HealthCheckExecutionResult healthCheckExecutionResult = new ExecutionResult(
+ new HealthCheckMetadata(new DummyHcServiceReference(name, testTags,
+ new String[0])),
+ result, 0L);
+ return healthCheckExecutionResult;
+ }
+
+ @Test
+ public void testSimpleRecursion() {
+
+ // composite check referencing itself
+ final String[] filterTags = new String[] { "check1" };
+ final DummyHcServiceReference hcRef = new DummyHcServiceReference("Check 1", new String[] { "check1" }, filterTags);
+
+ // test check is hcRef
+ doReturn(hcRef).when(componentContext).getServiceReference();
+ compositeHealthCheck.setFilterTags(filterTags);
+
+ compositeHealthCheck.setHealthCheckFilter(new HealthCheckFilter(null) {
+
+ @Override
+ public ServiceReference[] getHealthCheckServiceReferences(HealthCheckSelector selector) {
+ String[] tags = selector.tags();
+ ServiceReference[] result = new ServiceReference[] {};
+ if (tags.length > 0) {
+ if (tags[0].equals(filterTags[0])) {
+ result = new ServiceReference[] { hcRef };
+ }
+ }
+ return result;
+ }
+
+ });
+
+ Result result = compositeHealthCheck.execute();
+
+ verify(healthCheckExecutor, never()).execute(any(HealthCheckSelector.class));
+ assertEquals(Result.Status.HEALTH_CHECK_ERROR, result.getStatus());
+ }
+
+ @Test
+ public void testCyclicRecursion() {
+
+ // three checks, cyclic
+ final String[] filterTags = new String[] { "check2" };
+ final DummyHcServiceReference hcRef1 = new DummyHcServiceReference("Check 1", new String[] { "check1" }, filterTags);
+ final DummyHcServiceReference hcRef2 = new DummyHcServiceReference("Check 2", new String[] { "check2" }, new String[] { "check3" });
+ final DummyHcServiceReference hcRef3 = new DummyHcServiceReference("Check 3", new String[] { "check3" }, new String[] { "check1" });
+
+ // test check is hcRef1
+ doReturn(hcRef1).when(componentContext).getServiceReference();
+ compositeHealthCheck.setFilterTags(filterTags);
+
+ compositeHealthCheck.setHealthCheckFilter(new HealthCheckFilter(null) {
+
+ @Override
+ public ServiceReference[] getHealthCheckServiceReferences(HealthCheckSelector selector, boolean combineTagsWithOr) {
+ String[] tags = selector.tags();
+ ServiceReference[] result = new ServiceReference[] {};
+ if (tags.length > 0) {
+ if (tags[0].equals(filterTags[0])) {
+ result = new ServiceReference[] { hcRef2 };
+ } else if (tags[0].equals("check3")) {
+ result = new ServiceReference[] { hcRef3 };
+ } else if (tags[0].equals("check1")) {
+ result = new ServiceReference[] { hcRef1 };
+ }
+ }
+
+ return result;
+ }
+
+ });
+
+ Result result = compositeHealthCheck.execute();
+
+ verify(healthCheckExecutor, never()).execute(any(HealthCheckSelector.class));
+ assertEquals(Result.Status.HEALTH_CHECK_ERROR, result.getStatus());
+ }
+
+ @Test
+ public void testCombineWithOr() {
+
+ // composite check referencing itself
+ final String[] filterTags = new String[] { "check1" };
+ compositeHealthCheck.setFilterTags(filterTags);
+ compositeHealthCheck.setCombineTagsWithOr(true);
+
+ compositeHealthCheck.execute();
+
+ verify(healthCheckExecutor, times(1)).execute(argThat(selectorWithTags(filterTags)), argThat(orOptions));
+ }
+
+ private Matcher<HealthCheckExecutionOptions> orOptions = new TypeSafeMatcher<HealthCheckExecutionOptions>() {
+ @Override
+ protected boolean matchesSafely(HealthCheckExecutionOptions options) {
+ return options.isCombineTagsWithOr();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("options combining tags with or.");
+ }
+ };
+
+ private Matcher<HealthCheckExecutionOptions> andOptions = new TypeSafeMatcher<HealthCheckExecutionOptions>() {
+ @Override
+ protected boolean matchesSafely(HealthCheckExecutionOptions options) {
+ return !options.isCombineTagsWithOr();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("options combining tags with and.");
+ }
+ };
+
+ private static class DummyHcServiceReference implements ServiceReference {
+
+ private long id;
+ private String name;
+ private String[] tags;
+ private String[] filterTags;
+
+ public DummyHcServiceReference(String name, String[] tags, String[] filterTags) {
+ super();
+ this.id = (long) (Math.random() * Long.MAX_VALUE);
+ this.name = name;
+ this.tags = tags;
+ this.filterTags = filterTags;
+ }
+
+ @Override
+ public Object getProperty(String key) {
+
+ if (Constants.SERVICE_ID.equals(key)) {
+ return id;
+ } else if (HealthCheck.NAME.equals(key)) {
+ return name;
+ } else if (HealthCheck.MBEAN_NAME.equals(key)) {
+ return name;
+ } else if (HealthCheck.TAGS.equals(key)) {
+ return tags;
+ } else if (CompositeHealthCheck.PROP_FILTER_TAGS.equals(key)) {
+ return filterTags;
+ } else if (ComponentConstants.COMPONENT_NAME.equals(key)) {
+ return filterTags != null ? CompositeHealthCheck.class.getName() : "some.other.HealthCheck";
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String[] getPropertyKeys() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle getBundle() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle[] getUsingBundles() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int compareTo(Object reference) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,52 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.felix.hc.api.Result;
+import org.junit.Test;
+
+public class JmxAttributeHealthCheckTest {
+
+ static void assertJmxValue(String objectName, String attributeName, String constraint, boolean expected) {
+ final JmxAttributeHealthCheck hc = new JmxAttributeHealthCheck();
+
+ final JmxAttributeHealthCheckConfiguration configuration = mock(JmxAttributeHealthCheckConfiguration.class);
+ when(configuration.mbean_name()).thenReturn(objectName);
+ when(configuration.attribute_name()).thenReturn(attributeName);
+ when(configuration.attribute_value_constraint()).thenReturn(constraint);
+
+ hc.activate(configuration);
+
+ final Result r = hc.execute();
+ assertEquals("Expected result " + expected, expected, r.isOk());
+ }
+
+ @Test
+ public void testJmxAttributeMatch() {
+ assertJmxValue("java.lang:type=ClassLoading", "LoadedClassCount", "> 10", true);
+ }
+
+ @Test
+ public void testJmxAttributeNoMatch() {
+ assertJmxValue("java.lang:type=ClassLoading", "LoadedClassCount", "< 10", false);
+ }
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,179 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+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.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+public class HealthCheckExecutorImplTest {
+
+ @InjectMocks
+ private HealthCheckExecutorImpl healthCheckExecutorImpl = new HealthCheckExecutorImpl();;
+
+ @Mock
+ private HealthCheckFuture future;
+
+ @Mock
+ private HealthCheckMetadata HealthCheckMetadata;
+
+ @Spy
+ private HealthCheckResultCache healthCheckResultCache = new HealthCheckResultCache();
+
+ @Before
+ public void setup() {
+ initMocks(this);
+
+ when(future.getHealthCheckMetadata()).thenReturn(HealthCheckMetadata);
+ when(HealthCheckMetadata.getTitle()).thenReturn("Test Check");
+
+ // 2 sec normal timeout
+ healthCheckExecutorImpl.setTimeoutInMs(2000L);
+ // 10 sec timeout for critical
+ healthCheckExecutorImpl.setLongRunningFutureThresholdForRedMs(10000L);
+ }
+
+ @Test
+ public void testCollectResultsFromFutures() throws Exception {
+
+ List<HealthCheckFuture> futures = new LinkedList<HealthCheckFuture>();
+ futures.add(future);
+ Collection<HealthCheckExecutionResult> results = new TreeSet<HealthCheckExecutionResult>();
+
+ when(future.isDone()).thenReturn(true);
+ ExecutionResult testResult = new ExecutionResult(HealthCheckMetadata, new Result(Result.Status.OK, "test"), 10L);
+ when(future.get()).thenReturn(testResult);
+
+ healthCheckExecutorImpl.collectResultsFromFutures(futures, results);
+
+ verify(future, times(1)).get();
+
+ assertEquals(1, results.size());
+ assertTrue(results.contains(testResult));
+ }
+
+ @Test
+ public void testCollectResultsFromFuturesTimeout() throws Exception {
+
+ // add an earlier result with status ok (that will be shown as part of the log)
+ addResultToCache(Status.OK);
+
+ List<HealthCheckFuture> futures = new LinkedList<HealthCheckFuture>();
+ futures.add(future);
+ Set<HealthCheckExecutionResult> results = new TreeSet<HealthCheckExecutionResult>();
+
+ when(future.isDone()).thenReturn(false);
+ // simulating a future that was created 5sec ago
+ when(future.getCreatedTime()).thenReturn(new Date(new Date().getTime() - 1000 * 5));
+
+ healthCheckExecutorImpl.collectResultsFromFutures(futures, results);
+
+ verify(future, times(0)).get();
+
+ assertEquals(1, results.size());
+ HealthCheckExecutionResult result = results.iterator().next();
+
+ assertEquals(Result.Status.WARN, result.getHealthCheckResult().getStatus());
+
+ // 3 because previous result exists and is part of log
+ assertEquals(3, getLogEntryCount(result));
+ }
+
+ @Test
+ public void testCollectResultsFromFuturesCriticalTimeout() throws Exception {
+
+ List<HealthCheckFuture> futures = new LinkedList<HealthCheckFuture>();
+ futures.add(future);
+ Set<HealthCheckExecutionResult> results = new TreeSet<HealthCheckExecutionResult>();
+
+ when(future.isDone()).thenReturn(false);
+
+ // use an old date now (simulating a future that has run for an hour)
+ when(future.getCreatedTime()).thenReturn(new Date(new Date().getTime() - 1000 * 60 * 60));
+
+ healthCheckExecutorImpl.collectResultsFromFutures(futures, results);
+ assertEquals(1, results.size());
+ HealthCheckExecutionResult result = results.iterator().next();
+
+ verify(future, times(0)).get();
+
+ assertEquals(Result.Status.CRITICAL, result.getHealthCheckResult().getStatus());
+ assertEquals(1, getLogEntryCount(result));
+ }
+
+ @Test
+ public void testCollectResultsFromFuturesWarnTimeoutWithPreviousCritical() throws Exception {
+
+ // an earlier result with critical
+ addResultToCache(Status.CRITICAL);
+
+ List<HealthCheckFuture> futures = new LinkedList<HealthCheckFuture>();
+ futures.add(future);
+ Set<HealthCheckExecutionResult> results = new TreeSet<HealthCheckExecutionResult>();
+
+ when(future.isDone()).thenReturn(false);
+ // simulating a future that was created 5sec ago
+ when(future.getCreatedTime()).thenReturn(new Date(new Date().getTime() - 1000 * 5));
+
+ healthCheckExecutorImpl.collectResultsFromFutures(futures, results);
+ assertEquals(1, results.size());
+ HealthCheckExecutionResult result = results.iterator().next();
+
+ verify(future, times(0)).get();
+
+ // expect CRITICAL because previous result (before timeout) was CRITICAL (and not only WARN)
+ assertEquals(Result.Status.CRITICAL, result.getHealthCheckResult().getStatus());
+ assertEquals(3, getLogEntryCount(result));
+ }
+
+ private int getLogEntryCount(HealthCheckExecutionResult result) {
+ int logEntryCount = 0;
+ final Iterator<Entry> it = result.getHealthCheckResult().iterator();
+ while (it.hasNext()) {
+ it.next();
+ logEntryCount++;
+ }
+ return logEntryCount;
+ }
+
+ private void addResultToCache(Status status) {
+ healthCheckResultCache.updateWith(new ExecutionResult(HealthCheckMetadata, new Result(status, "Status " + status), 1000));
+ }
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCacheTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCacheTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCacheTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCacheTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,204 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+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.HealthCheckMetadata;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class HealthCheckResultCacheTest {
+
+ private static final int HC_TIMEOUT_NOT_SET = -1;
+ private static final int DUR_1_MIN = 60 * 1000;
+ private static final int DUR_2_MIN = 2 * DUR_1_MIN;
+ private static final int DUR_3_MIN = 3 * DUR_1_MIN;
+ private static final int DUR_4_MIN = 4 * DUR_1_MIN;
+
+ private HealthCheckResultCache healthCheckResultCache = new HealthCheckResultCache();
+
+ @Mock
+ ServiceReference serviceRef;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ }
+
+ private HealthCheckMetadata setupHealthCheckMetadata(long id, long ttl) {
+ reset(serviceRef);
+ doReturn(id).when(serviceRef).getProperty(Constants.SERVICE_ID);
+ doReturn(ttl).when(serviceRef).getProperty(HealthCheck.RESULT_CACHE_TTL_IN_MS);
+ doReturn("HC id=" + id).when(serviceRef).getProperty(HealthCheck.NAME);
+ return new HealthCheckMetadata(serviceRef);
+ }
+
+ @Test
+ public void testHealthCheckResultCache() {
+
+ HealthCheckMetadata hc1 = setupHealthCheckMetadata(1, HC_TIMEOUT_NOT_SET);
+ ExecutionResult executionResult1 = spy(new ExecutionResult(hc1, new Result(Result.Status.OK, "result for hc1"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_1_MIN)).when(executionResult1).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult1);
+
+ HealthCheckMetadata hc2 = setupHealthCheckMetadata(2, HC_TIMEOUT_NOT_SET);
+ ExecutionResult executionResult2 = spy(new ExecutionResult(hc2, new Result(Result.Status.OK, "result for hc2"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_3_MIN)).when(executionResult2).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult2);
+
+ HealthCheckMetadata hc3 = setupHealthCheckMetadata(3, DUR_4_MIN);
+ ExecutionResult executionResult3 = spy(new ExecutionResult(hc3, new Result(Result.Status.OK, "result for hc3"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_3_MIN)).when(executionResult3).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult3);
+
+ HealthCheckMetadata hc4 = setupHealthCheckMetadata(4, HC_TIMEOUT_NOT_SET);
+ // no result for this yet
+
+ List<HealthCheckMetadata> hcList = new ArrayList<HealthCheckMetadata>(Arrays.asList(hc1, hc2, hc3, hc4));
+ List<HealthCheckExecutionResult> results = new ArrayList<HealthCheckExecutionResult>();
+
+ healthCheckResultCache.useValidCacheResults(hcList, results, DUR_2_MIN);
+
+ assertTrue(hcList.contains(hc2)); // result too old, left in hcList for later execution
+ assertTrue(hcList.contains(hc4)); // no result was added to cache via updateWith()
+
+ assertTrue(results.contains(executionResult1)); // true <= result one min old, global timeout 2min
+ assertFalse(results.contains(executionResult2)); // false <= result three min old, global timeout 2min
+ assertTrue(results.contains(executionResult3)); // true <= result one three old, HC timeout 4min
+
+ // values not found in cache are left in hcList
+ assertEquals(2, hcList.size());
+ assertEquals(2, results.size());
+
+ }
+
+ @Test
+ public void testHealthCheckResultCacheTtl() {
+
+ // -- test cache miss due to HC TTL
+ HealthCheckMetadata hcWithTtl = setupHealthCheckMetadata(1, DUR_1_MIN);
+ ExecutionResult executionResult = spy(new ExecutionResult(hcWithTtl, new Result(Result.Status.OK, "result for hc"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_2_MIN)).when(executionResult).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult);
+
+ HealthCheckExecutionResult result = healthCheckResultCache.getValidCacheResult(hcWithTtl, DUR_3_MIN);
+ assertNull(result); // even though global timeout would be ok (2min<3min, the hc timeout of 1min invalidates the result)
+
+ // -- test cache hit due to HC TTL
+ hcWithTtl = setupHealthCheckMetadata(2, DUR_3_MIN);
+ executionResult = spy(new ExecutionResult(hcWithTtl, new Result(Result.Status.OK, "result for hc"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_2_MIN)).when(executionResult).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult);
+
+ result = healthCheckResultCache.getValidCacheResult(hcWithTtl, DUR_1_MIN);
+ assertEquals(executionResult, result); // even though global timeout would invalidate this result (1min<2min, the hc timeout of 3min
+ // allows the result)
+
+ // -- test Long.MAX_VALUE
+ hcWithTtl = setupHealthCheckMetadata(3, Long.MAX_VALUE);
+ executionResult = spy(new ExecutionResult(hcWithTtl, new Result(Result.Status.OK, "result for hc"), 1));
+ doReturn(new Date(new Date().getTime() - DUR_4_MIN)).when(executionResult).getFinishedAt();
+ healthCheckResultCache.updateWith(executionResult);
+
+ result = healthCheckResultCache.getValidCacheResult(hcWithTtl, DUR_1_MIN);
+ assertEquals(executionResult, result);
+
+ }
+
+ private HealthCheckMetadata setupHealthCheckMetadataWithStickyResults(long id, long warningsStickForMinutes) {
+ reset(serviceRef);
+ doReturn(id).when(serviceRef).getProperty(Constants.SERVICE_ID);
+ doReturn(warningsStickForMinutes).when(serviceRef).getProperty(HealthCheck.WARNINGS_STICK_FOR_MINUTES);
+ doReturn("HC id=" + id).when(serviceRef).getProperty(HealthCheck.NAME);
+ return new HealthCheckMetadata(serviceRef);
+ }
+
+ @Test
+ public void testCreateExecutionResultWithStickyResults() {
+
+ HealthCheckMetadata hcWithStickyResultsSet = setupHealthCheckMetadataWithStickyResults(1, 2 /* 2 minutes */);
+ ExecutionResult currentResult = spy(new ExecutionResult(hcWithStickyResultsSet, new Result(Result.Status.OK, "result for hc"), 1));
+ HealthCheckExecutionResult overallResultWithStickyResults = healthCheckResultCache
+ .createExecutionResultWithStickyResults(currentResult);
+ assertTrue("Exact same result is expected if no history exists", currentResult == overallResultWithStickyResults);
+
+ // add 4 minutes old WARN to cache
+ ExecutionResult oldWarnResult = spy(
+ new ExecutionResult(hcWithStickyResultsSet, new Result(Result.Status.WARN, "result for hc"), 1));
+ doReturn(new Date(System.currentTimeMillis() - DUR_4_MIN)).when(oldWarnResult).getFinishedAt();
+ healthCheckResultCache.updateWith(oldWarnResult);
+
+ // check that it is not used
+ currentResult = new ExecutionResult(hcWithStickyResultsSet, new Result(Result.Status.OK, "result for hc"), 1);
+ overallResultWithStickyResults = healthCheckResultCache.createExecutionResultWithStickyResults(currentResult);
+ assertTrue("Exact same result is expected if WARN HC Result is too old", currentResult == overallResultWithStickyResults);
+
+ // change WARN to 1 minute age
+ doReturn(new Date(System.currentTimeMillis() - DUR_1_MIN)).when(oldWarnResult).getFinishedAt();
+ overallResultWithStickyResults = healthCheckResultCache.createExecutionResultWithStickyResults(currentResult);
+ assertTrue("Expect newly created result as sticky result should be taken into account",
+ currentResult != overallResultWithStickyResults);
+ assertEquals("Expect status to be taken over from old, sticky WARN", Result.Status.WARN,
+ overallResultWithStickyResults.getHealthCheckResult().getStatus());
+ assertEquals("Expect 4 entries, two each for current and WARN", 4, getLogMsgCount(overallResultWithStickyResults));
+
+ // add 1 minutes old CRITICAL to cache
+ ExecutionResult oldCriticalResult = spy(
+ new ExecutionResult(hcWithStickyResultsSet, new Result(Result.Status.CRITICAL, "result for hc"), 1));
+ doReturn(new Date(System.currentTimeMillis() - DUR_1_MIN)).when(oldCriticalResult).getFinishedAt();
+ healthCheckResultCache.updateWith(oldCriticalResult);
+
+ overallResultWithStickyResults = healthCheckResultCache.createExecutionResultWithStickyResults(currentResult);
+ assertTrue("Expect newly created result as sticky result should be taken into account",
+ currentResult != overallResultWithStickyResults);
+ assertEquals("Expect status to be taken over from old, sticky CRITICAL", Result.Status.CRITICAL,
+ overallResultWithStickyResults.getHealthCheckResult().getStatus());
+ assertEquals("Expect six entries, two each for current, WARN and CRITICAL result", 6,
+ getLogMsgCount(overallResultWithStickyResults));
+
+ }
+
+ private int getLogMsgCount(HealthCheckExecutionResult result) {
+ int count = 0;
+ for (ResultLog.Entry entry : result.getHealthCheckResult()) {
+ count++;
+ }
+ return count;
+ }
+
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,305 @@
+/*
+ * 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.servlet;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.core.impl.executor.ExecutionResult;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class HealthCheckExecutorServletTest {
+
+ @InjectMocks
+ private HealthCheckExecutorServlet healthCheckExecutorServlet = new HealthCheckExecutorServlet();
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private HealthCheckExecutor healthCheckExecutor;
+
+ @Mock
+ private ResultHtmlSerializer htmlSerializer;
+
+ @Mock
+ private ResultJsonSerializer jsonSerializer;
+
+ @Mock
+ private ResultTxtSerializer txtSerializer;
+
+ @Mock
+ private ResultTxtVerboseSerializer verboseTxtSerializer;
+
+ @Mock
+ private ServiceReference hcServiceRef;
+
+ @Mock
+ private PrintWriter writer;
+
+ @Before
+ public void setup() throws IOException {
+ initMocks(this);
+
+ doReturn(500L).when(hcServiceRef).getProperty(Constants.SERVICE_ID);
+ doReturn(writer).when(response).getWriter();
+ }
+
+ @Test
+ public void testDoGetHtml() throws ServletException, IOException {
+
+ final String testTag = "testTag";
+ doReturn(testTag).when(request).getParameter(HealthCheckExecutorServlet.PARAM_TAGS.name);
+ doReturn("false").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR.name);
+ final List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.CRITICAL);
+ doReturn(executionResults).when(healthCheckExecutor).execute(selector(new String[] { testTag }, new String[0]),
+ eq(new HealthCheckExecutionOptions()));
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(jsonSerializer);
+ verifyZeroInteractions(txtSerializer);
+ verifyZeroInteractions(verboseTxtSerializer);
+ verify(htmlSerializer)
+ .serialize(resultEquals(new Result(Result.Status.CRITICAL, "Overall Status CRITICAL")), eq(executionResults),
+ contains("Supported URL parameters"), eq(false));
+ }
+
+ @Test
+ public void testDoGetNameAndTagInPath() throws ServletException, IOException {
+
+ final String testTag = "testTag";
+ final String testName = "test name";
+
+ doReturn(testTag + "," + testName).when(request).getPathInfo();
+ doReturn("false").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR.name);
+ final List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.CRITICAL);
+ doReturn(executionResults).when(healthCheckExecutor).execute(selector(new String[] { testTag }, new String[] { testName }),
+ eq(new HealthCheckExecutionOptions()));
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verify(request, never()).getParameter(HealthCheckExecutorServlet.PARAM_TAGS.name);
+ verify(request, never()).getParameter(HealthCheckExecutorServlet.PARAM_NAMES.name);
+ verifyZeroInteractions(jsonSerializer);
+ verifyZeroInteractions(txtSerializer);
+ verifyZeroInteractions(verboseTxtSerializer);
+ verify(htmlSerializer)
+ .serialize(resultEquals(new Result(Result.Status.CRITICAL, "Overall Status CRITICAL")), eq(executionResults),
+ contains("Supported URL parameters"), eq(false));
+ }
+
+ @Test
+ public void testDoGetJson() throws ServletException, IOException {
+
+ final String testTag = "testTag";
+ doReturn("true").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR.name);
+ int timeout = 5000;
+ doReturn(timeout + "").when(request).getParameter(HealthCheckExecutorServlet.PARAM_OVERRIDE_GLOBAL_TIMEOUT.name);
+ doReturn("/" + testTag + ".json").when(request).getPathInfo();
+ final List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.WARN);
+ HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+ options.setCombineTagsWithOr(true);
+ options.setOverrideGlobalTimeout(timeout);
+ doReturn(executionResults).when(healthCheckExecutor).execute(selector(new String[] { testTag }, new String[0]), eq(options));
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(htmlSerializer);
+ verifyZeroInteractions(txtSerializer);
+ verifyZeroInteractions(verboseTxtSerializer);
+ verify(jsonSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")), eq(executionResults),
+ anyString(),
+ eq(false));
+
+ }
+
+ @Test
+ public void testDoGetTxt() throws ServletException, IOException {
+
+ final String testTag = "testTag";
+ doReturn(testTag).when(request).getParameter(HealthCheckExecutorServlet.PARAM_TAGS.name);
+ doReturn(HealthCheckExecutorServlet.FORMAT_TXT).when(request).getParameter(HealthCheckExecutorServlet.PARAM_FORMAT.name);
+ doReturn("true").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR.name);
+ int timeout = 5000;
+ doReturn(timeout + "").when(request).getParameter(HealthCheckExecutorServlet.PARAM_OVERRIDE_GLOBAL_TIMEOUT.name);
+ final List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.WARN);
+ HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+ options.setCombineTagsWithOr(true);
+ options.setOverrideGlobalTimeout(timeout);
+
+ doReturn(executionResults).when(healthCheckExecutor).execute(selector(new String[] { testTag }, new String[0]), eq(options));
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(htmlSerializer);
+ verifyZeroInteractions(jsonSerializer);
+ verifyZeroInteractions(verboseTxtSerializer);
+ verify(txtSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")));
+
+ }
+
+ @Test
+ public void testDoGetVerboseTxt() throws ServletException, IOException {
+
+ String testTag = "testTag";
+ doReturn(testTag).when(request).getParameter(HealthCheckExecutorServlet.PARAM_TAGS.name);
+ doReturn(HealthCheckExecutorServlet.FORMAT_VERBOSE_TXT).when(request).getParameter(HealthCheckExecutorServlet.PARAM_FORMAT.name);
+
+ List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.WARN);
+ doReturn(executionResults).when(healthCheckExecutor).execute(selector(new String[] { testTag }, new String[0]),
+ any(HealthCheckExecutionOptions.class));
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(htmlSerializer);
+ verifyZeroInteractions(jsonSerializer);
+ verifyZeroInteractions(txtSerializer);
+ verify(verboseTxtSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")), eq(executionResults),
+ eq(false));
+
+ }
+
+ private List<HealthCheckExecutionResult> getExecutionResults(Result.Status worstStatus) {
+ List<HealthCheckExecutionResult> results = new ArrayList<HealthCheckExecutionResult>();
+ results.add(new ExecutionResult(new HealthCheckMetadata(hcServiceRef), new Result(worstStatus, worstStatus.name()), 100));
+ results.add(new ExecutionResult(new HealthCheckMetadata(hcServiceRef), new Result(Result.Status.OK, "OK"), 100));
+ return results;
+ }
+
+ @Test
+ public void testGetStatusMapping() throws ServletException {
+
+ Map<Status, Integer> statusMapping = healthCheckExecutorServlet.getStatusMapping("CRITICAL:503");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 503);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("OK:333");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 333);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("OK:200,WARN:418,CRITICAL:503,HEALTH_CHECK_ERROR:500");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 418);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 500);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("CRITICAL:503,HEALTH_CHECK_ERROR:500");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 500);
+
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidToken() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("CRITICAL");
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidStatus() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("INVALID:200");
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidStatusCode() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("CRITICAL:xxx");
+ }
+
+ static Result resultEquals(Result expected) {
+ return argThat(new ResultMatcher(expected));
+ }
+
+ static class ResultMatcher extends ArgumentMatcher<Result> {
+
+ private final Result expectedResult;
+
+ public ResultMatcher(Result expected) {
+ this.expectedResult = expected;
+ }
+
+ @Override
+ public boolean matches(Object actual) {
+ Result actualResult = (Result) actual;
+ return actualResult.getStatus().equals(expectedResult.getStatus()); // simple status matching only sufficient for this test case
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expectedResult == null ? null : expectedResult.toString());
+ }
+ }
+
+ HealthCheckSelector selector(final String[] tags, final String[] names) {
+ return argThat(new ArgumentMatcher<HealthCheckSelector>() {
+ @Override
+ public boolean matches(Object actual) {
+ if (actual instanceof HealthCheckSelector) {
+ HealthCheckSelector actualSelector = (HealthCheckSelector) actual;
+ return Arrays.equals(actualSelector.tags(), tags.length == 0 ? new String[] { "" } : tags) &&
+ Arrays.equals(actualSelector.names(), names.length == 0 ? new String[] { "" } : names);
+ } else {
+ return false;
+ }
+ }
+ });
+ }
+
+}
Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializerTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializerTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializerTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializerTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,72 @@
+/*
+ * 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.servlet;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.util.Arrays;
+
+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.core.impl.executor.ExecutionResult;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class ResultJsonSerializerTest {
+
+ @Mock
+ private ServiceReference<HealthCheck> serviceReference;
+
+ ResultJsonSerializer resultJsonSerializer = new ResultJsonSerializer();
+
+ @Before
+ public void setup() {
+ initMocks(this);
+
+ when(serviceReference.getProperty(HealthCheck.NAME)).thenReturn("Test");
+ when(serviceReference.getProperty(HealthCheck.TAGS)).thenReturn(new String[] { "tag1", "tag2" });
+ when(serviceReference.getProperty(Constants.SERVICE_ID)).thenReturn(1L);
+ }
+
+ @Test
+ public void testJsonSerialisation() {
+
+ FormattingResultLog log = new FormattingResultLog();
+ log.info("test message");
+ Result result = new Result(log);
+ HealthCheckMetadata hcMetadata = new HealthCheckMetadata(serviceReference);
+ HealthCheckExecutionResult executionResult = new ExecutionResult(hcMetadata, result, 100);
+ Result overallResult = new Result(Result.Status.OK, "Overall status");
+
+ String json = resultJsonSerializer.serialize(overallResult, Arrays.asList(executionResult), null, false);
+ assertThat(json, containsString("\"overallResult\":\"OK\""));
+ assertThat(json, containsString("\"tags\":[\"tag1\",\"tag2\"]"));
+ assertThat(json, containsString("\"messages\":[{\"status\":\"OK\",\"message\":\"test message\"}]"));
+ }
+
+}