/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat Middleware LLC, 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.model.test; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALTERNATIVES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NILLABLE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REPLY_PROPERTIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REQUEST_PROPERTIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REQUIRED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.TreeSet; import java.util.regex.Pattern; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.transform.OperationTransformer.TransformedOperation; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; import org.junit.Assert; import org.w3c.dom.Document; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSInput; import org.w3c.dom.ls.LSParser; import org.w3c.dom.ls.LSSerializer; /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ public class ModelTestUtils { private static final Pattern EXPRESSION_PATTERN = Pattern.compile(".*\\$\\{.*\\}.*"); /** * Read the classpath resource with the given name and return its contents as a string. Hook to * for reading in classpath resources for subsequent parsing. The resource is loaded using similar * semantics to {@link Class#getResource(String)} * * @param name the name of the resource * @return the contents of the resource as a string * @throws IOException */ public static String readResource(final Class<?> clazz, final String name) throws IOException { URL configURL = clazz.getResource(name); Assert.assertNotNull(name + " url is null", configURL); BufferedReader reader = new BufferedReader(new InputStreamReader(configURL.openStream(), StandardCharsets.UTF_8)); StringWriter writer = new StringWriter(); try { String line; while ((line = reader.readLine()) != null) { writer.write(line); } } finally { reader.close(); } return writer.toString(); } /** * Checks that the result was successful and gets the real result contents * * @param result the result to check * @return the result contents */ public static ModelNode checkResultAndGetContents(ModelNode result) { checkOutcome(result); Assert.assertTrue("could not check for result as its missing! look for yourself here [" + result.toString() + "] and result.hasDefined(RESULT) returns " + result.hasDefined(RESULT) , result.hasDefined(RESULT)); return result.get(RESULT); } /** * Checks that the result was successful * * @param result the result to check * @return the result contents */ public static ModelNode checkOutcome(ModelNode result) { boolean success = SUCCESS.equals(result.get(OUTCOME).asString()); Assert.assertTrue(result.get(FAILURE_DESCRIPTION).asString(), success); return result; } /** * Checks that the operation failes * * @param result the result to check * @return the failure desciption contents */ public static ModelNode checkFailed(ModelNode result) { Assert.assertEquals(FAILED, result.get(OUTCOME).asString()); return result.get(FAILURE_DESCRIPTION); } public static void validateModelDescriptions(PathAddress address, ImmutableManagementResourceRegistration reg) { ModelNode description = reg.getModelDescription(PathAddress.EMPTY_ADDRESS).getModelDescription(Locale.getDefault()); ModelNode attributes = description.get(ATTRIBUTES); Set<String> regAttributeNames = reg.getAttributeNames(PathAddress.EMPTY_ADDRESS); Set<String> attributeNames = new HashSet<String>(); if (attributes.isDefined()) { if (attributes.asList().size() != regAttributeNames.size()) { for (Property p : attributes.asPropertyList()) { attributeNames.add(p.getName()); } if (regAttributeNames.size() > attributeNames.size()) { regAttributeNames.removeAll(attributeNames); Assert.fail("More attributes defined on resource registration than in description, missing: " + regAttributeNames + " for " + address); } else if (regAttributeNames.size() < attributeNames.size()) { attributeNames.removeAll(regAttributeNames); Assert.fail("More attributes defined in description than on resource registration, missing: " + attributeNames + " for " + address); } } Map<String, ModelNode> attrMap = new LinkedHashMap<>(); for (Property p : attributes.asPropertyList()) { attrMap.put(p.getName(), p.getValue()); } attributeNames = attrMap.keySet(); if (!attributeNames.containsAll(regAttributeNames)) { Set<String> missDesc = new HashSet<String>(attributeNames); missDesc.removeAll(regAttributeNames); Set<String> missReg = new HashSet<String>(regAttributeNames); missReg.removeAll(attributeNames); if (!missReg.isEmpty()) { Assert.fail("There are different attributes defined on resource registration than in description, registered only on Resource Reg: " + missReg + " for " + address); } if (!missDesc.isEmpty()) { Assert.fail("There are different attributes defined on resource registration than in description, registered only int description: " + missDesc + " for " + address); } } for (Map.Entry<String, ModelNode> entry : attrMap.entrySet()) { validateRequiredNillable(ATTRIBUTE + " " + entry.getKey(), entry.getValue()); } } if (description.hasDefined(OPERATIONS)) { ModelNode operations = description.get(OPERATIONS); // TODO compare operation descriptions to the MRR (e.g. same names) for (Property property : operations.asPropertyList()) { ModelNode opDesc = property.getValue(); if (opDesc.hasDefined(REQUEST_PROPERTIES)) { String prefix = "operation " + property.getName() + " param "; for (Property param : opDesc.get(REQUEST_PROPERTIES).asPropertyList()) { validateRequiredNillable(prefix + param.getName(), param.getValue()); } } if (opDesc.hasDefined(REPLY_PROPERTIES)) { String prefix = "operation " + property.getName() + " reply field "; for (Property field : opDesc.get(REPLY_PROPERTIES).asPropertyList()) { validateRequiredNillable(prefix + field.getName(), field.getValue()); } } } } for (PathElement pe : reg.getChildAddresses(PathAddress.EMPTY_ADDRESS)) { ImmutableManagementResourceRegistration sub = reg.getSubModel(PathAddress.pathAddress(pe)); validateModelDescriptions(address.append(pe), sub); } } private static void validateRequiredNillable(String name, ModelNode desc) { Assert.assertTrue(name + " does not have 'required' metadata", desc.hasDefined(REQUIRED)); Assert.assertEquals(name + " does not have boolean 'required' metadata", ModelType.BOOLEAN, desc.get(REQUIRED).getType()); Assert.assertTrue(name + " does not have 'nillable' metadata", desc.hasDefined(NILLABLE)); Assert.assertEquals(name + " does not have boolean 'nillable' metadata", ModelType.BOOLEAN, desc.get(NILLABLE).getType()); boolean alternatives = false; if (desc.hasDefined(ALTERNATIVES)) { Assert.assertEquals(name + " does not have 'alternatives' metadata in list form", ModelType.LIST, desc.get(ALTERNATIVES).getType()); alternatives = desc.get(ALTERNATIVES).asInt() > 0; } boolean required = desc.get(REQUIRED).asBoolean(); Assert.assertEquals(name + " does not have correct 'nillable' metadata. required: " + required + " -- alternatives: " + desc.get(ALTERNATIVES), !required || alternatives, desc.get("nillable").asBoolean()); } /** * Compares two models to make sure that they are the same * * @param node1 the first model * @param node2 the second model */ public static void compare(ModelNode node1, ModelNode node2) { compare(node1, node2, false); } /** * Resolve two models and compare them to make sure that they have same content after expression resolution * * @param node1 the first model * @param node2 the second model */ public static void resolveAndCompareModels(ModelNode node1, ModelNode node2) { compare(node1.resolve(), node2.resolve(), false, true, new Stack<String>()); } /** * Compares two models to make sure that they are the same * * @param node1 the first model * @param node2 the second model * @param ignoreUndefined {@code true} if keys containing undefined nodes should be ignored */ public static void compare(ModelNode node1, ModelNode node2, boolean ignoreUndefined) { compare(node1, node2, ignoreUndefined, false, new Stack<String>()); } /** * Normalize and pretty-print XML so that it can be compared using string * compare. The following code does the following: - Removes comments - * Makes sure attributes are ordered consistently - Trims every element - * Pretty print the document * * @param xml The XML to be normalized * @return The equivalent XML, but now normalized */ public static String normalizeXML(String xml) throws Exception { // Remove all white space adjoining tags ("trim all elements") xml = xml.replaceAll("\\s*<", "<"); xml = xml.replaceAll(">\\s*", ">"); DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); DOMImplementationLS domLS = (DOMImplementationLS) registry.getDOMImplementation("LS"); LSParser lsParser = domLS.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); LSInput input = domLS.createLSInput(); input.setStringData(xml); Document document = lsParser.parse(input); LSSerializer lsSerializer = domLS.createLSSerializer(); lsSerializer.getDomConfig().setParameter("comments", Boolean.FALSE); lsSerializer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); return lsSerializer.writeToString(document); } /** * Validate the marshalled xml without adjusting the namespaces for the original and marshalled xml. * * @param original the original subsystem xml * @param marshalled the marshalled subsystem xml * @throws Exception */ public static void compareXml(final String original, final String marshalled) throws Exception { compareXml(original, marshalled, false); } /** * Validate the marshalled xml without adjusting the namespaces for the original and marshalled xml. * * @param original the original subsystem xml * @param marshalled the marshalled subsystem xml * @param ignoreNamespace if {@code true} the subsystem's namespace is ignored, otherwise it is taken into account when comparing the normalized xml. * @throws Exception */ public static void compareXml(final String original, final String marshalled, final boolean ignoreNamespace) throws Exception { final String xmlOriginal; final String xmlMarshalled; if (ignoreNamespace) { xmlOriginal = removeNamespace(original); xmlMarshalled = removeNamespace(marshalled); } else { xmlOriginal = original; xmlMarshalled = marshalled; } Assert.assertEquals(normalizeXML(xmlOriginal), normalizeXML(xmlMarshalled)); } public static ModelNode getSubModel(ModelNode model, PathElement pathElement) { return model.get(pathElement.getKey(), pathElement.getValue()); } public static ModelNode getSubModel(ModelNode model, PathAddress pathAddress) { for (PathElement pathElement : pathAddress) { model = getSubModel(model, pathElement); } return model; } /** * Scans for entries of type STRING containing expression formatted strings. This is to trap where parsers * call ModelNode.set("${A}") when ModelNode.setExpression("${A}) should have been used * * @param model the model to check */ public static void scanForExpressionFormattedStrings(ModelNode model) { if (model.getType().equals(ModelType.STRING)) { if (EXPRESSION_PATTERN.matcher(model.asString()).matches()) { Assert.fail("ModelNode with type==STRING contains an expression formatted string: " + model.asString()); } } else if (model.getType() == ModelType.OBJECT) { for (String key : model.keys()) { final ModelNode child = model.get(key); scanForExpressionFormattedStrings(child); } } else if (model.getType() == ModelType.LIST) { List<ModelNode> list = model.asList(); for (ModelNode entry : list) { scanForExpressionFormattedStrings(entry); } } else if (model.getType() == ModelType.PROPERTY) { Property prop = model.asProperty(); scanForExpressionFormattedStrings(prop.getValue()); } } private static String removeNamespace(String xml) { int start = xml.indexOf(" xmlns=\""); int end = xml.indexOf('"', start + "xmlns=\"".length() + 1); if (start != -1) { StringBuilder sb = new StringBuilder(xml.substring(0, start)); sb.append(xml.substring(end + 1)); return sb.toString(); } return xml; } private static void compare(ModelNode node1, ModelNode node2, boolean ignoreUndefined, boolean ignoreType, Stack<String> stack) { if (! ignoreType) { Assert.assertEquals(getCompareStackAsString(stack) + " types", node1.getType(), node2.getType()); } if (node1.getType() == ModelType.OBJECT) { ModelNode model1 = ignoreUndefined ? trimUndefinedChildren(node1) : node1; ModelNode model2 = ignoreUndefined ? trimUndefinedChildren(node2) : node2; final Set<String> keys1 = new TreeSet<String>(model1.keys()); final Set<String> keys2 = new TreeSet<String>(model2.keys()); // compare string representations of the keys to help see the difference if (!keys1.toString().equals(keys2.toString())){ //Just to make debugging easier System.out.print(""); } Assert.assertEquals(getCompareStackAsString(stack) + ": " + node1 + "\n" + node2, keys1.toString(), keys2.toString()); Assert.assertTrue(keys1.containsAll(keys2)); for (String key : keys1) { final ModelNode child1 = model1.get(key); Assert.assertTrue("Missing: " + key + "\n" + node1 + "\n" + node2, model2.has(key)); final ModelNode child2 = model2.get(key); if (child1.isDefined()) { if (!ignoreUndefined) { Assert.assertTrue(getCompareStackAsString(stack) + " key=" + key + "\n with child1 \n" + child1.toString() + "\n has child2 not defined\n node2 is:\n" + node2.toString(), child2.isDefined()); } stack.push(key + "/"); compare(child1, child2, ignoreUndefined, ignoreType, stack); stack.pop(); } else if (!ignoreUndefined) { Assert.assertFalse(getCompareStackAsString(stack) + " key=" + key + "\n with child1 undefined has child2 \n" + child2.asString(), child2.isDefined()); } } } else if (node1.getType() == ModelType.LIST) { List<ModelNode> list1 = node1.asList(); List<ModelNode> list2 = node2.asList(); Assert.assertEquals(list1 + "\n" + list2, list1.size(), list2.size()); for (int i = 0; i < list1.size(); i++) { stack.push(i + "/"); compare(list1.get(i), list2.get(i), ignoreUndefined, ignoreType, stack); stack.pop(); } } else if (node1.getType() == ModelType.PROPERTY) { Property prop1 = node1.asProperty(); Property prop2 = node2.asProperty(); Assert.assertEquals(prop1 + "\n" + prop2, prop1.getName(), prop2.getName()); stack.push(prop1.getName() + "/"); compare(prop1.getValue(), prop2.getValue(), ignoreUndefined, ignoreType, stack); stack.pop(); } else { Assert.assertEquals(getCompareStackAsString(stack) + "\n\"" + node1.asString() + "\"\n\"" + node2.asString() + "\"\n-----", node1.asString().trim(), node2.asString().trim()); } } private static ModelNode trimUndefinedChildren(ModelNode model) { ModelNode copy = model.clone(); for (String key : new HashSet<String>(copy.keys())) { if (!copy.hasDefined(key)) { copy.remove(key); } else if (copy.get(key).getType() == ModelType.OBJECT) { boolean undefined = true; for (ModelNode mn : model.get(key).asList()) { Property p = mn.asProperty(); if (p.getValue().getType() != ModelType.OBJECT) { continue; } for (String subKey : new HashSet<String>(p.getValue().keys())) { if (copy.get(key, p.getName()).hasDefined(subKey)) { undefined = false; break; } else { copy.get(key, p.getName()).remove(subKey); } } if (undefined) { copy.get(key).remove(p.getName()); if (!copy.hasDefined(key)) { copy.remove(key); } else if (copy.get(key).getType() == ModelType.OBJECT) { //this is stupid workaround if (copy.get(key).keys().size() == 0) { copy.remove(key); } } } } } } return copy; } private static String getCompareStackAsString(Stack<String> stack) { String result = ""; for (String element : stack) { result += element; } return result; } public static void checkModelAgainstDefinition(final ModelNode model, ManagementResourceRegistration rr) { checkModelAgainstDefinition(model, rr, new Stack<PathElement>()); } private static void checkModelAgainstDefinition(final ModelNode model, ManagementResourceRegistration rr, Stack<PathElement> stack) { final Set<String> children = rr.getChildNames(PathAddress.EMPTY_ADDRESS); final Set<String> attributeNames = rr.getAttributeNames(PathAddress.EMPTY_ADDRESS); for (ModelNode el : model.asList()) { String name = el.asProperty().getName(); ModelNode value = el.asProperty().getValue(); if (attributeNames.contains(name)) { AttributeAccess aa = rr.getAttributeAccess(PathAddress.EMPTY_ADDRESS, name); Assert.assertNotNull(getComparePathAsString(stack) + " Attribute " + name + " is not known", aa); AttributeDefinition ad = aa.getAttributeDefinition(); if (!value.isDefined()) { // check if the attribute definition allows null *or* if its default value is null Assert.assertTrue(getComparePathAsString(stack) + " Attribute " + name + " does not allow null", (!ad.isRequired() || ad.getDefaultValue() == null)); } else { // Assert.assertEquals("Attribute '" + name + "' type mismatch", value.getType(), ad.getType()); //todo re-enable this check } try { if (ad.isRequired() && value.isDefined()){ ad.getValidator().validateParameter(name, value); } } catch (OperationFailedException e) { Assert.fail(getComparePathAsString(stack) + " validation for attribute '" + name + "' failed, " + e.getFailureDescription().asString()); } } else if (!children.contains(name)) { Assert.fail(getComparePathAsString(stack) + " Element '" + name + "' is not known in target definition"); } } for (PathElement pe : rr.getChildAddresses(PathAddress.EMPTY_ADDRESS)) { if (pe.isWildcard()) { if (children.contains(pe.getKey()) && model.hasDefined(pe.getKey())) { for (ModelNode v : model.get(pe.getKey()).asList()) { String name = v.asProperty().getName(); ModelNode value = v.asProperty().getValue(); ManagementResourceRegistration sub = rr.getSubModel(PathAddress.pathAddress(pe)); Assert.assertNotNull(getComparePathAsString(stack) + " Child with name '" + name + "' not found", sub); if (value.isDefined()) { stack.push(pe); checkModelAgainstDefinition(value, sub, stack); stack.pop(); } } } } else { if (children.contains(pe.getKey()) && model.hasDefined(pe.getKey()) && model.get(pe.getKey()).hasDefined(pe.getValue())) { String name = pe.getValue(); ModelNode value = model.get(pe.getKeyValuePair()); ManagementResourceRegistration sub = rr.getSubModel(PathAddress.pathAddress(pe)); Assert.assertNotNull(getComparePathAsString(stack) + " Child with name '" + name + "' not found", sub); if (value.isDefined()) { stack.push(pe); checkModelAgainstDefinition(value, sub, stack); stack.pop(); } } } } } private static String getComparePathAsString(Stack<PathElement> stack) { PathAddress pa = PathAddress.EMPTY_ADDRESS; for (PathElement element : stack) { pa = pa.append(element); } return pa.toModelNode().asString(); } /** * <P>A standard test for transformers where things should be rejected. * Takes the operations and installs them in the main controller. * </P> * <P> * It then attempts to transform the same operations for the legacy controller, validating that expected * failures take place. * It then attempts to fix the operations so they can be executed in the legacy controller, since if an 'add' fails, * there could be adds for children later in the list. * </P> * <P> * Internally the operations (both for the main and legacy controllers) are added to a composite so that we have * everything we need if any versions of the subsystem use capabilities and requirements. Normally this composite * will contain the original operations that have been fixed by the {@code config}. This composite is then transformed * before executing it in the legacy controller. However, in some extreme cases the one-shot transformation of the * composite intended for the legacy controller may not be possible. For these cases you can call * {@link FailedOperationTransformationConfig#setDontTransformComposite()} and the individually transformed operations * get added to the composite, which is then used as-is (without any transformation). * </P> * <P> * To configure a callback that gets executed before the composite is transformed for the legacy controller, * and executed there, you can call * {@link FailedOperationTransformationConfig#setCallback(FailedOperationTransformationConfig.BeforeExecuteCompositeCallback)}. * </P> * * @param mainServices The main controller services * @param modelVersion The version of the legacy controller * @param operations the operations * @param config the config */ public static void checkFailedTransformedBootOperations(ModelTestKernelServices<?> mainServices, ModelVersion modelVersion, List<ModelNode> operations, FailedOperationTransformationConfig config) throws OperationFailedException { //Create a composite to execute all the boot operations in the main controller. //We execute the operations in the main controller to make sure it is a valid config in the main controller //The reason this needs to be a composite is that an add operation for a resource might have a reference on //a capability introduced by a later add operation, and the controller has already started final ModelNode mainComposite = Util.createEmptyOperation(COMPOSITE, PathAddress.EMPTY_ADDRESS); final ModelNode mainSteps = mainComposite.get(STEPS).setEmptyList(); for (ModelNode op : operations) { mainSteps.add(op.clone()); } Assert.assertEquals(operations, mainSteps.asList()); ModelTestUtils.checkOutcome(mainServices.executeOperation(mainComposite)); //Next check the transformations of the operations for the legacy controller, fixing them up from the config when //they are rejected. //We gather all the transformed operations into a composite which we then execute in the legacy controller. The //reason this is a composite is the same as for the main controller. final ModelNode legacyComposite = Util.createEmptyOperation(COMPOSITE, PathAddress.EMPTY_ADDRESS); final ModelNode legacySteps = legacyComposite.get(STEPS).setEmptyList(); //We also check all the attributes by executing write operations after we have created the legacy composite. //Let's gather these while checking rejections of the ops, and creating the legacy composite. final List<ModelNode> writeOps = new ArrayList<>(); for (ModelNode op : operations) { writeOps.addAll(config.createWriteAttributeOperations(op)); checkFailedTransformedAddOperation(mainServices, modelVersion, op, config, legacySteps); } config.invokeCallback(); if (config.isTransformComposite()) { //Transform and execute the composite TransformedOperation transformedComposite = mainServices.transformOperation(modelVersion, legacyComposite); if (transformedComposite.rejectOperation(successResult())) { Assert.fail(transformedComposite.getFailureDescription()); } mainServices.executeOperation(modelVersion, transformedComposite); } else { //The composite already contains the transformed operations ModelTestKernelServices<?> legacyServices = mainServices.getLegacyServices(modelVersion); ModelTestUtils.checkOutcome(legacyServices.executeOperation(legacyComposite)); } //Check all the write ops for (ModelNode writeOp : writeOps) { checkFailedTransformedWriteAttributeOperation(mainServices, modelVersion, writeOp, config); } } private static void checkFailedTransformedAddOperation( ModelTestKernelServices<?> mainServices, ModelVersion modelVersion, ModelNode operation, FailedOperationTransformationConfig config, ModelNode legacySteps) throws OperationFailedException { TransformedOperation transformedOperation = mainServices.transformOperation(modelVersion, operation.clone()); if (config.expectFailed(operation)) { Assert.assertTrue("Expected transformation to get rejected " + operation + " for version " + modelVersion, transformedOperation.rejectOperation(successResult())); Assert.assertNotNull("Expected transformation to get rejected " + operation + " for version " + modelVersion , transformedOperation.getFailureDescription()); if (config.canCorrectMore(operation)) { checkFailedTransformedAddOperation(mainServices, modelVersion, config.correctOperation(operation), config, legacySteps); } } else if (config.expectDiscarded(operation)) { Assert.assertNull("Expected null transformed operation for discarded " + operation, transformedOperation.getTransformedOperation()); Assert.assertFalse("Expected transformation to not be rejected for discarded " + operation, transformedOperation.rejectOperation(successResult())); } else { if (transformedOperation.rejectOperation(successResult())) { Assert.fail(operation + " should not have been rejected " + transformedOperation.getFailureDescription()); } Assert.assertFalse(config.canCorrectMore(operation)); config.operationDone(operation); if (config.isTransformComposite()) { //Add the original operation here since the legacy composite as a whole // will be transformed by checkFailedTransformedBootOperations() legacySteps.add(operation); } else { ModelNode transformed = transformedOperation.getTransformedOperation(); if (transformed != null) { legacySteps.add(transformed); } } } } private static void checkFailedTransformedWriteAttributeOperation(ModelTestKernelServices<?> mainServices, ModelVersion modelVersion, ModelNode operation, FailedOperationTransformationConfig config) throws OperationFailedException { TransformedOperation transformedOperation = mainServices.transformOperation(modelVersion, operation.clone()); if (config.expectFailedWriteAttributeOperation(operation)) { Assert.assertNotNull("Expected transformation to get rejected " + operation, transformedOperation.getFailureDescription()); //For write-attribute we currently only correct once, all in one go checkFailedTransformedWriteAttributeOperation(mainServices, modelVersion, config.correctWriteAttributeOperation(operation), config); } else { ModelNode result = mainServices.executeOperation(modelVersion, transformedOperation); Assert.assertEquals("Failed: " + operation + "\n: " + result, SUCCESS, result.get(OUTCOME).asString()); } } private static ModelNode successResult() { final ModelNode result = new ModelNode(); result.get(ModelDescriptionConstants.OUTCOME).set(ModelDescriptionConstants.SUCCESS); result.get(ModelDescriptionConstants.RESULT); return result; } }