/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.subsystem.test; import java.io.IOException; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Properties; import java.util.Set; import org.jboss.as.controller.Extension; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.model.test.ModelTestUtils; import org.jboss.dmr.ModelNode; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; /** * A test routine every subsystem should go through. * * @author Emanuel Muckenhuber * @author <a href="stefano.maestri@redhat.com>Stefano Maestri</a> */ public abstract class AbstractSubsystemBaseTest extends AbstractSubsystemTest { public AbstractSubsystemBaseTest(final String mainSubsystemName, final Extension mainExtension) { super(mainSubsystemName, mainExtension); } public AbstractSubsystemBaseTest(final String mainSubsystemName, final Extension mainExtension, final Comparator<PathAddress> removeOrderComparator) { super(mainSubsystemName, mainExtension, removeOrderComparator); } /** * Get the subsystem xml as string. * * @return the subsystem xml * @throws IOException */ protected abstract String getSubsystemXml() throws IOException; /** * Get the pathc of the subsystem XML Schema. * * If the returned value is not null, the subsystem's XML will be validated against these schemas in #testSchema * * By default, this method returns an null (thus disabling the #testSchema and #testSchemaOfSubsystemTemplates tests). * * Note that the XSD validation may fail if the XML contains attributes or text that uses expressions. * In that case, you will have to make sure that the corresponding expressions have resolved properties * returned by #getResolvedProperties. */ protected String getSubsystemXsdPath() throws Exception { return null; } /** * Get the paths of the subsystem XML templates (such as <code>/subsystem-templates/io.xml</code> file for the IO subsystem). * * If the returned value is not null, the template <subsystem> element will be validated against this schema * returned by #getSubsystemXsdPaths in #testSchemaOfSubsystemTemplates. * * Note that the XSD validation may fail if the XML contains attributes or text that uses expressions. * In that case, you will have to make sure that the corresponding expressions have resolved properties * returned by #getResolvedProperties. */ protected String[] getSubsystemTemplatePaths() throws IOException { return new String[0]; } /** * Return the properties to use to resolve any expressions in the subsystem's XML prior to its XSD validation. * * If the XML contains an expression instead of a valid type (e.g. a boolean or a int), the XSD validation will * fail. To make sure the XML is valid (except for those expressions), this method can be used to resolve any such * expressions in the XML before the validation process. */ protected Properties getResolvedProperties() { return new Properties(); } /** * Get the subsystem xml with the given id as a string. * <p> * This default implementation returns the result of a call to {@link #readResource(String)}. * </p> * * @param configId the id of the xml configuration * * @return the subsystem xml * @throws IOException */ protected String getSubsystemXml(String configId) throws IOException { return readResource(configId); } @Test public void testSubsystem() throws Exception { standardSubsystemTest(null); } @Test public void testSchema() throws Exception { String schemaPath = getSubsystemXsdPath(); Assume.assumeTrue("getSubsystemXsdPath() has been overridden to disable the validation of the subsystem templates", schemaPath != null); SchemaValidator.validateXML(getSubsystemXml(), schemaPath, getResolvedProperties()); } /** * To enable a test for validation of the subsystem templates, override both {@link #getSubsystemXsdPath()} and {@link #getSubsystemTemplatePaths()} * then add a new test just calling this method. */ protected void testSchemaOfSubsystemTemplates() throws Exception { String schemaPath = getSubsystemXsdPath(); Assert.assertTrue("getSubsystemXsdPath() needs to be overridden to enable the validation of the subsystem templates", schemaPath != null); String[] templates = getSubsystemTemplatePaths(); Assert.assertTrue("Override getSubsystemTemplatePaths() to activate the validation of the subsystem templates", templates != null && templates.length > 0); for (String template : templates) { String content = readResource(template); SchemaValidator.validateXML(content, "subsystem", schemaPath, getResolvedProperties()); } } /** * Tests the ability to create a model from an xml configuration, marshal the model back to xml, * re-read that marshalled model into a new model that matches the first one, execute a "describe" * operation for the model, create yet another model from executing the results of that describe * operation, and compare that model to first model. * * @param configId id to pass to {@link #getSubsystemXml(String)} to get the configuration; if {@code null} * {@link #getSubsystemXml()} will be called * * @throws Exception */ protected void standardSubsystemTest(final String configId) throws Exception { standardSubsystemTest(configId, true); } protected KernelServices standardSubsystemTest(final String configId, boolean compareXml) throws Exception { return standardSubsystemTest(configId, null, compareXml); } protected void standardSubsystemTest(final String configId, final String configIdResolvedModel) throws Exception { standardSubsystemTest(configId, configIdResolvedModel, true); } protected KernelServices standardSubsystemTest(final String configId, final String configIdResolvedModel, boolean compareXml) throws Exception { return standardSubsystemTest(configId, configIdResolvedModel, compareXml, createAdditionalInitialization()); } /** * Tests the ability to create a model from an xml configuration, marshal the model back to xml, * re-read that marshalled model into a new model that matches the first one, execute a "describe" * operation for the model, create yet another model from executing the results of that describe * operation, and compare that model to first model. * if configIdResolvedModel is not null compare the model from configId and one from configIdResolvedModel * after all expression have been reolved. * * @param configId id to pass to {@link #getSubsystemXml(String)} to get the configuration; if {@code null} * {@link #getSubsystemXml()} will be called * @param configIdResolvedModel id to pass to {@link #getSubsystemXml(String)} to get the configuration; * it is the expected result of resolve() on configId if {@code null} ] this step is skipped * * @param compareXml if {@code true} a comparison of xml output to original input is performed. This can be * set to {@code false} if the original input is from an earlier xsd and the current * schema has a different output * @param additionalInit service container and model initialization * * @throws Exception */ protected KernelServices standardSubsystemTest(final String configId, final String configIdResolvedModel, boolean compareXml, final AdditionalInitialization additionalInit) throws Exception { // Parse the subsystem xml and install into the first controller final String subsystemXml = configId == null ? getSubsystemXml() : getSubsystemXml(configId); final KernelServices servicesA = super.createKernelServicesBuilder(additionalInit).setSubsystemXml(subsystemXml).build(); Assert.assertTrue("Subsystem boot failed!", servicesA.isSuccessfulBoot()); //Get the model and the persisted xml from the first controller final ModelNode modelA = servicesA.readWholeModel(); validateModel(modelA); ModelTestUtils.scanForExpressionFormattedStrings(modelA); // Test marshaling final String marshalled = servicesA.getPersistedSubsystemXml(); servicesA.shutdown(); // validate the the normalized xmls String normalizedSubsystem = normalizeXML(subsystemXml); if (compareXml) { compareXml(configId, normalizedSubsystem, normalizeXML(marshalled)); } //Install the persisted xml from the first controller into a second controller final KernelServices servicesB = super.createKernelServicesBuilder(additionalInit).setSubsystemXml(marshalled).build(); Assert.assertTrue("Subsystem boot failed!", servicesB.isSuccessfulBoot()); final ModelNode modelB = servicesB.readWholeModel(); //Make sure the models from the two controllers are identical compare(modelA, modelB); // Test the describe operation validateDescribeOperation(servicesB, additionalInit, modelA); assertRemoveSubsystemResources(servicesB, getIgnoredChildResourcesForRemovalTest()); servicesB.shutdown(); if (configIdResolvedModel != null) { final String subsystemResolvedXml = getSubsystemXml(configIdResolvedModel); final KernelServices servicesD = super.createKernelServicesBuilder(additionalInit).setSubsystemXml(subsystemResolvedXml).build(); Assert.assertTrue("Subsystem w/ resolved xml boot failed!", servicesD.isSuccessfulBoot()); final ModelNode modelD = servicesD.readWholeModel(); validateModel(modelD); resolveandCompareModel(modelA, modelD); } return servicesA; } protected void validateDescribeOperation(KernelServices hc, AdditionalInitialization serverInit, ModelNode expectedModel) throws Exception { final ModelNode operation = createDescribeOperation(); final ModelNode result = hc.executeOperation(operation); Assert.assertTrue("the subsystem describe operation has to generate a list of operations to recreate the subsystem: " + result.asString(), !result.hasDefined(ModelDescriptionConstants.FAILURE_DESCRIPTION)); final List<ModelNode> operations = result.get(ModelDescriptionConstants.RESULT).asList(); final KernelServices servicesC = super.createKernelServicesBuilder(serverInit).setBootOperations(operations).build(); Assert.assertTrue("Subsystem boot failed!", servicesC.isSuccessfulBoot()); final ModelNode serverModel = servicesC.readWholeModel(); compare(expectedModel, serverModel); servicesC.shutdown(); } protected void validateModel(ModelNode model) { Assert.assertNotNull(model); } protected ModelNode createDescribeOperation() { final ModelNode address = new ModelNode(); address.add(ModelDescriptionConstants.SUBSYSTEM, getMainSubsystemName()); final ModelNode operation = new ModelNode(); operation.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.DESCRIBE); operation.get(ModelDescriptionConstants.OP_ADDR).set(address); return operation; } protected AdditionalInitialization createAdditionalInitialization() { return AdditionalInitialization.MANAGEMENT; } /** * Returns a set of child resources addresses that should not be removed directly. Rather they should be managed * by their parent resource. * <p/> * The last element of each address is allowed to be a wildcard address. * * @return the set of child resource addresses * @see AbstractSubsystemTest#assertRemoveSubsystemResources(KernelServices, Set) */ protected Set<PathAddress> getIgnoredChildResourcesForRemovalTest() { return Collections.emptySet(); } }