| /* |
| * 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.generalchecks; |
| |
| import java.lang.management.ManagementFactory; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| |
| import org.apache.felix.hc.api.FormattingResultLog; |
| import org.apache.felix.hc.api.HealthCheck; |
| import org.apache.felix.hc.api.Result; |
| import org.apache.felix.hc.api.ResultLog; |
| import org.apache.felix.hc.core.impl.util.lang.StringUtils; |
| import org.apache.felix.hc.generalchecks.util.SimpleConstraintChecker; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.ConfigurationPolicy; |
| import org.osgi.service.metatype.annotations.AttributeDefinition; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.osgi.service.metatype.annotations.ObjectClassDefinition; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** {@link HealthCheck} that checks one (or multiple) JMX attribute(s). */ |
| @Component(service = HealthCheck.class, configurationPolicy = ConfigurationPolicy.REQUIRE) |
| @Designate(ocd = JmxAttributeCheck.Config.class, factory = true) |
| public class JmxAttributeCheck implements HealthCheck { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(JmxAttributeCheck.class); |
| |
| public static final String HC_NAME = "JMX Attribute"; |
| public static final String HC_LABEL = "Health Check: " + HC_NAME; |
| |
| private Result.Status statusForFailedContraint; |
| |
| @ObjectClassDefinition(name = HC_LABEL, description = "Checks the value of a single JMX attribute.") |
| @interface Config { |
| |
| @AttributeDefinition(name = "Name", description = "Name of this health check.") |
| String hc_name() default HC_NAME; |
| |
| @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.") |
| String[] hc_tags() default {}; |
| |
| @AttributeDefinition(name = "MBean Name", description = "The name of the MBean to retrieve the attribute to be checked from.") |
| String mbean_name() default ""; |
| |
| @AttributeDefinition(name = "Attribute Name", description = "The name of the MBean attribute to check against the constraint.") |
| String attribute_name() default ""; |
| |
| @AttributeDefinition(name = "Attribute Constraint", description = "Constraint on the MBean attribute value. If simple value, uses equals. For strings constraints like 'CONTAINS mystr', 'STARTS_WITH mystr' or 'ENDS_WITH mystr' can be used, for numbers constraints like '> 4', '= 7', '< 9' or 'between 3 and 7' work.") |
| String attribute_value_constraint() default ""; |
| |
| @AttributeDefinition(name = "Status for failed constraint", description = "Status to fail with if the constraint check fails") |
| Result.Status statusForFailedContraint() default Result.Status.WARN; |
| |
| @AttributeDefinition |
| String webconsole_configurationFactory_nameHint() default "JMX MBean {mbean.name} Attribute '{attribute.name}' constraint: {attribute.value.constraint}"; |
| } |
| |
| private List<AttributeConstraintConfig> attributeConstraintConfigs; |
| |
| @Activate |
| protected void activate(final Config config, final Map<String,Object> rawConfig) { |
| statusForFailedContraint = config.statusForFailedContraint(); |
| attributeConstraintConfigs = AttributeConstraintConfig.load(config, rawConfig); |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Activated JMX Attribute HC with statusForFailedContraint={} and attribute constraint config(s):", statusForFailedContraint); |
| for (AttributeConstraintConfig attributeConstraintConfig : attributeConstraintConfigs) { |
| LOG.debug(attributeConstraintConfig.toString()); |
| } |
| } |
| } |
| |
| |
| @Override |
| public Result execute() { |
| FormattingResultLog resultLog = new FormattingResultLog(); |
| for (AttributeConstraintConfig attributeConstraintConfig : attributeConstraintConfigs) { |
| checkAttributeConstraint(resultLog, attributeConstraintConfig); |
| } |
| return new Result(resultLog); |
| } |
| |
| private void checkAttributeConstraint(final FormattingResultLog resultLog, AttributeConstraintConfig attributeConstraintConfig) { |
| resultLog.debug("Checking {}", attributeConstraintConfig); |
| try { |
| final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer(); |
| final ObjectName objectName = new ObjectName(attributeConstraintConfig.mbeanName); |
| if (jmxServer.queryNames(objectName, null).size() == 0) { |
| resultLog.warn("MBean not found: {}", objectName); |
| } else { |
| final Object value = jmxServer.getAttribute(objectName, attributeConstraintConfig.attributeName); |
| resultLog.debug("{} {} returns {}", attributeConstraintConfig.mbeanName, attributeConstraintConfig.attributeName, value); |
| boolean matches = new SimpleConstraintChecker().check(value, attributeConstraintConfig.attributeValueConstraint); |
| String baseMsg = "JMX attribute "+attributeConstraintConfig.mbeanName+" -> '"+attributeConstraintConfig.attributeName+"': Value [" + value + "] "; |
| if (matches) { |
| resultLog.add(new ResultLog.Entry(Result.Status.OK, baseMsg+"matches constraint [" + attributeConstraintConfig.attributeValueConstraint + "]")); |
| } else { |
| resultLog.add(new ResultLog.Entry( statusForFailedContraint, baseMsg+"does not match constraint [" + attributeConstraintConfig.attributeValueConstraint + "]")); |
| } |
| } |
| } catch (Exception e) { |
| LOG.warn("JMX check {} failed: {}", attributeConstraintConfig, e.getMessage(), e); |
| resultLog.healthCheckError("JMX attribute check failed: {}", attributeConstraintConfig, e); |
| } |
| } |
| |
| |
| private static class AttributeConstraintConfig { |
| |
| public static final String PROP_MBEAN = "mbean"; |
| public static final String PROP_ATTRIBUTE = "attribute"; |
| |
| public static final String SUFFIX_NAME = ".name"; |
| public static final String SUFFIX_VALUE_CONSTRAINT = ".value.constraint"; |
| |
| private static List<AttributeConstraintConfig> load(final Config config, final Map<String, Object> rawConfig) { |
| List<AttributeConstraintConfig> attributeConstraintConfigs = new ArrayList<AttributeConstraintConfig>(); |
| |
| // first attribute via metatype |
| attributeConstraintConfigs.add(new AttributeConstraintConfig(config.mbean_name(), config.attribute_name(),config.attribute_value_constraint())); |
| |
| // additional attributes possible via naming scheme "mbean2.name" / "attribute2.name" ... |
| int attributeCounter = 2; |
| while(AttributeConstraintConfig.hasConfig(rawConfig, attributeCounter)) { |
| attributeConstraintConfigs.add(new AttributeConstraintConfig(rawConfig, attributeCounter)); |
| attributeCounter++; |
| } |
| return attributeConstraintConfigs; |
| } |
| |
| private static String getAttributePropName(int attributeCounter) { |
| return PROP_ATTRIBUTE + attributeCounter + SUFFIX_NAME; |
| } |
| |
| private static boolean hasConfig(Map<String,Object> rawConfig, int attributeCounter) { |
| return rawConfig.containsKey(getAttributePropName(attributeCounter)); |
| } |
| |
| final String mbeanName; |
| |
| final String attributeName; |
| final String attributeValueConstraint; |
| |
| public AttributeConstraintConfig(String mbeanName, String attributeName, String attributeValueConstraint) { |
| this.mbeanName = mbeanName; |
| this.attributeName = attributeName; |
| this.attributeValueConstraint = attributeValueConstraint; |
| } |
| |
| public AttributeConstraintConfig(Map<String,Object> rawConfig, int attributeCounter) { |
| String propNameAttribute = getAttributePropName(attributeCounter); |
| String defaultMBeanName = (String) rawConfig.get(PROP_MBEAN + SUFFIX_NAME); |
| String mBeanName = (String) rawConfig.get(PROP_MBEAN + attributeCounter + SUFFIX_NAME); |
| this.mbeanName = StringUtils.defaultIfBlank(mBeanName, defaultMBeanName); |
| this.attributeName = (String) rawConfig.get(propNameAttribute); |
| this.attributeValueConstraint = (String) rawConfig.get(PROP_ATTRIBUTE + attributeCounter + SUFFIX_VALUE_CONSTRAINT); |
| if(StringUtils.isBlank(mbeanName) || StringUtils.isBlank(attributeName) || StringUtils.isBlank(attributeValueConstraint)) { |
| throw new IllegalArgumentException("Invalid JmxAttributeCheck config for property "+mbeanName+" -> "+propNameAttribute+": "+toString()); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "JMX attribute "+mbeanName+" -> '"+attributeName+"': Constraint: "+attributeValueConstraint; |
| }; |
| } |
| } |