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\"}]")); + } + +}
