/*
* 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.core.model.test;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILDREN;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_CONTROLLER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FIXED_PORT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FIXED_SOURCE_PORT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE_ALIASES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE_DEFAULTS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INHERITED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INTERFACE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MAJOR_VERSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MICRO_VERSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MINOR_VERSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_SUBSYSTEM_ENDPOINT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MASTER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MODEL_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAMESPACES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PLATFORM_MBEAN;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ONLY;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RECURSIVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELEASE_CODENAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELEASE_VERSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOTE_DESTINATION_OUTBOUND_SOCKET_BINDING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SCHEMA_LOCATIONS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SYSTEM_PROPERTIES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
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.ProcessType;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.RunningModeControl;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.extension.RuntimeHostControllerInfoAccessor;
import org.jboss.as.core.model.bridge.impl.LegacyControllerKernelServicesProxy;
import org.jboss.as.core.model.bridge.local.ScopedKernelServicesBootstrap;
import org.jboss.as.host.controller.HostRunningModeControl;
import org.jboss.as.host.controller.RestartMode;
import org.jboss.as.host.controller.operations.LocalDomainControllerAddHandler;
import org.jboss.as.host.controller.operations.RemoteDomainControllerAddHandler;
import org.jboss.as.model.test.ChildFirstClassLoaderBuilder;
import org.jboss.as.model.test.ModelFixer;
import org.jboss.as.model.test.ModelTestBootOperationsBuilder;
import org.jboss.as.model.test.ModelTestControllerVersion;
import org.jboss.as.model.test.ModelTestModelDescriptionValidator;
import org.jboss.as.model.test.ModelTestModelDescriptionValidator.ValidationConfiguration;
import org.jboss.as.model.test.ModelTestModelDescriptionValidator.ValidationFailure;
import org.jboss.as.model.test.ModelTestOperationValidatorFilter;
import org.jboss.as.model.test.ModelTestOperationValidatorFilter.Action;
import org.jboss.as.model.test.ModelTestUtils;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLMapper;
import org.junit.Assert;
import org.wildfly.legacy.test.spi.Version;
/**
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public class CoreModelTestDelegate {
public static final ModelTestModelDescriptionValidator.AttributeOrParameterArbitraryDescriptorValidator ARBITRARY_DESCRIPTOR_VALIDATOR = (ModelType currentType, ModelNode currentNode, String descriptor) -> null;
private static final Set<PathAddress> EMPTY_RESOURCE_ADDRESSES = new HashSet<PathAddress>();
private static final Set<PathAddress> MISSING_NAME_ADDRESSES = new HashSet<PathAddress>();
static {
EMPTY_RESOURCE_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(PROFILE)));
EMPTY_RESOURCE_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY), PathElement.pathElement(DEPLOYMENT)));
EMPTY_RESOURCE_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(SERVER_GROUP),
PathElement.pathElement(DEPLOYMENT_OVERLAY), PathElement.pathElement(DEPLOYMENT)));
MISSING_NAME_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(PROFILE)));
MISSING_NAME_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(INTERFACE)));
MISSING_NAME_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(PATH)));
MISSING_NAME_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT)));
MISSING_NAME_ADDRESSES.add(PathAddress.pathAddress(PathElement.pathElement(SERVER_GROUP), PathElement.pathElement(DEPLOYMENT)));
}
private final Class<?> testClass;
private final List<KernelServices> kernelServices = new ArrayList<KernelServices>();
//Gets set by TransformersTestParameterized for transformers tests. Non transformers tests do not set this
private volatile ClassloaderParameter currentTransformerClassloaderParameter;
public CoreModelTestDelegate(Class<?> testClass) {
this.testClass = testClass;
}
void initializeParser() throws Exception {
//Initialize the parser
}
void setCurrentTransformerClassloaderParameter(ClassloaderParameter parameter) {
ClassloaderParameter current = currentTransformerClassloaderParameter;
if (current != null) {
if (current != parameter) {
//Clear the cached classloader
current.setClassLoader(null);
currentTransformerClassloaderParameter = parameter;
}
} else {
currentTransformerClassloaderParameter = parameter;
}
}
void cleanup() throws Exception {
for (KernelServices kernelServices : this.kernelServices) {
try {
kernelServices.shutdown();
} catch (Exception e) {
}
}
kernelServices.clear();
}
protected KernelServicesBuilder createKernelServicesBuilder(TestModelType type) {
return new KernelServicesBuilderImpl(type);
}
private void validateDescriptionProviders(TestModelType type, KernelServices kernelServices,
Map<ModelNode, Map<String, Set<String>>> attributeDescriptors,
Map<ModelNode, Map<String, Map<String, Set<String>>>> operationParameterDescriptors) {
ModelNode op = new ModelNode();
op.get(OP).set(READ_RESOURCE_DESCRIPTION_OPERATION);
op.get(OP_ADDR).setEmptyList();
op.get(RECURSIVE).set(true);
op.get(INHERITED).set(false);
op.get(OPERATIONS).set(true);
op.get(INCLUDE_ALIASES).set(true);
ModelNode result = kernelServices.executeOperation(op);
if (result.hasDefined(FAILURE_DESCRIPTION)) {
throw new RuntimeException(result.get(FAILURE_DESCRIPTION).toString());
}
ModelNode model = result.get(RESULT);
if (type == TestModelType.HOST) {
//TODO (1)
//Big big hack to get around the fact that the tests install the host description twice
//we're only interested in the host model anyway
//See KnownIssuesValidator.createHostPlatformMBeanAddress
model = model.require(CHILDREN).require(HOST).require(MODEL_DESCRIPTION).require(MASTER);
}
//System.out.println(model);
ValidationConfiguration config = KnownIssuesValidationConfiguration.createAndFixupModel(type, model);
for(Map.Entry<ModelNode, Map<String, Set<String>>> attributeDescriptor : attributeDescriptors.entrySet()) {
ModelNode address = attributeDescriptor.getKey();
Map<String, Set<String>> descriptors = attributeDescriptor.getValue();
for(Map.Entry<String, Set<String>> descriptor : descriptors.entrySet()) {
String attributeName = descriptor.getKey();
Set<String> arbitraryAttributeDescriptors = descriptor.getValue();
for(String arbitraryAttributeDescriptor : arbitraryAttributeDescriptors) {
config.registerAttributeArbitraryDescriptor(address, attributeName, arbitraryAttributeDescriptor, ARBITRARY_DESCRIPTOR_VALIDATOR);
}
}
}
for (Map.Entry<ModelNode, Map<String, Map<String, Set<String>>>> operationParameterDescriptor : operationParameterDescriptors.entrySet()) {
ModelNode address = operationParameterDescriptor.getKey();
Map<String, Map<String, Set<String>>> operationDescriptors = operationParameterDescriptor.getValue();
for (Map.Entry<String, Map<String, Set<String>>> operationDescriptor : operationDescriptors.entrySet()) {
String operationName = operationDescriptor.getKey();
Map<String, Set<String>> parameterDescriptors = operationDescriptor.getValue();
for (Map.Entry<String, Set<String>> parameterDescriptor : parameterDescriptors.entrySet()) {
String parameter = parameterDescriptor.getKey();
Set<String> arbitraryAttributeDescriptors = parameterDescriptor.getValue();
for (String arbitraryAttributeDescriptor : arbitraryAttributeDescriptors) {
config.registerArbitraryDescriptorForOperationParameter(address, operationName, parameter, arbitraryAttributeDescriptor, ARBITRARY_DESCRIPTOR_VALIDATOR);
}
}
}
}
ModelTestModelDescriptionValidator validator = new ModelTestModelDescriptionValidator(PathAddress.EMPTY_ADDRESS.toModelNode(), model, config);
List<ValidationFailure> validationMessages = validator.validateResources();
if (validationMessages.size() > 0) {
final StringBuilder builder = new StringBuilder("VALIDATION ERRORS IN MODEL:");
for (ValidationFailure failure : validationMessages) {
builder.append(failure);
builder.append("\n");
}
Assert.fail("Failed due to validation errors in the model. Please fix :-) " + builder.toString());
}
}
/**
* Checks that the transformed model is the same as the model built up in the legacy subsystem controller via the transformed operations,
* and that the transformed model is valid according to the resource definition in the legacy subsystem controller.
*
* @param kernelServices the main kernel services
* @param modelVersion the model version of the targeted legacy subsystem
* @param legacyModelFixer use to touch up the model read from the legacy controller, use sparingly when the legacy model is just wrong. May be {@code null}
* @return the whole model of the legacy controller
*/
ModelNode checkCoreModelTransformation(KernelServices kernelServices, ModelVersion modelVersion, ModelFixer legacyModelFixer, ModelFixer transformedModelFixer) throws IOException {
KernelServices legacyServices = kernelServices.getLegacyServices(modelVersion);
//Only read the model without any defaults
ModelNode op = new ModelNode();
op.get(OP_ADDR).setEmptyList();
op.get(OP).set(READ_RESOURCE_OPERATION);
op.get(RECURSIVE).set(true);
op.get(INCLUDE_DEFAULTS).set(false);
ModelNode legacyModel;
try {
legacyModel = legacyServices.executeForResult(op);
} catch (OperationFailedException e) {
throw new RuntimeException(e);
}
//Work around known problem where the recursice :read-resource on legacy controllers in ModelVersion < 1.4.0
//incorrectly does not propagate include-defaults=true when recursing
//https://issues.jboss.org/browse/AS7-6077
removeDefaultAttributesWronglyShowingInRecursiveReadResource(modelVersion, legacyServices, legacyModel);
//1) Check that the transformed model is the same as the whole model read from the legacy controller.
//The transformed model is done via the resource transformers
//The model in the legacy controller is built up via transformed operations
ModelNode transformed = kernelServices.readTransformedModel(modelVersion);
adjustUndefinedInTransformedToEmpty(modelVersion, legacyModel, transformed);
if (legacyModelFixer != null) {
legacyModel = legacyModelFixer.fixModel(legacyModel);
}
if (transformedModelFixer != null) {
transformed = transformedModelFixer.fixModel(transformed);
}
//TODO temporary hacks
temporaryHack(transformed, legacyModel);
ModelTestUtils.compare(legacyModel, transformed, true);
//2) Check that the transformed model is valid according to the resource definition in the legacy subsystem controller
//ResourceDefinition rd = TransformerRegistry.loadSubsystemDefinition(mainSubsystemName, modelVersion);
//ManagementResourceRegistration rr = ManagementResourceRegistration.Factory.create(rd);
//ModelTestUtils.checkModelAgainstDefinition(transformed, rr);
return legacyModel;
}
private void temporaryHack(ModelNode transformedModel, ModelNode legacyModel) {
if (legacyModel.hasDefined(NAMESPACES) && !transformedModel.hasDefined(NAMESPACES)) {
if (legacyModel.get(NAMESPACES).asList().isEmpty()) {
legacyModel.get(NAMESPACES).set(new ModelNode());
}
}
if (legacyModel.hasDefined(SCHEMA_LOCATIONS) && !transformedModel.hasDefined(SCHEMA_LOCATIONS)) {
if (legacyModel.get(SCHEMA_LOCATIONS).asList().isEmpty()) {
legacyModel.get(SCHEMA_LOCATIONS).set(new ModelNode());
}
}
//We will test these in mixed-domain instead since something differs in the test setup for these attributes
legacyModel.remove(MANAGEMENT_MAJOR_VERSION);
legacyModel.remove(MANAGEMENT_MINOR_VERSION);
legacyModel.remove(MANAGEMENT_MICRO_VERSION);
legacyModel.remove(NAME);
legacyModel.remove(RELEASE_CODENAME);
legacyModel.remove(RELEASE_VERSION);
transformedModel.remove(MANAGEMENT_MAJOR_VERSION);
transformedModel.remove(MANAGEMENT_MINOR_VERSION);
transformedModel.remove(MANAGEMENT_MICRO_VERSION);
transformedModel.remove(NAME);
transformedModel.remove(RELEASE_CODENAME);
transformedModel.remove(RELEASE_VERSION);
}
private void removeDefaultAttributesWronglyShowingInRecursiveReadResource(ModelVersion modelVersion, KernelServices legacyServices, ModelNode legacyModel) {
if (modelVersion.getMajor() == 1 && modelVersion.getMinor() < 4) {
//Work around known problem where the recursice :read-resource on legacy controllers in ModelVersion < 1.4.0
//incorrectly does not propagate include-defaults=true when recursing
//https://issues.jboss.org/browse/AS7-6077
checkAttributeIsActuallyDefinedAndReplaceIfNot(legacyServices, legacyModel, MANAGEMENT_SUBSYSTEM_ENDPOINT, SERVER_GROUP);
checkAttributeIsActuallyDefinedAndReplaceIfNot(legacyServices, legacyModel, READ_ONLY, PATH);
removeDefaultAttributesWronglyShowingInRecursiveReadResourceInSocketBindingGroup(modelVersion, legacyServices, legacyModel);
}
}
private void removeDefaultAttributesWronglyShowingInRecursiveReadResourceInSocketBindingGroup(ModelVersion modelVersion, KernelServices legacyServices, ModelNode legacyModel) {
if (legacyModel.hasDefined(SOCKET_BINDING_GROUP)) {
for (Property prop : legacyModel.get(SOCKET_BINDING_GROUP).asPropertyList()) {
if (prop.getValue().isDefined()) {
checkAttributeIsActuallyDefinedAndReplaceIfNot(legacyServices, legacyModel, FIXED_SOURCE_PORT, SOCKET_BINDING_GROUP, prop.getName(), REMOTE_DESTINATION_OUTBOUND_SOCKET_BINDING);
checkAttributeIsActuallyDefinedAndReplaceIfNot(legacyServices, legacyModel, FIXED_SOURCE_PORT, SOCKET_BINDING_GROUP, prop.getName(), LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING);
checkAttributeIsActuallyDefinedAndReplaceIfNot(legacyServices, legacyModel, FIXED_PORT, SOCKET_BINDING_GROUP, prop.getName(), SOCKET_BINDING);
}
}
}
}
private void adjustUndefinedInTransformedToEmpty(ModelVersion modelVersion, ModelNode legacyModel, ModelNode transformed) {
boolean is7_1_x = ModelVersion.compare(ModelVersion.create(1, 4, 0), modelVersion) < 0;
for (PathAddress address : EMPTY_RESOURCE_ADDRESSES) {
harmonizeModel(modelVersion, legacyModel, transformed, address, ModelHarmonizer.UNDEFINED_TO_EMPTY);
}
if (!is7_1_x) {
for (PathAddress address : MISSING_NAME_ADDRESSES) {
harmonizeModel(modelVersion, legacyModel, transformed, address, ModelHarmonizer.MISSING_NAME);
}
}
}
private void harmonizeModel(ModelVersion modelVersion, ModelNode legacyModel, ModelNode transformed,
PathAddress address, ModelHarmonizer harmonizer) {
if (address.size() > 0) {
PathElement pathElement = address.getElement(0);
if (legacyModel.hasDefined(pathElement.getKey()) && transformed.hasDefined(pathElement.getKey())) {
ModelNode legacyType = legacyModel.get(pathElement.getKey());
ModelNode transformedType = transformed.get(pathElement.getKey());
PathAddress childAddress = address.size() > 1 ? address.subAddress(1) : PathAddress.EMPTY_ADDRESS;
if (pathElement.isWildcard()) {
for (String key : legacyType.keys()) {
if (transformedType.has(key)) {
harmonizeModel(modelVersion, legacyType.get(key),
transformedType.get(key), childAddress, harmonizer);
}
}
} else {
harmonizeModel(modelVersion, legacyType.get(pathElement.getValue()),
transformedType.get(pathElement.getValue()), address, harmonizer);
}
}
} else {
harmonizer.harmonizeModel(modelVersion, legacyModel, transformed);
}
}
private void checkAttributeIsActuallyDefinedAndReplaceIfNot(KernelServices legacyServices, ModelNode legacyModel, String attributeName, String...parentAddress) {
ModelNode parentNode = legacyModel;
for (String s : parentAddress) {
if (parentNode.hasDefined(s)) {
parentNode = parentNode.get(s);
} else {
return;
}
if (!parentNode.isDefined()) {
return;
}
}
for (Property prop : parentNode.asPropertyList()) {
if (prop.getValue().isDefined()) {
ModelNode attribute = parentNode.get(prop.getName(), attributeName);
if (attribute.isDefined()) {
//Attribute is defined in the legacy model - remove it if that is not the case
ModelNode op = new ModelNode();
op.get(OP).set(READ_ATTRIBUTE_OPERATION);
for (int i = 0 ; i < parentAddress.length ; i ++) {
if (i < parentAddress.length -1) {
op.get(OP_ADDR).add(parentAddress[i], parentAddress[++i]);
} else {
op.get(OP_ADDR).add(parentAddress[i], prop.getName());
}
}
op.get(NAME).set(attributeName);
op.get(INCLUDE_DEFAULTS).set(false);
try {
ModelNode result = legacyServices.executeForResult(op);
if (!result.isDefined()) {
attribute.set(new ModelNode());
}
} catch (OperationFailedException e) {
//TODO this might get thrown because the attribute does not exist in the legacy model?
//In which case it should perhaps be undefined
throw new RuntimeException(e);
}
}
}
}
}
private class KernelServicesBuilderImpl implements KernelServicesBuilder, ModelTestBootOperationsBuilder.BootOperationParser {
private final TestModelType type;
private final ModelTestBootOperationsBuilder bootOperationBuilder = new ModelTestBootOperationsBuilder(testClass, this);
private final TestParser testParser;
private ProcessType processType;
private ModelInitializer modelInitializer;
private ModelWriteSanitizer modelWriteSanitizer;
private boolean validateDescription;
private boolean validateOperations = true;
private XMLMapper xmlMapper = XMLMapper.Factory.create();
private Map<ModelVersion, LegacyKernelServicesInitializerImpl> legacyControllerInitializers = new HashMap<ModelVersion, LegacyKernelServicesInitializerImpl>();
private List<String> contentRepositoryContents = new ArrayList<String>();
private final RunningModeControl runningModeControl;
ExtensionRegistry extensionRegistry;
private final Map<ModelNode, Map<String, Set<String>>> attributeDescriptors = new HashMap<>();
private Map<ModelNode, Map<String, Map<String, Set<String>>>> operationParameterDescriptors = new HashMap<>();
public KernelServicesBuilderImpl(TestModelType type) {
this.type = type;
this.processType = type == TestModelType.HOST || type == TestModelType.DOMAIN ? ProcessType.HOST_CONTROLLER : ProcessType.STANDALONE_SERVER;
runningModeControl = type == TestModelType.HOST ? new HostRunningModeControl(RunningMode.ADMIN_ONLY, RestartMode.HC_ONLY) : new RunningModeControl(RunningMode.ADMIN_ONLY);
extensionRegistry = new ExtensionRegistry(processType, runningModeControl, null, null, null, RuntimeHostControllerInfoAccessor.SERVER);
testParser = TestParser.create(extensionRegistry, xmlMapper, type);
}
public KernelServicesBuilder validateDescription() {
this.validateDescription = true;
return this;
}
@Override
public KernelServicesBuilder setXmlResource(String resource) throws IOException, XMLStreamException {
bootOperationBuilder.setXmlResource(resource);
return this;
}
@Override
public KernelServicesBuilder setXml(String subsystemXml) throws XMLStreamException {
bootOperationBuilder.setXml(subsystemXml);
return this;
}
@Override
public KernelServicesBuilder setBootOperations(List<ModelNode> bootOperations) {
bootOperationBuilder.setBootOperations(bootOperations);
return this;
}
@Override
public List<ModelNode> parseXml(String xml) throws Exception {
ModelTestBootOperationsBuilder builder = new ModelTestBootOperationsBuilder(testClass, this);
builder.setXml(xml);
return builder.build();
}
@Override
public List<ModelNode> parseXmlResource(String xmlResource) throws Exception {
ModelTestBootOperationsBuilder builder = new ModelTestBootOperationsBuilder(testClass, this);
builder.setXmlResource(xmlResource);
return builder.build();
}
@Override
public KernelServicesBuilder setModelInitializer(ModelInitializer modelInitializer, ModelWriteSanitizer modelWriteSanitizer) {
bootOperationBuilder.validateNotAlreadyBuilt();
this.modelInitializer = modelInitializer;
this.modelWriteSanitizer = modelWriteSanitizer;
testParser.addModelWriteSanitizer(modelWriteSanitizer);
return this;
}
@Override
public KernelServicesBuilder createContentRepositoryContent(String hash) {
contentRepositoryContents.add(hash);
return this;
}
public KernelServices build() throws Exception {
bootOperationBuilder.validateNotAlreadyBuilt();
List<ModelNode> bootOperations = bootOperationBuilder.build();
if (type == TestModelType.HOST) {
adjustLocalDomainControllerWriteForHost(bootOperations);
}
AbstractKernelServicesImpl kernelServices = AbstractKernelServicesImpl.create(processType, runningModeControl, ModelTestOperationValidatorFilter.createValidateAll(), bootOperations, testParser, null, type, modelInitializer, extensionRegistry, contentRepositoryContents);
CoreModelTestDelegate.this.kernelServices.add(kernelServices);
if (validateDescription) {
validateDescriptionProviders(type, kernelServices, attributeDescriptors, operationParameterDescriptors);
}
ModelTestUtils.validateModelDescriptions(PathAddress.EMPTY_ADDRESS, kernelServices.getRootRegistration());
ModelNode model = kernelServices.readWholeModel();
model = removeForIntellij(model);
ModelTestUtils.scanForExpressionFormattedStrings(model);
for (Map.Entry<ModelVersion, LegacyKernelServicesInitializerImpl> entry : legacyControllerInitializers.entrySet()) {
LegacyKernelServicesInitializerImpl legacyInitializer = entry.getValue();
List<ModelNode> transformedBootOperations;
if (legacyInitializer.isDontUseBootOperations()) {
transformedBootOperations = Collections.emptyList();
} else {
transformedBootOperations = new ArrayList<ModelNode>();
for (ModelNode op : bootOperations) {
ModelNode transformed = kernelServices.transformOperation(entry.getKey(), op).getTransformedOperation();
if (transformed != null) {
transformedBootOperations.add(transformed);
}
}
}
LegacyControllerKernelServicesProxy legacyServices = legacyInitializer.install(kernelServices, modelInitializer, modelWriteSanitizer, contentRepositoryContents, transformedBootOperations);
kernelServices.addLegacyKernelService(entry.getKey(), legacyServices);
}
return kernelServices;
}
private void adjustLocalDomainControllerWriteForHost(List<ModelNode> bootOperations) {
//Remove the write-local-domain-controller operation since the test controller already simulates what it does at runtime.
//For model validation to work, it always needs to be part of the model, and will be added there by the test controller.
//We need to keep track of if it was part of the boot ops or not. If it was not part of the boot ops, the model will need
//'sanitising' to remove it otherwise the xml comparison checks will fail.
boolean dcInBootOps = false;
for (Iterator<ModelNode> it = bootOperations.iterator() ; it.hasNext() ; ) {
ModelNode op = it.next();
String opName = op.get(OP).asString();
if (opName.equals(LocalDomainControllerAddHandler.OPERATION_NAME) || opName.equals(RemoteDomainControllerAddHandler.OPERATION_NAME)) {
dcInBootOps = true;
break;
}
if(WRITE_ATTRIBUTE_OPERATION.equals(opName)) {
String attributeName = op.get(NAME).asString();
if(DOMAIN_CONTROLLER.equals(attributeName)) {
dcInBootOps = true;
break;
}
}
}
if (!dcInBootOps) {
testParser.addModelWriteSanitizer(new ModelWriteSanitizer() {
@Override
public ModelNode sanitize(ModelNode model) {
if (model.isDefined() && model.has(DOMAIN_CONTROLLER)) {
model.remove(DOMAIN_CONTROLLER);
}
return model;
}
});
}
}
private ModelNode removeForIntellij(ModelNode model){
//When running in intellij it includes
// "-Dorg.jboss.model.test.maven.repository.urls=${org.jboss.model.test.maven.repository.urls}"
//in the runtime platform-mbeans's arguments for the host controller so it fails the scan for expression
//formatted strings. Simply remove it.
//Also do the same for the following system property in the runtime platform mbean system properties:
//"org.jboss.model.test.maven.repository.urls" => "${org.jboss.model.test.maven.repository.urls}"
ModelNode runtime = findModelNode(model, HOST, "master", CORE_SERVICE, PLATFORM_MBEAN, TYPE, "runtime");
if (runtime.isDefined()){
runtime.remove("input-arguments");
if (runtime.hasDefined(SYSTEM_PROPERTIES)) {
ModelNode properties = runtime.get(SYSTEM_PROPERTIES);
properties.remove("org.jboss.model.test.maven.repository.urls");
}
}
return model;
}
private ModelNode findModelNode(ModelNode model, String...name){
ModelNode currentModel = model;
for (String part : name){
if (!currentModel.hasDefined(part)){
return new ModelNode();
} else {
currentModel = currentModel.get(part);
}
}
return currentModel;
}
@Override
public List<ModelNode> parse(String xml) throws XMLStreamException {
final XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(xml));
final List<ModelNode> operationList = new ArrayList<ModelNode>();
xmlMapper.parseDocument(operationList, reader);
return operationList;
}
@Override
public LegacyKernelServicesInitializer createLegacyKernelServicesBuilder(ModelVersion modelVersion, ModelTestControllerVersion testControllerVersion) {
if (type != TestModelType.DOMAIN) {
throw new IllegalStateException("Can only create legacy kernel services for DOMAIN.");
}
LegacyKernelServicesInitializerImpl legacyKernelServicesInitializerImpl = new LegacyKernelServicesInitializerImpl(modelVersion, testControllerVersion);
legacyControllerInitializers.put(modelVersion, legacyKernelServicesInitializerImpl);
return legacyKernelServicesInitializerImpl;
}
@Override
public KernelServicesBuilder setDontValidateOperations() {
validateOperations = true;
return this;
}
@Override
public KernelServicesBuilder registerAttributeArbitraryDescriptor(ModelNode address, String name, String descriptor) {
if(!attributeDescriptors.containsKey(address)) {
attributeDescriptors.put(address, new HashMap<>());
}
if(!attributeDescriptors.get(address).containsKey(name)) {
attributeDescriptors.get(address).put(name, new HashSet<>());
}
attributeDescriptors.get(address).get(name).add(descriptor);
return this;
}
@Override
public KernelServicesBuilder registerArbitraryDescriptorForOperationParameter(ModelNode address, String operation, String parameter, String descriptor) {
if(!operationParameterDescriptors.containsKey(address)) {
operationParameterDescriptors.put(address, new HashMap<>());
}
if(!operationParameterDescriptors.get(address).containsKey(operation)) {
operationParameterDescriptors.get(address).put(operation, new HashMap<>());
}
if(!operationParameterDescriptors.get(address).get(operation).containsKey(parameter)) {
operationParameterDescriptors.get(address).get(operation).put(parameter, new HashSet<>());
}
operationParameterDescriptors.get(address).get(operation).get(parameter).add(descriptor);
return this;
}
}
private class LegacyKernelServicesInitializerImpl implements LegacyKernelServicesInitializer {
private final ChildFirstClassLoaderBuilder classLoaderBuilder;
private final ModelVersion modelVersion;
private final List<LegacyModelInitializerEntry> modelInitializerEntries = new ArrayList<LegacyModelInitializerEntry>();
private final ModelTestControllerVersion testControllerVersion;
private boolean dontUseBootOperations = false;
private boolean skipReverseCheck;
private ModelFixer reverseCheckMainModelFixer;
private ModelFixer reverseCheckLegacyModelFixer;
private ModelTestOperationValidatorFilter.Builder operationValidationExcludeFilterBuilder;
LegacyKernelServicesInitializerImpl(ModelVersion modelVersion, ModelTestControllerVersion version) {
this.classLoaderBuilder = new ChildFirstClassLoaderBuilder(version.isEap());
this.modelVersion = modelVersion;
this.testControllerVersion = version;
}
private LegacyControllerKernelServicesProxy install(AbstractKernelServicesImpl mainServices, ModelInitializer modelInitializer, ModelWriteSanitizer modelWriteSanitizer, List<String> contentRepositoryContents, List<ModelNode> bootOperations) throws Exception {
if (testControllerVersion == null) {
throw new IllegalStateException();
}
if (!skipReverseCheck) {
bootCurrentVersionWithLegacyBootOperations(bootOperations, modelInitializer, modelWriteSanitizer, contentRepositoryContents, mainServices);
}
final ClassLoader legacyCl;
if (currentTransformerClassloaderParameter != null && currentTransformerClassloaderParameter.getClassLoader() != null) {
legacyCl = currentTransformerClassloaderParameter.getClassLoader();
} else {
classLoaderBuilder.addParentFirstClassPattern("org.jboss.as.core.model.bridge.shared.*");
//These two is needed or the child first classloader never gets GC'ed which causes OOMEs for very big tests
//Here the Reference$ReaperThread hangs onto the classloader
classLoaderBuilder.addParentFirstClassPattern("org.jboss.modules.*");
//Here the NDC hangs onto the classloader
classLoaderBuilder.addParentFirstClassPattern("org.jboss.logmanager.*");
classLoaderBuilder.addMavenResourceURL("org.wildfly.core:wildfly-core-model-test-framework:" + ModelTestControllerVersion.CurrentVersion.VERSION);
classLoaderBuilder.addMavenResourceURL("org.wildfly.core:wildfly-model-test:" + ModelTestControllerVersion.CurrentVersion.VERSION);
classLoaderBuilder.addMavenResourceURL("org.wildfly.legacy.test:wildfly-legacy-spi:" + Version.LEGACY_TEST_CONTROLLER_VERSION);
if (testControllerVersion != ModelTestControllerVersion.MASTER) {
String groupId = testControllerVersion.getCoreMavenGroupId();
String hostControllerArtifactId = testControllerVersion.getHostControllerMavenArtifactId();
classLoaderBuilder.addRecursiveMavenResourceURL(groupId + ":" + hostControllerArtifactId + ":" + testControllerVersion.getCoreVersion());
classLoaderBuilder.addMavenResourceURL("org.wildfly.legacy.test:wildfly-legacy-core-" + testControllerVersion.getTestControllerVersion() + ":" + Version.LEGACY_TEST_CONTROLLER_VERSION);
}
legacyCl = classLoaderBuilder.build();
if (currentTransformerClassloaderParameter != null) {
//Cache the classloader for the other tests
currentTransformerClassloaderParameter.setClassLoader(legacyCl);
}
}
ScopedKernelServicesBootstrap scopedBootstrap = new ScopedKernelServicesBootstrap(legacyCl);
LegacyControllerKernelServicesProxy legacyServices = scopedBootstrap.createKernelServices(bootOperations, getOperationValidationFilter(), modelVersion, modelInitializerEntries);
return legacyServices;
}
@Override
public LegacyKernelServicesInitializer initializerCreateModelResource(PathAddress parentAddress, PathElement relativeResourceAddress, ModelNode model, String... capabilities) {
modelInitializerEntries.add(new LegacyModelInitializerEntry(parentAddress, relativeResourceAddress, model, capabilities));
return this;
}
@Override
public LegacyKernelServicesInitializer initializerCreateModelResource(PathAddress parentAddress, PathElement relativeResourceAddress, ModelNode model) {
return initializerCreateModelResource(parentAddress, relativeResourceAddress, model, new String[0]);
}
@Override
public LegacyKernelServicesInitializer addOperationValidationExclude(String name, PathAddress pathAddress) {
addOperationValidationConfig(name, pathAddress, Action.NOCHECK);
return this;
}
@Override
public LegacyKernelServicesInitializer addOperationValidationResolve(String name, PathAddress pathAddress) {
addOperationValidationConfig(name, pathAddress, Action.RESOLVE);
return this;
}
private void addOperationValidationConfig(String name, PathAddress pathAddress, Action action) {
if (operationValidationExcludeFilterBuilder == null) {
operationValidationExcludeFilterBuilder = ModelTestOperationValidatorFilter.createBuilder();
}
operationValidationExcludeFilterBuilder.addOperation(pathAddress, name, action, null);
}
@Override
public LegacyKernelServicesInitializer setDontUseBootOperations() {
dontUseBootOperations = true;
return this;
}
boolean isDontUseBootOperations() {
return dontUseBootOperations;
}
@Override
public LegacyKernelServicesInitializer skipReverseControllerCheck() {
skipReverseCheck = true;
return this;
}
@Override
public LegacyKernelServicesInitializer configureReverseControllerCheck(ModelFixer mainModelFixer, ModelFixer legacyModelFixer) {
this.reverseCheckMainModelFixer = mainModelFixer;
this.reverseCheckLegacyModelFixer = legacyModelFixer;
return this;
}
private KernelServices bootCurrentVersionWithLegacyBootOperations(List<ModelNode> bootOperations, ModelInitializer modelInitializer, ModelWriteSanitizer modelWriteSanitizer, List<String> contentRepositoryHashes, KernelServices mainServices) throws Exception {
KernelServicesBuilder reverseServicesBuilder = createKernelServicesBuilder(TestModelType.DOMAIN)
.setBootOperations(bootOperations)
.setModelInitializer(modelInitializer, modelWriteSanitizer);
for (String hash : contentRepositoryHashes) {
reverseServicesBuilder.createContentRepositoryContent(hash);
}
KernelServices reverseServices = reverseServicesBuilder.build();
if (reverseServices.getBootError() != null) {
Throwable t = reverseServices.getBootError();
if (t instanceof Exception) {
throw (Exception)t;
}
throw new Exception(t);
}
Assert.assertTrue(reverseServices.getBootError() == null ? "error" : reverseServices.getBootError().getMessage(), reverseServices.isSuccessfulBoot());
ModelNode mainModel = mainServices.readWholeModel();
if (reverseCheckMainModelFixer != null) {
mainModel = reverseCheckMainModelFixer.fixModel(mainModel);
}
ModelNode reverseModel = reverseServices.readWholeModel();
if (reverseCheckLegacyModelFixer != null) {
reverseModel = reverseCheckLegacyModelFixer.fixModel(reverseModel);
}
ModelTestUtils.compare(mainModel, reverseModel);
return reverseServices;
}
private ModelTestOperationValidatorFilter getOperationValidationFilter() {
if (operationValidationExcludeFilterBuilder != null) {
return operationValidationExcludeFilterBuilder.build();
}
return ModelTestOperationValidatorFilter.createValidateAll();
}
}
}