/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.wildfly10.itest;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.Nullable;
import org.testng.annotations.Test;
import org.rhq.core.clientapi.agent.configuration.ConfigurationUtility;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.MeasurementDefinitionFilter;
import org.rhq.core.domain.util.ResourceTypeUtility;
import org.rhq.core.pc.inventory.InventoryManager;
import org.rhq.core.pc.inventory.ResourceContainer;
import org.rhq.core.plugin.testutil.AbstractAgentPluginTest;
import org.rhq.modules.plugins.wildfly10.itest.domain.ManagedServerTest;
import org.rhq.modules.plugins.wildfly10.test.util.Constants;
import org.rhq.test.arquillian.RunDiscovery;
/**
* AS7 plugin tests that are not specific to particular Resource types. The tests delegate to methods in
* {@link AbstractAgentPluginTest}, which provides generic impls of such tests.
*
* @author Ian Springer
*/
@Test(groups = { "integration", "pc" }, singleThreaded = true)
public class GenericJBossAS7PluginTest extends AbstractJBossAS7PluginTest {
// ****************************** LIFECYCLE ****************************** //
@Test(priority = 1)
@RunDiscovery
public void testAllResourceComponentsStarted() throws Exception {
// first, wait for entire discovery to stabilize
Resource platform = validatePlatform();
validateDiscovery();
// (jshaughn) The idea of this test, i think, is to ensure all component start methods work, so
// next, proactively try and start all components. Some may already be started, that's OK, the
// activate call will just be a no-op.
System.out.println("Starting all resources...");
startComponent(this.pluginContainer.getInventoryManager(), platform);
// now, check that they are started
System.out.println("Validating all resources have started...");
assertAllResourceComponentsStarted(this.pluginContainer.getInventoryManager(), platform);
}
private void startComponent(InventoryManager im, Resource resource) {
ResourceContainer container = im.getResourceContainer(resource);
if (null == container) {
throw new IllegalStateException("No container found for resource " + resource);
}
try {
im.activateResource(resource, container, false);
} catch (Exception e) {
throw new IllegalStateException("Failed to activate resource " + resource);
}
for (Resource child : resource.getChildResources()) {
startComponent(im, child);
}
}
private void assertAllResourceComponentsStarted(InventoryManager im, Resource platform) throws Exception {
Map<ResourceType, ResourceContainer> nonStartedResourceContainersByType = new LinkedHashMap<ResourceType, ResourceContainer>();
findNonStartedResourceComponentsRecursively(im, platform, nonStartedResourceContainersByType);
assertTrue(nonStartedResourceContainersByType.isEmpty(),
"Resource containers with non-started Resource components by type: " + nonStartedResourceContainersByType);
}
private void findNonStartedResourceComponentsRecursively(InventoryManager im, Resource resource,
Map<ResourceType, ResourceContainer> nonStartedResourceContainersByType) throws Exception {
ResourceType resourceType = resource.getResourceType();
if (!nonStartedResourceContainersByType.containsKey(resourceType)) {
ResourceContainer resourceContainer = im.getResourceContainer(resource);
if (resourceContainer.getResourceComponentState() == ResourceContainer.ResourceComponentState.STARTING) {
// give it 5s to finish starting
try {
System.out.println("Resource is STARTING, Waiting 5s before checking for STARTED " + resource);
Thread.sleep(5000L);
} catch (InterruptedException e) {
// keep going
}
}
if (resourceContainer.getResourceComponentState() != ResourceContainer.ResourceComponentState.STARTED) {
nonStartedResourceContainersByType.put(resourceType, resourceContainer);
} else if (resourceContainer.getResourceComponent() == null) {
System.err.println("****** Resource container " + resourceContainer
+ " says its Resource component is started, but the component is null. ******");
nonStartedResourceContainersByType.put(resourceType, resourceContainer);
}
}
// Recurse.
for (Resource childResource : resource.getChildResources()) {
findNonStartedResourceComponentsRecursively(im, childResource, nonStartedResourceContainersByType);
}
}
// ******************************* METRICS ******************************* //
@Test(priority = 2)
public void testAllMetricsHaveNonNullValues() throws Exception {
Map<ResourceType, String[]> excludedMetricNamesByType = new HashMap<ResourceType, String[]>();
// It's normal for the "startTime" trait to be null for a Managed Server that is down/disabled.
// It's normal for the "multicastAddress" trait to be null for a Managed Server that is not configured for JGroups HA.
excludedMetricNamesByType
.put(ManagedServerTest.RESOURCE_TYPE, new String[] { "startTime", "multicastAddress" });
// Some memory pools do not expose those statistics (by default), so in case they
// are not exposed, it is normal that the server returns 'undefined' for the value
// Note that those
excludedMetricNamesByType.put(new ResourceType("Memory Pool Resource", Constants.PLUGIN_NAME, ResourceCategory.SERVICE,
null), new String[] { "collection-usage-threshold-count", "collection-usage-threshold", "collection-usage",
"collection-usage-threshold-exceeded", "collection-usage:committed", "collection-usage:init",
"collection-usage:max", "collection-usage:used", "usage-threshold-count", "usage-threshold-exceeded" });
//the max-connections will be 'undefined' if no specific value is set. This is AS's way of saying the value
//is connector specific
excludedMetricNamesByType.put(new ResourceType("Connector (Managed Server)", Constants.PLUGIN_NAME, ResourceCategory.SERVICE, null),
new String[] {"_expr:max-connections"});
excludedMetricNamesByType.put(new ResourceType("Connector", Constants.PLUGIN_NAME, ResourceCategory.SERVICE, null),
new String[] {"_expr:max-connections"});
// It's normal for the "active-patches" to have null value on servers that do not support patching.
excludedMetricNamesByType.put(new ResourceType("JBossAS7 Host Controller", Constants.PLUGIN_NAME, ResourceCategory.SERVER,
null), new String[]{"active-patches"});
excludedMetricNamesByType.put(new ResourceType("JBossAS7 Standalone Server", Constants.PLUGIN_NAME, ResourceCategory.SERVER,
null), new String[]{"active-patches"});
assertAllNumericMetricsAndTraitsHaveNonNullValues(excludedMetricNamesByType);
}
protected void assertAllNumericMetricsAndTraitsHaveNonNullValues(
Map<ResourceType, String[]> excludedMetricNamesByType) throws Exception {
Resource platform = this.pluginContainer.getInventoryManager().getPlatform();
LinkedHashMap<ResourceType, Set<String>> metricsWithNullValuesByType = new LinkedHashMap<ResourceType, Set<String>>();
findNumericMetricsAndTraitsWithNullValuesRecursively(platform, metricsWithNullValuesByType);
removeExcludedMetricNames(metricsWithNullValuesByType, excludedMetricNamesByType);
assertTrue(metricsWithNullValuesByType.isEmpty(), "Metrics with null values by type: "
+ metricsWithNullValuesByType);
}
private void removeExcludedMetricNames(LinkedHashMap<ResourceType, Set<String>> metricsWithNullValuesByType,
Map<ResourceType, String[]> excludedMetricNamesByType) {
for (Iterator<ResourceType> mapIterator = metricsWithNullValuesByType.keySet().iterator(); mapIterator
.hasNext();) {
ResourceType resourceType = mapIterator.next();
if (excludedMetricNamesByType.get(resourceType) == null) {
continue;
}
Set<String> namesOfMetricsWithNullValues = metricsWithNullValuesByType.get(resourceType);
List<String> excludedMetricNames = Arrays.asList(excludedMetricNamesByType.get(resourceType));
for (Iterator<String> setIterator = namesOfMetricsWithNullValues.iterator(); setIterator.hasNext();) {
String nameOfMetricWithNullValue = setIterator.next();
if (excludedMetricNames.contains(nameOfMetricWithNullValue)) {
setIterator.remove();
}
}
if (namesOfMetricsWithNullValues.isEmpty()) {
mapIterator.remove();
}
}
}
private void findNumericMetricsAndTraitsWithNullValuesRecursively(Resource resource,
Map<ResourceType, Set<String>> metricsWithNullValuesByType) throws Exception {
ResourceType resourceType = resource.getResourceType();
// Only check metrics on types of Resources from the plugin under test.
if (resourceType.getPlugin().equals(getPluginName())) {
ResourceContainer resourceContainer = this.pluginContainer.getInventoryManager().getResourceContainer(
resource);
if (resourceContainer.getResourceComponentState() != ResourceContainer.ResourceComponentState.STARTED) {
return;
}
Set<String> metricsWithNullValues = getNumericMetricsAndTraitsWithNullValues(resource);
if (!metricsWithNullValues.isEmpty()) {
Set<String> metricsWithNullValuesForType = metricsWithNullValuesByType.get(resourceType);
if (metricsWithNullValuesForType != null) {
metricsWithNullValuesForType.addAll(metricsWithNullValues);
} else {
metricsWithNullValuesByType.put(resourceType, metricsWithNullValues);
}
}
}
// Recurse.
for (Resource childResource : resource.getChildResources()) {
findNumericMetricsAndTraitsWithNullValuesRecursively(childResource, metricsWithNullValuesByType);
}
}
protected Set<String> getNumericMetricsAndTraitsWithNullValues(Resource resource) throws Exception {
ResourceType type = resource.getResourceType();
Set<MeasurementDefinition> numericMetricAndTraitDefs = ResourceTypeUtility.getMeasurementDefinitions(type,
new MeasurementDefinitionFilter() {
private final Set<DataType> acceptableDataTypes = EnumSet.of(DataType.MEASUREMENT, DataType.TRAIT);
@Override
public boolean accept(MeasurementDefinition metricDef) {
return acceptableDataTypes.contains(metricDef.getDataType());
}
});
Set<String> metricsWithNullValues = getMetricsWithNullValues(resource, numericMetricAndTraitDefs);
return metricsWithNullValues;
}
protected Set<String> getMetricsWithNullValues(Resource resource, Set<MeasurementDefinition> metricDefs)
throws Exception {
Set<String> metricsWithNullValues = new TreeSet<String>();
for (MeasurementDefinition metricDef : metricDefs) {
if (!metricDef.getResourceType().equals(resource.getResourceType())) {
throw new IllegalArgumentException(metricDef + " is not defined by " + resource.getResourceType());
}
Object value;
switch (metricDef.getDataType()) {
case MEASUREMENT:
value = collectNumericMetric(resource, metricDef.getName());
break;
case TRAIT:
value = collectTrait(resource, metricDef.getName());
break;
default:
throw new IllegalArgumentException("Unsupported metric type: " + metricDef.getDataType());
}
if (value == null) {
metricsWithNullValues.add(metricDef.getName());
}
}
return metricsWithNullValues;
}
@Nullable
protected Double collectNumericMetric(Resource resource, String metricName) throws Exception {
System.out.println("=== Collecting numeric metric [" + metricName + "] for " + resource + "...");
MeasurementReport report = collectMetric(resource, metricName);
Double value;
if (report.getNumericData().isEmpty()) {
assertEquals(
report.getTraitData().size(),
0,
"Metric [" + metricName + "] for Resource type " + resource.getResourceType()
+ " is defined as a numeric metric, but the plugin returned one or more traits!: "
+ report.getTraitData());
assertEquals(report.getCallTimeData().size(), 0,
"Metric [" + metricName + "] for Resource type " + resource.getResourceType()
+ " is defined as a numeric metric, but the plugin returned one or more call-time metrics!: "
+ report.getCallTimeData());
value = null;
} else {
assertEquals(report.getNumericData().size(), 1,
"Requested a single metric, but plugin returned more than one datum: " + report.getNumericData());
MeasurementDataNumeric datum = report.getNumericData().iterator().next();
assertEquals(datum.getName(), metricName, "Numeric metric [" + metricName + "] for Resource type "
+ resource.getResourceType() + " was requested, but the plugin returned a numeric metric with name ["
+ datum.getName() + "] and value [" + datum.getValue() + "]!");
// Normalize NaN or infinite to null, as the PC does.
value = (datum.getValue().isNaN() || datum.getValue().isInfinite()) ? null : datum.getValue();
}
System.out.println("====== Collected numeric metric [" + metricName + "] with value of [" + value + "] for "
+ resource + ".");
return value;
}
// **************************** RESOURCE CONFIG ************************** //
@Test(priority = 3)
public void testAllResourceConfigsLoad() throws Exception {
assertAllResourceConfigsLoad();
}
protected void assertAllResourceConfigsLoad() throws Exception {
Resource platform = this.pluginContainer.getInventoryManager().getPlatform();
Map<ResourceType, Exception> resourceConfigLoadExceptionsByType = new LinkedHashMap<ResourceType, Exception>();
findResourceConfigsThatFailToLoadRecursively(platform, resourceConfigLoadExceptionsByType);
assertTrue(resourceConfigLoadExceptionsByType.isEmpty(), "Resource configs that failed to load by type: "
+ resourceConfigLoadExceptionsByType);
}
private void findResourceConfigsThatFailToLoadRecursively(Resource resource,
Map<ResourceType, Exception> resourceConfigLoadExceptionsByType) throws Exception {
ResourceType resourceType = resource.getResourceType();
// Only check resource configs on types of Resources from the plugin under test.
if (resourceType.getPlugin().equals(getPluginName())
&& (resourceType.getResourceConfigurationDefinition() != null)
&& !resourceConfigLoadExceptionsByType.containsKey(resourceType)) {
ResourceContainer resourceContainer = this.pluginContainer.getInventoryManager().getResourceContainer(
resource);
if (resourceContainer.getResourceComponentState() != ResourceContainer.ResourceComponentState.STARTED) {
return;
}
Exception exception = null;
try {
Configuration resourceConfig = loadResourceConfiguration(resource);
List<String> validationErrors = ConfigurationUtility.validateConfiguration(resourceConfig,
resourceType.getResourceConfigurationDefinition());
if (!validationErrors.isEmpty()) {
exception = new Exception("Resource config is not valid: " + validationErrors.toString());
}
} catch (Exception e) {
exception = e;
}
if (exception != null) {
resourceConfigLoadExceptionsByType.put(resourceType, exception);
}
}
// Recurse.
for (Resource childResource : resource.getChildResources()) {
findResourceConfigsThatFailToLoadRecursively(childResource, resourceConfigLoadExceptionsByType);
}
}
}