@@ -249,7 +249,7 @@ public void registerOperations(ManagementResourceRegistration resourceRegistrati
DomainService domain = new DomainService(defaultRealm, trustedSecurityDomain, identityOperator);

ServiceBuilder<SecurityDomain> domainBuilder = serviceTarget.addService(initialName, domain)
.setInitialMode(Mode.ACTIVE);
.setInitialMode(Mode.LAZY);

if (preRealmPrincipalTransformer != null) {
injectPrincipalTransformer(preRealmPrincipalTransformer, context, domainBuilder, domain.createPreRealmPrincipalTransformerInjector(preRealmPrincipalTransformer));
@@ -354,7 +354,7 @@ public void dispose() {

ServiceTarget serviceTarget = context.getServiceTarget();
ServiceBuilder<SecurityDomain> domainBuilder = serviceTarget.addService(domainName, finalDomainService)
.setInitialMode(Mode.ACTIVE);
.setInitialMode(Mode.LAZY);
domainBuilder.addDependency(initialName, SecurityDomain.class, securityDomain);
for (String trustedDomainName : trustedSecurityDomainNames) {
InjectedValue<SecurityDomain> trustedDomainInjector = new InjectedValue<>();
@@ -215,7 +215,7 @@ static ResourceDefinition getProviderHttpServerMechanismFactoryDefinition() {

static ResourceDefinition getServiceLoaderServerMechanismFactoryDefinition() {
AttributeDefinition[] attributes = new AttributeDefinition[] { MODULE };
AbstractAddStepHandler add = new TrivialAddHandler<HttpServerAuthenticationMechanismFactory>(HttpServerAuthenticationMechanismFactory.class, attributes, HTTP_SERVER_MECHANISM_FACTORY_RUNTIME_CAPABILITY) {
AbstractAddStepHandler add = new TrivialAddHandler<HttpServerAuthenticationMechanismFactory>(HttpServerAuthenticationMechanismFactory.class, ServiceController.Mode.ACTIVE, ServiceController.Mode.LAZY, attributes, HTTP_SERVER_MECHANISM_FACTORY_RUNTIME_CAPABILITY) {

@Override
protected ValueSupplier<HttpServerAuthenticationMechanismFactory> getValueSupplier(
@@ -62,6 +62,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
@@ -368,16 +369,26 @@ public void validateParameter(String parameterName, ModelNode value) throws Oper
}
}

static class HostContextMapValidator implements ParameterValidator{
// valid hosts in SNI mapping can contain letters, numbers, asterisks, dots and dashes
// dash and dot can be at most one at a time, and dash must be surrounded by [a-z0-9*]
static Pattern hostnamePattern = Pattern.compile("^([a-zA-Z0-9*]+(-[a-zA-Z0-9*]+)*\\.?)+[a-zA-Z0-9*]$");
static class HostContextMapValidator implements ParameterValidator {
// Hostnames can contain ASCII letters a-z (case-insensitive), digits 0-9, hyphens and dots.
// This pattern allows also [,],*,? characters to make regular expressions possible. Non-escaped dot represents any character, escaped dot is delimeter.
static Pattern hostnameRegexPattern = Pattern.compile("[0-9a-zA-Z\\[.*]" + // first character can be digit, letter, left square bracket, non-escaped dot or asterisk
"([0-9a-zA-Z*.\\[\\]?-]" + // any combination of digits, letters, asterisks, non-escaped dots, square brackets, question marks and hyphens
"|" + // OR
"(?<!\\\\\\.)\\\\\\.)*" + // if there is an escaped dot, there cannot be another escaped dot right behind it
// backslash must be escaped, so '\\\\' translates to literally slash, and '\\.' translates to literally dot
"[0-9a-zA-Z*.\\[\\]?]"); // escaped dot or hyphen cannot be at the end

@Override
public void validateParameter(String parameterName, ModelNode value) throws OperationFailedException {
if (value.isDefined()) {
for (String hostname : value.keys()) {
if (!hostnamePattern.matcher(hostname).matches()) {
if (!hostnameRegexPattern.matcher(hostname).matches()) {
throw ROOT_LOGGER.invalidHostContextMapValue(hostname);
}
try {
Pattern.compile(hostname); // make sure the input is valid regex as well (eg. will check that the square brackets are paired)
} catch (PatternSyntaxException exception) {
throw ROOT_LOGGER.invalidHostContextMapValue(hostname);
}
}
@@ -942,7 +953,7 @@ static ResourceDefinition getServerSSLContextDefinition(boolean serverOrHostCont
PRE_REALM_PRINCIPAL_TRANSFORMER, POST_REALM_PRINCIPAL_TRANSFORMER, FINAL_PRINCIPAL_TRANSFORMER, REALM_MAPPER,
providersDefinition, PROVIDER_NAME};

AbstractAddStepHandler add = new TrivialAddHandler<SSLContext>(SSLContext.class, ServiceController.Mode.ACTIVE, attributes, SSL_CONTEXT_RUNTIME_CAPABILITY) {
AbstractAddStepHandler add = new TrivialAddHandler<SSLContext>(SSLContext.class, ServiceController.Mode.ACTIVE, ServiceController.Mode.LAZY, attributes, SSL_CONTEXT_RUNTIME_CAPABILITY) {

@Override
protected ValueSupplier<SSLContext> getValueSupplier(ServiceBuilder<SSLContext> serviceBuilder,
@@ -28,6 +28,8 @@
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
@@ -46,16 +48,22 @@

private final RuntimeCapability<?> runtimeCapability;
private final Mode initialMode;
private final Mode embeddedInitialMode;

TrivialAddHandler(Class<T> serviceType, AttributeDefinition[] attributes, RuntimeCapability<?> runtimeCapability) {
this(serviceType, Mode.ACTIVE, attributes, runtimeCapability);
}

TrivialAddHandler(Class<T> serviceType, Mode initialMode, AttributeDefinition[] attributes, RuntimeCapability<?> runtimeCapability) {
this(serviceType, initialMode, initialMode, attributes, runtimeCapability);
}

TrivialAddHandler(Class<T> serviceType, Mode initialMode, Mode embeddedInitialMode, AttributeDefinition[] attributes, RuntimeCapability<?> runtimeCapability) {
super(new HashSet<>(Collections.singletonList(checkNotNullParam("runtimeCapabilities", runtimeCapability))), attributes);
this.runtimeCapability = runtimeCapability;
checkNotNullParam("serviceType", serviceType);
this.initialMode = checkNotNullParam("initialMode", initialMode);
this.embeddedInitialMode = checkNotNullParam("embeddedInitialMode", embeddedInitialMode);
}

@SuppressWarnings("unchecked")
@@ -70,7 +78,7 @@ protected final void performRuntime(OperationContext context, ModelNode operatio
trivialService.setValueSupplier(getValueSupplier(serviceBuilder, context, resource.getModel()));

installedForResource(commonDependencies(serviceBuilder, dependOnProperties(), dependOnProviderRegistration())
.setInitialMode(initialMode)
.setInitialMode(!context.isBooting() && context.getProcessType() == ProcessType.EMBEDDED_SERVER && context.getRunningMode() == RunningMode.ADMIN_ONLY ? embeddedInitialMode : initialMode)
.install(), resource);
}

@@ -557,6 +557,6 @@
@Message(id = 1060, value = "Fileless KeyStore needs to have a defined type.")
OperationFailedException filelessKeyStoreMissingType();

@Message(id = 1061, value = "Value of host context map '%s' is not a valid hostname.")
@Message(id = 1061, value = "Invalid value of host context map: '%s' is not valid hostname pattern.")
OperationFailedException invalidHostContextMapValue(String hostname);
}
@@ -84,6 +84,12 @@ private void init() throws Exception {
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}

TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "MyDomain");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "X500Domain");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "X500DomainTwo");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "X500DomainThree");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "AnotherDomain");
}

@Test
@@ -110,6 +110,9 @@ public void testReadSecurityDomainIdentity() throws Exception {
KernelServices services = createKernelServicesBuilder(null)
.setSubsystemXmlResource("identity-management.xml")
.build();

TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "FileSystemDomain");

PathAddress securityDomainAddress = getSecurityDomainAddress("FileSystemDomain");
String principalName = "plainUser";
ModelNode operation = createAddIdentityOperation(getSecurityRealmAddress("FileSystemRealm"), principalName);
@@ -48,6 +48,7 @@ public void testPrincipalTransformerTree() throws Exception {
Assert.fail(services.getBootError().toString());
}

TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestingDomain");
ServiceName serviceName = Capabilities.PRINCIPAL_TRANSFORMER_RUNTIME_CAPABILITY.getCapabilityServiceName("tree");
PrincipalTransformer transformer = (PrincipalTransformer) services.getContainer().getService(serviceName).getValue();
Assert.assertNotNull(transformer);
@@ -34,6 +34,8 @@
* @author <a href="mailto:mmazanek@redhat.com">Martin Mazanek</a>
*/
public class RoleMappersTestCase extends AbstractSubsystemBaseTest {
private KernelServices services = null;

public RoleMappersTestCase() {
super(ElytronExtension.SUBSYSTEM_NAME, new ElytronExtension());
}
@@ -43,13 +45,22 @@ protected String getSubsystemXml() throws IOException {
return readResource("role-mappers-test.xml");
}

@Test
public void testMappedRoleMapper() throws Exception {
KernelServices services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
private void init(String... domainsToActivate) throws Exception {
services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}

TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain1");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain2");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain3");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain4");
}

@Test
public void testMappedRoleMapper() throws Exception {
init("TestDomain1");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain1");
Assert.assertNotNull(services.getContainer());
Assert.assertNotNull(services.getContainer().getService(serviceName));
@@ -73,10 +84,7 @@ public void testMappedRoleMapper() throws Exception {

@Test
public void testKeepMappedRoleMapper() throws Exception {
KernelServices services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}
init("TestDomain2");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain2");
Assert.assertNotNull(services.getContainer());
@@ -101,10 +109,7 @@ public void testKeepMappedRoleMapper() throws Exception {

@Test
public void testKeepNonMappedRoleMapper() throws Exception {
KernelServices services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}
init("TestDomain3");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain3");
Assert.assertNotNull(services.getContainer());
@@ -129,10 +134,7 @@ public void testKeepNonMappedRoleMapper() throws Exception {

@Test
public void testKeepBothMappedRoleMapper() throws Exception {
KernelServices services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}
init("TestDomain4");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain4");
Assert.assertNotNull(services.getContainer());
@@ -19,8 +19,13 @@

import mockit.Mock;
import mockit.MockUp;

import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.subsystem.test.AdditionalInitialization;
import org.jboss.as.subsystem.test.ControllerInitializer;
import org.jboss.as.subsystem.test.KernelServices;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.wildfly.security.x500.cert.BasicConstraintsExtension;
import org.wildfly.security.x500.cert.SelfSignedX509CertificateAndSigningKey;
import org.wildfly.security.x500.cert.X509CertificateBuilder;
@@ -198,4 +203,10 @@ static void mockCallerModuleClassloader() {
new ClassLoadingAttributeDefinitionsMock();
}

static void activateService(KernelServices services, RuntimeCapability capability, String... dynamicNameElements) throws InterruptedException {
ServiceName serviceName = capability.getCapabilityServiceName(dynamicNameElements);
ServiceController<?> serviceController = services.getContainer().getService(serviceName);
serviceController.setMode(ServiceController.Mode.ACTIVE);
serviceController.awaitValue();
}
}
@@ -312,7 +312,7 @@
<server-ssl-sni-contexts>
<server-ssl-sni-context name="sni" default-ssl-context="server">
<sni-mapping host="server" ssl-context="server" />
<sni-mapping host="*.server" ssl-context="server2" />
<sni-mapping host=".*\.server" ssl-context="server2" />
</server-ssl-sni-context>
</server-ssl-sni-contexts>
</tls>
@@ -299,7 +299,7 @@
<server-ssl-sni-contexts>
<server-ssl-sni-context name="sni" default-ssl-context="server">
<sni-mapping host="server" ssl-context="server" />
<sni-mapping host="*.server" ssl-context="server2" />
<sni-mapping host=".*\.server" ssl-context="server2" />
</server-ssl-sni-context>
</server-ssl-sni-contexts>
</tls>
@@ -299,7 +299,7 @@
<server-ssl-sni-contexts>
<server-ssl-sni-context name="sni" default-ssl-context="server">
<sni-mapping host="server" ssl-context="server" />
<sni-mapping host="*.server" ssl-context="server2" />
<sni-mapping host=".*\.server" ssl-context="server2" />
</server-ssl-sni-context>
</server-ssl-sni-contexts>
</tls>
@@ -29,6 +29,9 @@
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.operations.global.GlobalOperationHandlers.STD_READ_OPS;
import static org.jboss.as.controller.operations.global.GlobalOperationHandlers.STD_WRITE_OPS;

import java.util.Collections;
import java.util.HashSet;
@@ -43,6 +46,7 @@
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.as.domain.controller.LocalHostControllerInfo;
import org.jboss.as.domain.controller.logging.DomainControllerLogger;
import org.jboss.as.host.controller.logging.HostControllerLogger;
import org.jboss.dmr.ModelNode;

/**
@@ -55,19 +59,22 @@
static OperationRouting determineRouting(OperationContext context, ModelNode operation,
final LocalHostControllerInfo localHostControllerInfo, Set<String> hostNames) throws OperationFailedException {
final ImmutableManagementResourceRegistration rootRegistration = context.getRootResourceRegistration();
return determineRouting(operation, localHostControllerInfo, rootRegistration, hostNames);
return determineRouting(operation, localHostControllerInfo, rootRegistration, hostNames, false);
}

private static OperationRouting determineRouting(final ModelNode operation, final LocalHostControllerInfo localHostControllerInfo,
final ImmutableManagementResourceRegistration rootRegistration, Set<String> hostNames) throws OperationFailedException {
final ImmutableManagementResourceRegistration rootRegistration,
final Set<String> hostNames, final boolean compositeStep) throws OperationFailedException {
HostControllerLogger.ROOT_LOGGER.tracef("Determining routing for %s", operation);
final PathAddress address = PathAddress.pathAddress(operation.get(OP_ADDR));
final String operationName = operation.require(OP).asString();
final Set<OperationEntry.Flag> operationFlags = resolveOperationFlags(address, operationName, rootRegistration);
final Set<OperationEntry.Flag> operationFlags = resolveOperationFlags(address, operationName, rootRegistration, compositeStep);
return determineRouting(operation, address, operationName, operationFlags, localHostControllerInfo, rootRegistration, hostNames);
}

private static Set<OperationEntry.Flag> resolveOperationFlags(final PathAddress address, final String operationName,
final ImmutableManagementResourceRegistration rootRegistration) throws OperationFailedException {
final ImmutableManagementResourceRegistration rootRegistration,
final boolean compositeStep) throws OperationFailedException {
Set<OperationEntry.Flag> result = null;
boolean validAddress = false;

@@ -81,6 +88,25 @@ private static OperationRouting determineRouting(final ModelNode operation, fina
validAddress = true;
OperationEntry opE = targetReg.getOperationEntry(PathAddress.EMPTY_ADDRESS, operationName);
result = opE == null ? null : opE.getFlags();
} else if (compositeStep) {
// WFCORE-323. This could be a subsystem step in a composite where an earlier step adds
// the extension. So the registration of the subsystem would not be done yet.
// See if we can figure out flags usable for routing.
PathAddress subsystemRoot = findSubsystemRootAddress(address);
if (subsystemRoot != null // else this isn't for a subsystem
// Only bother if the subsystem root is not registered.
// If the root is registered then the was already added
&& (address.equals(subsystemRoot) || rootRegistration.getSubModel(subsystemRoot) == null)) {

if (STD_READ_OPS.contains(operationName)) {
// One of the global read ops
result = Collections.singleton(OperationEntry.Flag.READ_ONLY);
} else if (STD_WRITE_OPS.contains(operationName)) {
// One of the global write ops, or 'add' or 'remove'.
// Not read only and not allowed t
result = Collections.emptySet();
} // else we don't know what this op does so we can't provide a routing.
}
}

if (result == null) {
@@ -97,6 +123,23 @@ private static OperationRouting determineRouting(final ModelNode operation, fina
return result;
}

private static PathAddress findSubsystemRootAddress(PathAddress address) {
PathAddress result = null;
int size = address.size();
if (size > 1) {
int subsystemKey = Integer.MAX_VALUE;
String firstKey = address.getElement(0).getKey();
if (HOST.equals(firstKey) || PROFILE.equals(firstKey)) {
subsystemKey = 1;
}
if (size > subsystemKey
&& SUBSYSTEM.equals(address.getElement(subsystemKey).getKey())) {
result = subsystemKey == size - 1 ? address : address.subAddress(0, subsystemKey + 1);
}
}
return result;
}

private static OperationRouting determineRouting(final ModelNode operation,
final PathAddress address,
final String operationName,
@@ -116,8 +159,7 @@ private static OperationRouting determineRouting(final ModelNode operation,
if (HOST.equals(first.getKey())) {
if (first.isMultiTarget()) {
if (first.isWildcard()) {
targetHost = new HashSet<>();
targetHost.addAll(hostNames);
targetHost = new HashSet<>(hostNames);
targetHost.add(localHostControllerInfo.getLocalHostName());
} else {
targetHost = new HashSet<>();
@@ -147,6 +189,9 @@ else if(address.size() > 1) {
// even if the request is for a write op. A write-op to a server
// is illegal anyway, so there is no reason to handle it two-phase
routing = new OperationRouting(targetHost, false);
} else if (!ServerOperationResolver.isHostChildAddressMultiphase(address)) {
// Address does not result in changes to child processes
routing = new OperationRouting(targetHost, false);
}
}
if (routing == null) {
@@ -167,7 +212,7 @@ else if(address.size() > 1) {
boolean fwdToAllHosts = false;
boolean twoStep = false;
for (ModelNode step : operation.get(STEPS).asList()) {
OperationRouting stepRouting = determineRouting(step, localHostControllerInfo, rootRegistration, hostNames);
OperationRouting stepRouting = determineRouting(step, localHostControllerInfo, rootRegistration, hostNames, true);
if (stepRouting.isMultiphase()) {
twoStep = true;
// Make sure we don't loose the information that we have to execute the operation on all hosts
@@ -113,6 +113,25 @@
private static final AttachmentKey<ModelNode> DOMAIN_MODEL_ATTACHMENT = AttachmentKey.create(ModelNode.class);
private static final AttachmentKey<ModelNode> ORIGINAL_DOMAIN_MODEL_ATTACHMENT = AttachmentKey.create(ModelNode.class);

/**
* Gets whether the given address requires multiphase handling
* @param address an address, which most be 2 or more elements long with 'host' as the key of the first element
* @return {@code true} if the address requires multiphase handling; {@code} false if it can be handled
* directly on the target host
*/
static boolean isHostChildAddressMultiphase(PathAddress address) {
assert address.size() > 1 : "address size must be greater than 1";
assert ModelDescriptionConstants.HOST.equals(address.getElement(0).getKey()) : "Only host addresses allowed";
switch (address.getElement(1).getKey()) {
case ModelDescriptionConstants.EXTENSION:
case ModelDescriptionConstants.SUBSYSTEM:
case ModelDescriptionConstants.SERVER:
case SOCKET_BINDING_GROUP:
return false;
default:
return true;
}
}
private enum DomainKey {

UNKNOWN(null),
@@ -162,7 +162,8 @@ public void execute(final OperationContext context, final ModelNode operation) t
final PathAddress relativeAddress = domainOpAddress.subAddress(originalAddress.size());
if(! pushToServers) {
Set<OperationEntry.Flag> flags = originalRegistration.getOperationFlags(relativeAddress, domainOp.require(OP).asString());
if (flags.contains(OperationEntry.Flag.READ_ONLY)
if (flags != null
&& flags.contains(OperationEntry.Flag.READ_ONLY)
&& !flags.contains(OperationEntry.Flag.DOMAIN_PUSH_TO_SERVERS)
&& !flags.contains(OperationEntry.Flag.RUNTIME_ONLY)) {
result = Collections.emptyMap();
@@ -816,7 +816,7 @@ private static boolean equalValue(final AttributeDefinition attribute, final Ope
} else if (attribute.getName().equals(SUBHANDLERS.getName())) {
final Collection<String> resolvedValue = SUBHANDLERS.resolvePropertyValue(context, model);
final Collection<String> currentValue = configuration.getHandlerNames();
result = (resolvedValue == null ? currentValue == null : resolvedValue.containsAll(currentValue));
result = (resolvedValue.size() == currentValue.size() && resolvedValue.containsAll(currentValue));
} else if (attribute.getName().equals(PROPERTIES.getName())) {
result = true;
final PropertyConfigurable propertyConfigurable;
14 pom.xml
@@ -117,7 +117,7 @@
<!-- wildfly plugin-->
<version.org.wildfly.plugin>1.2.1.Final</version.org.wildfly.plugin>
<!-- plugins related to wildfly build and tooling -->
<version.org.wildfly.component-matrix-plugin>1.0.2.Final</version.org.wildfly.component-matrix-plugin>
<version.org.wildfly.component-matrix-plugin>1.0.3.Final</version.org.wildfly.component-matrix-plugin>
<version.org.wildfly.plugins>2.0.0.Final</version.org.wildfly.plugins>
<version.org.zanata.plugin>3.9.1</version.org.zanata.plugin>
<version.versions.plugin>2.5</version.versions.plugin>
@@ -148,7 +148,7 @@
<version.com.jcraft.jzlib>1.1.1</version.com.jcraft.jzlib>
<version.commons-io>2.5</version.commons-io>
<version.commons-lang>2.6</version.commons-lang>
<version.io.undertow>2.0.20.Final</version.io.undertow>
<version.io.undertow>2.0.21.Final</version.io.undertow>
<version.javax.inject.javax.inject>1</version.javax.inject.javax.inject>
<version.javax.json.javax-json-api>1.1.2</version.javax.json.javax-json-api>
<version.junit>4.12</version.junit>
@@ -179,13 +179,13 @@
<version.org.jboss.logging.jboss-logging>3.4.0.Final</version.org.jboss.logging.jboss-logging>
<version.org.jboss.logging.jboss-logging-tools>2.2.0.Final</version.org.jboss.logging.jboss-logging-tools>
<version.org.jboss.logging.jul-to-slf4j-stub>1.0.1.Final</version.org.jboss.logging.jul-to-slf4j-stub>
<version.org.jboss.logmanager.jboss-logmanager>2.1.10.Final</version.org.jboss.logmanager.jboss-logmanager>
<version.org.jboss.logmanager.jboss-logmanager>2.1.11.Final</version.org.jboss.logmanager.jboss-logmanager>
<version.org.jboss.logmanager.log4j-jboss-logmanager>1.2.0.Final</version.org.jboss.logmanager.log4j-jboss-logmanager>
<version.org.jboss.marshalling.jboss-marshalling>2.0.7.Final</version.org.jboss.marshalling.jboss-marshalling>
<version.org.jboss.modules.jboss-modules>1.9.1.Final</version.org.jboss.modules.jboss-modules>
<version.org.jboss.msc.jboss-msc>1.4.5.Final</version.org.jboss.msc.jboss-msc>
<version.org.jboss.remoting>5.0.9.Final</version.org.jboss.remoting>
<version.org.jboss.remotingjmx.remoting-jmx>3.0.2.Final</version.org.jboss.remotingjmx.remoting-jmx>
<version.org.jboss.remoting>5.0.10.Final</version.org.jboss.remoting>
<version.org.jboss.remotingjmx.remoting-jmx>3.0.3.Final</version.org.jboss.remotingjmx.remoting-jmx>
<version.org.jboss.shrinkwrap.shrinkwrap>1.2.6</version.org.jboss.shrinkwrap.shrinkwrap>
<version.org.jboss.slf4j.slf4j-jboss-logmanager>1.0.3.GA</version.org.jboss.slf4j.slf4j-jboss-logmanager>
<version.org.jboss.spec.javax.interceptor.jboss-interceptors-api_1.2_spec>1.0.1.Final</version.org.jboss.spec.javax.interceptor.jboss-interceptors-api_1.2_spec>
@@ -194,7 +194,7 @@
<version.org.jboss.staxmapper>1.3.0.Final</version.org.jboss.staxmapper>
<version.org.jboss.stdio>1.1.0.Final</version.org.jboss.stdio>
<version.org.jboss.threads>2.3.3.Final</version.org.jboss.threads>
<version.org.jboss.xnio>3.7.1.Final</version.org.jboss.xnio>
<version.org.jboss.xnio>3.7.2.Final</version.org.jboss.xnio>
<version.org.jboss.xnio.xnio-api>${version.org.jboss.xnio}</version.org.jboss.xnio.xnio-api>
<version.org.jboss.xnio.xnio-nio>${version.org.jboss.xnio}</version.org.jboss.xnio.xnio-nio>
<version.org.jmockit>1.39</version.org.jmockit>
@@ -216,7 +216,7 @@
<version.org.wildfly.openssl.wildfly-openssl-solaris-x86_64>${version.org.wildfly.openssl.natives}</version.org.wildfly.openssl.wildfly-openssl-solaris-x86_64>
<version.org.wildfly.openssl.wildfly-openssl-windows-i386>${version.org.wildfly.openssl.natives}</version.org.wildfly.openssl.wildfly-openssl-windows-i386>
<version.org.wildfly.openssl.wildfly-openssl-windows-x86_64>${version.org.wildfly.openssl.natives}</version.org.wildfly.openssl.wildfly-openssl-windows-x86_64>
<version.org.wildfly.security.elytron>1.9.0.Final</version.org.wildfly.security.elytron>
<version.org.wildfly.security.elytron>1.9.1.Final</version.org.wildfly.security.elytron>
<version.org.wildfly.security.elytron-web>1.5.0.Final</version.org.wildfly.security.elytron-web>
<version.xalan>2.7.1.jbossorg-2</version.xalan>
<version.xml-resolver>1.2</version.xml-resolver>
@@ -101,41 +101,41 @@ public void createConfigurationChanges(PathElement host) throws Exception {
DomainClient client = domainMasterLifecycleUtil.getDomainClient();
final ModelNode add = Util.createAddOperation(PathAddress.pathAddress().append(host).append(getAddress()));
add.get(LegacyConfigurationChangeResourceDefinition.MAX_HISTORY.getName()).set(MAX_HISTORY_SIZE);
executeForResult(client, add);
executeForResult(client, add); // 0 -- write to host subsystem
PathAddress allowedOrigins = PathAddress.pathAddress().append(host).append(ALLOWED_ORIGINS_ADDRESS);
ModelNode setAllowedOrigins = Util.createEmptyOperation("list-add", allowedOrigins);
setAllowedOrigins.get(ModelDescriptionConstants.NAME).set(ModelDescriptionConstants.ALLOWED_ORIGINS);
setAllowedOrigins.get(ModelDescriptionConstants.VALUE).set("http://www.wildfly.org");
client.execute(setAllowedOrigins);
client.execute(setAllowedOrigins); // 1 -- write to host core
PathAddress auditLogAddress = PathAddress.pathAddress().append(host).append(AUDIT_LOG_ADDRESS);
ModelNode disableLogBoot = Util.getWriteAttributeOperation(auditLogAddress, ModelDescriptionConstants.LOG_BOOT, false);
client.execute(disableLogBoot);
client.execute(disableLogBoot); // 2 -- write to host core
//read
client.execute(Util.getReadAttributeOperation(allowedOrigins, ModelDescriptionConstants.ALLOWED_ORIGINS));
//invalid operation
//invalid operation; not recorded
client.execute(Util.getUndefineAttributeOperation(allowedOrigins, "not-exists-attribute"));
//invalid operation
//invalid operation; not recorded
client.execute(Util.getWriteAttributeOperation(allowedOrigins, "not-exists-attribute", "123456"));
//write operation, failed
ModelNode setAllowedOriginsFails = Util.getWriteAttributeOperation(allowedOrigins, ModelDescriptionConstants.ALLOWED_ORIGINS, "123456"); //wrong type, expected is LIST, op list-add
client.execute(setAllowedOriginsFails);
client.execute(setAllowedOriginsFails); // 3 -- write to host core (recorded despite failure)
PathAddress systemPropertyAddress = PathAddress.pathAddress().append(host).append(SYSTEM_PROPERTY_ADDRESS);
ModelNode setSystemProperty = Util.createAddOperation(systemPropertyAddress);
setSystemProperty.get(ModelDescriptionConstants.VALUE).set("changeConfig");
client.execute(setSystemProperty);
client.execute(setSystemProperty); // 4 -- write to host core
ModelNode unsetAllowedOrigins = Util.getUndefineAttributeOperation(allowedOrigins, ModelDescriptionConstants.ALLOWED_ORIGINS);
client.execute(unsetAllowedOrigins);
client.execute(unsetAllowedOrigins); // 5 -- write to host core
ModelNode enableLogBoot = Util.getWriteAttributeOperation(auditLogAddress, ModelDescriptionConstants.LOG_BOOT, true);
client.execute(enableLogBoot);
client.execute(enableLogBoot); // 6 -- write to host core
ModelNode unsetSystemProperty = Util.createRemoveOperation(systemPropertyAddress);
client.execute(unsetSystemProperty);
client.execute(unsetSystemProperty); // 7 -- write to host core
PathAddress inMemoryAddress = PathAddress.pathAddress().append(host).append(IN_MEMORY_HANDLER_ADDRESS);
ModelNode addInMemoryHandler = Util.createAddOperation(inMemoryAddress);
client.execute(addInMemoryHandler);
client.execute(addInMemoryHandler); // 8 -- write to host core
ModelNode editInMemoryHandler = Util.getWriteAttributeOperation(inMemoryAddress, ModelDescriptionConstants.MAX_HISTORY, 50);
client.execute(editInMemoryHandler);
client.execute(editInMemoryHandler); // 9 -- write to host core
ModelNode removeInMemoryHandler = Util.createRemoveOperation(inMemoryAddress);
client.execute(removeInMemoryHandler);
client.execute(removeInMemoryHandler); // 10 -- write to host core
}

protected PathAddress removePrefix(ModelNode operation) {
@@ -51,7 +51,10 @@
import static org.junit.Assert.assertThat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.client.helpers.Operations;
@@ -74,13 +77,19 @@
@Test
public void testConfigurationChanges() throws Exception {
try {
// These first two changes don't get recorded on hosts because the host subsystem is not installed yet
createProfileConfigurationChange(DEFAULT_PROFILE, MAX_HISTORY_SIZE); // shouldn't appear on slave
createProfileConfigurationChange(OTHER_PROFILE, MAX_HISTORY_SIZE);

createConfigurationChanges(HOST_MASTER);
createConfigurationChanges(HOST_SLAVE);
checkConfigurationChanges(readConfigurationChanges(domainMasterLifecycleUtil.getDomainClient(), HOST_MASTER), 11);
checkConfigurationChanges(readConfigurationChanges(domainMasterLifecycleUtil.getDomainClient(), HOST_SLAVE), 11);
checkConfigurationChanges(readConfigurationChanges(domainSlaveLifecycleUtil.getDomainClient(), HOST_SLAVE), 11);
checkConfigurationChanges(readConfigurationChanges(domainMasterLifecycleUtil.getDomainClient(), HOST_MASTER),
11, Collections.singleton(10)); // the first op from createConfigurationChanges is
// non multi-process and gets no domain-uuid
checkConfigurationChanges(readConfigurationChanges(domainMasterLifecycleUtil.getDomainClient(), HOST_SLAVE),
11, Collections.emptySet()); // All ops routed from master to slave get a domain-uuid
checkConfigurationChanges(readConfigurationChanges(domainSlaveLifecycleUtil.getDomainClient(), HOST_SLAVE),
11, Collections.emptySet()); // All ops routed from master to slave get a domain-uuid

setConfigurationChangeMaxHistory(domainMasterLifecycleUtil.getDomainClient(), HOST_MASTER, 19);
checkMaxHistorySize(domainMasterLifecycleUtil.getDomainClient(), 19, HOST_MASTER);
@@ -241,16 +250,18 @@ public void testEnablingConfigurationChangesOnHC() throws Exception {
}
}

private void checkConfigurationChanges(List<ModelNode> changes, int size) throws IOException {
private void checkConfigurationChanges(List<ModelNode> changes, int size, Set<Integer> localOnly) throws IOException {
assertThat(changes.toString(), changes.size(), is(size));
for (ModelNode change : changes) {
assertThat(change.hasDefined(OPERATION_DATE), is(true));
assertThat(change.hasDefined(USER_ID), is(false));
assertThat(change.hasDefined(DOMAIN_UUID), is(true));
assertThat(change.hasDefined(ACCESS_MECHANISM), is(true));
assertThat(change.get(ACCESS_MECHANISM).asString(), is("NATIVE"));
assertThat(change.hasDefined(REMOTE_ADDRESS), is(true));
assertThat(change.get(OPERATIONS).asList().size(), is(1));
for (int i = 0; i < size; i++) {
ModelNode change = changes.get(i);
String msg = i + " -- " + changes.toString();
assertThat(msg, change.hasDefined(OPERATION_DATE), is(true));
assertThat(msg, change.hasDefined(USER_ID), is(false));
assertThat(msg, change.hasDefined(DOMAIN_UUID), is(!localOnly.contains(i)));
assertThat(msg, change.hasDefined(ACCESS_MECHANISM), is(true));
assertThat(msg, change.get(ACCESS_MECHANISM).asString(), is("NATIVE"));
assertThat(msg, change.hasDefined(REMOTE_ADDRESS), is(true));
assertThat(msg, change.get(OPERATIONS).asList().size(), is(1));
}
validateChanges(changes);
}
@@ -260,7 +271,9 @@ private void checkSlaveConfigurationChanges( List<ModelNode> changes, int size)
for (ModelNode change : changes) {
assertThat(change.hasDefined(OPERATION_DATE), is(true));
assertThat(change.hasDefined(USER_ID), is(false));
assertThat(change.hasDefined(DOMAIN_UUID), is(true));
assertThat(change.hasDefined(DOMAIN_UUID), is(true)); // all the slave changes have a domain-uuid,
// either due to routing from the master or
// due to local need for rollout to servers
assertThat(change.hasDefined(ACCESS_MECHANISM), is(true));
assertThat(change.get(ACCESS_MECHANISM).asString(), is("NATIVE"));
assertThat(change.hasDefined(REMOTE_ADDRESS), is(true));
@@ -276,15 +289,6 @@ private void checkSlaveConfigurationChanges( List<ModelNode> changes, int size)
}

private void validateChanges(List<ModelNode> changes) throws IOException {
for (ModelNode change : changes) {
assertThat(change.hasDefined(OPERATION_DATE), is(true));
assertThat(change.hasDefined(USER_ID), is(false));
assertThat(change.hasDefined(DOMAIN_UUID), is(true));
assertThat(change.hasDefined(ACCESS_MECHANISM), is(true));
assertThat(change.get(ACCESS_MECHANISM).asString(), is("NATIVE"));
assertThat(change.hasDefined(REMOTE_ADDRESS), is(true));
assertThat(change.get(OPERATIONS).asList().size(), is(1));
}
ModelNode currentChange = changes.get(0);
assertThat(currentChange.get(OUTCOME).asString(), is(SUCCESS));
ModelNode currentChangeOp = currentChange.get(OPERATIONS).asList().get(0);
@@ -22,11 +22,31 @@

package org.jboss.as.test.integration.domain.suites;


import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEFAULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MASTER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MODULE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.List;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.helpers.domain.DomainClient;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.integration.domain.extension.ExtensionSetup;
import org.jboss.as.test.integration.domain.extension.TestExtension;
import org.jboss.as.test.integration.domain.management.util.DomainLifecycleUtil;
@@ -49,6 +69,14 @@
public class ExtensionManagementTestCase {

private static final String ADDRESS = "extension=" + TestExtension.MODULE_NAME;
private static final PathElement EXTENSION_ELEMENT = PathElement.pathElement(EXTENSION, TestExtension.MODULE_NAME);
private static final PathAddress EXTENSION_ADDRESS = PathAddress.pathAddress(EXTENSION_ELEMENT);
private static final PathElement SUBSYSTEM_ELEMENT = PathElement.pathElement(SUBSYSTEM, "1");
private static final PathAddress PROFILE_SUBSYSTEM_ADDRESS = PathAddress.pathAddress(PROFILE, DEFAULT).append(SUBSYSTEM_ELEMENT);
private static final PathAddress SERVER_ONE_SUBSYSTEM_ADDRESS = PathAddress.pathAddress(HOST, MASTER).append(SERVER, "main-one").append(SUBSYSTEM_ELEMENT);
private static final PathAddress SERVER_THREE_SUBSYSTEM_ADDRESS = PathAddress.pathAddress(HOST, "slave").append(SERVER, "main-three").append(SUBSYSTEM_ELEMENT);
private static final PathAddress SERVER_ONE_EXT_ADDRESS = PathAddress.pathAddress(HOST, MASTER).append(SERVER, "main-one").append(EXTENSION_ELEMENT);
private static final PathAddress SERVER_THREE_EXT_ADDRESS = PathAddress.pathAddress(HOST, "slave").append(SERVER, "main-three").append(EXTENSION_ELEMENT);

private static DomainTestSupport testSupport;
private static DomainLifecycleUtil domainMasterLifecycleUtil;
@@ -90,6 +118,89 @@ public void testAddRemoveExtension() throws Exception {
extensionRemovalTest(slaveClient, null);
}

@Test
public void testExtensionSubsystemComposite() throws Exception {
DomainClient masterClient = domainMasterLifecycleUtil.getDomainClient();
Exception err = null;
try {
// 1) Sanity check -- subsystem not there
ModelNode read = Util.getReadAttributeOperation(PROFILE_SUBSYSTEM_ADDRESS, NAME);
testBadOp(read);

// 2) Sanity check -- Confirm slave has resources
verifyNotOnSlave();

// 3) Sanity check -- Confirm servers no longer have resource
verifyNotOnServers();

// 4) sanity check -- subsystem add w/o extension -- fail
ModelNode subAdd = Util.createAddOperation(PROFILE_SUBSYSTEM_ADDRESS);
subAdd.get(NAME).set(TestExtension.MODULE_NAME);
testBadOp(subAdd);

// 5) ext add + sub add + sub read in composite
ModelNode extAdd = Util.createAddOperation(EXTENSION_ADDRESS);
ModelNode goodAdd = buildComposite(extAdd, subAdd, read);
testGoodComposite(goodAdd);

// 6) Sanity check -- try read again outside the composite
ModelNode response = executeOp(read, "success");
assertTrue(response.toString(), response.has("result"));
assertEquals(response.toString(), TestExtension.MODULE_NAME, response.get("result").asString());

// 7) Confirm slave has resources
verifyOnSlave();

// 8) Confirm servers have the resources
verifyOnServers();

// 9) sub remove + ext remove + sub add in composite -- fail
ModelNode subRemove = Util.createRemoveOperation(PROFILE_SUBSYSTEM_ADDRESS);
ModelNode extRemove = Util.createRemoveOperation(EXTENSION_ADDRESS);
ModelNode badRemove = buildComposite(read, subRemove, extRemove, subAdd);
testBadOp(badRemove);

// 10) Confirm servers still have resources
verifyOnServers();

// 11) sub remove + ext remove in composite
ModelNode goodRemove = buildComposite(read, subRemove, extRemove);
response = executeOp(goodRemove, "success");
validateInvokePublicStep(response, 1, false);

// 12) Confirm slave no longer has resources
verifyNotOnSlave();

// 13) Confirm servers no longer have resource
verifyNotOnServers();

// 14) confirm ext add + sub add + sub read still works
testGoodComposite(goodAdd);

// 15) Sanity check -- try read again outside the composite
response = executeOp(read, "success");
assertTrue(response.toString(), response.has("result"));
assertEquals(response.toString(), TestExtension.MODULE_NAME, response.get("result").asString());

// 16) Confirm slave again has resources
verifyOnSlave();

// 17) Confirm servers again have resources
verifyOnServers();

} catch (Exception e) {
err = e;
} finally {
//Cleanup
removeIgnoreFailure(masterClient, PROFILE_SUBSYSTEM_ADDRESS);
removeIgnoreFailure(masterClient, EXTENSION_ADDRESS);
}

if (err != null) {
throw err;
}
}

private void extensionVersionTest(ModelControllerClient client, String addressPrefix) throws Exception {

String address = addressPrefix == null ? ADDRESS : addressPrefix + '/' + ADDRESS;
@@ -151,4 +262,111 @@ private static ModelNode executeForResult(final ModelNode op, final ModelControl
throw e;
}
}

private ModelNode executeOp(ModelNode op, String outcome) throws IOException {
ModelNode response = domainMasterLifecycleUtil.getDomainClient().execute(op);
assertTrue(response.toString(), response.hasDefined(OUTCOME));
assertEquals(response.toString(), outcome, response.get(OUTCOME).asString());
return response;
}

private void testGoodComposite(ModelNode composite) throws IOException {
ModelNode result = executeOp(composite, "success");
validateInvokePublicStep(result, 3, false);
}

private void testBadOp(ModelNode badOp) throws IOException {
ModelNode response = executeOp(badOp, "failed");
String msg = response.toString();
assertTrue(msg, response.has("failure-description"));
ModelNode failure = response.get("failure-description");
assertTrue(msg, failure.asString().contains("WFLYCTL0030"));
}

private void verifyOnSlave() throws IOException, MgmtOperationException {
DomainClient client = domainSlaveLifecycleUtil.getDomainClient();
ModelNode readExt = Util.createEmptyOperation(READ_RESOURCE_OPERATION, EXTENSION_ADDRESS);
ModelNode result = executeForResult(readExt, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.get(MODULE).asString());
ModelNode read = Util.getReadAttributeOperation(PROFILE_SUBSYSTEM_ADDRESS, NAME);
result = executeForResult(read, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.asString());
}

private void verifyOnServers() throws IOException, MgmtOperationException {
DomainClient client = domainMasterLifecycleUtil.getDomainClient();

ModelNode readExtOne = Util.getReadAttributeOperation(SERVER_ONE_EXT_ADDRESS, MODULE);
ModelNode result = executeForResult(readExtOne, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.asString());

ModelNode readSubOne = Util.getReadAttributeOperation(SERVER_ONE_SUBSYSTEM_ADDRESS, NAME);
result = executeForResult(readSubOne, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.asString());

ModelNode readExtThree = Util.getReadAttributeOperation(SERVER_THREE_EXT_ADDRESS, MODULE);
result = executeForResult(readExtThree, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.asString());

ModelNode readSubThree = Util.getReadAttributeOperation(SERVER_THREE_SUBSYSTEM_ADDRESS, NAME);
result = executeForResult(readSubThree, client);
assertEquals(result.toString(), TestExtension.MODULE_NAME, result.asString());
}

private void verifyNotOnSlave() throws IOException {
ModelNode readExt = Util.createEmptyOperation(READ_RESOURCE_OPERATION, EXTENSION_ADDRESS);
executeOp(readExt, FAILED);
ModelNode read = Util.getReadAttributeOperation(PROFILE_SUBSYSTEM_ADDRESS, NAME);
executeOp(read, FAILED);
}

private void verifyNotOnServers() throws IOException {
ModelNode readExtOne = Util.getReadAttributeOperation(SERVER_ONE_EXT_ADDRESS, MODULE);
executeOp(readExtOne, FAILED);

ModelNode readSubOne = Util.getReadAttributeOperation(SERVER_ONE_SUBSYSTEM_ADDRESS, NAME);
executeOp(readSubOne, FAILED);

ModelNode readExtThree = Util.getReadAttributeOperation(SERVER_THREE_EXT_ADDRESS, MODULE);
executeOp(readExtThree, FAILED);

ModelNode readSubThree = Util.getReadAttributeOperation(SERVER_THREE_SUBSYSTEM_ADDRESS, NAME);
executeOp(readSubThree, FAILED);

}

private static ModelNode buildComposite(ModelNode... steps) {
ModelNode result = Util.createEmptyOperation("composite", PathAddress.EMPTY_ADDRESS);
ModelNode stepsParam = result.get("steps");
for (ModelNode step : steps) {
stepsParam.add(step);
}
return result;
}

private static void validateInvokePublicStep(ModelNode response, int step, boolean expectRollback) {
String msg = response.toString();
assertTrue(msg, response.has("result"));
ModelNode result = response.get("result");
assertTrue(msg, result.isDefined());
String stepKey = "step-"+step;
assertEquals(msg, expectRollback ? "failed" : "success", result.get(stepKey, "outcome").asString());
assertTrue(msg, result.has(stepKey, "result"));
assertEquals(msg, TestExtension.MODULE_NAME, result.get(stepKey, "result").asString());
if (expectRollback) {
assertTrue(msg, result.has(stepKey, "rolled-back"));
assertTrue(msg, result.get(stepKey, "rolled-back").asBoolean());
} else {
assertFalse(msg, result.has(stepKey, "rolled-back"));
}
}

private void removeIgnoreFailure(ModelControllerClient client, PathAddress subsystemAddress) throws Exception {
try {
ModelNode op = Util.createRemoveOperation(subsystemAddress);
client.execute(op);
} catch (Exception ignore) {

}
}
}
@@ -41,6 +41,9 @@
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.net.InetAddress;
@@ -184,6 +187,63 @@ public void testSocketBindingCapabilities() throws Exception {
checkSocketBindingCapabilities(SLAVE_EXTENSION_ADDRESS);
}

@Test
public void testExtensionSubsystemComposite() throws Exception {
DomainClient slaveClient = domainSlaveLifecycleUtil.getDomainClient();
Exception err = null;
try {
// 1) Sanity check -- subsystem not there
ModelNode read = Util.getReadAttributeOperation(SLAVE_SUBSYSTEM_ADDRESS, NAME);
testBadOp(read);

// 2) sanity check -- subsystem add w/o extension -- fail
ModelNode subAdd = Util.createAddOperation(SLAVE_SUBSYSTEM_ADDRESS);
subAdd.get(NAME).set(TestHostCapableExtension.MODULE_NAME);
testBadOp(subAdd);

// 3) ext add + sub add + sub other in composite
ModelNode extAdd = Util.createAddOperation(SLAVE_EXTENSION_ADDRESS);
ModelNode goodAdd = buildComposite(extAdd, subAdd, read);
testGoodComposite(goodAdd);

// 4) Sanity check -- try read again outside the composite
ModelNode response = executeOp(read, "success");
assertTrue(response.toString(), response.has("result"));
assertEquals(response.toString(), TestHostCapableExtension.MODULE_NAME, response.get("result").asString());

// 5) sub remove + ext remove + sub add in composite -- fail
ModelNode subRemove = Util.createRemoveOperation(SLAVE_SUBSYSTEM_ADDRESS);
ModelNode extRemove = Util.createRemoveOperation(SLAVE_EXTENSION_ADDRESS);
ModelNode badRemove = buildComposite(read, subRemove, extRemove, subAdd);
response = testBadOp(badRemove);
// But the 'public' op should have worked
validateInvokePublicStep(response, 1, true);

// 6) sub remove + ext remove in composite
ModelNode goodRemove = buildComposite(read, subRemove, extRemove);
response = executeOp(goodRemove, "success");
validateInvokePublicStep(response, 1, false);

// 7) confirm ext add + sub add + sub other still works
testGoodComposite(goodAdd);

// 8) Sanity check -- try read again outside the composite
response = executeOp(read, "success");
assertTrue(response.toString(), response.has("result"));
assertEquals(response.toString(), TestHostCapableExtension.MODULE_NAME, response.get("result").asString());
} catch (Exception e) {
err = e;
} finally {
//Cleanup
removeIgnoreFailure(slaveClient, SLAVE_SUBSYSTEM_ADDRESS);
removeIgnoreFailure(slaveClient, SLAVE_EXTENSION_ADDRESS);
}

if (err != null) {
throw err;
}
}

private void checkSubsystemNeedsExtensionInLocalModel(ModelControllerClient masterClient, ModelControllerClient slaveClient, PathAddress extensionAddress) throws Exception {
Target target = Target.determineFromExtensionAddress(extensionAddress);
Exception err = null;
@@ -504,6 +564,53 @@ private boolean checkSlaveReconnected(ModelControllerClient masterClient) throws
return false;
}

private ModelNode executeOp(ModelNode op, String outcome) throws IOException {
ModelNode response = domainSlaveLifecycleUtil.getDomainClient().execute(op);
assertTrue(response.toString(), response.hasDefined(OUTCOME));
assertEquals(response.toString(), outcome, response.get(OUTCOME).asString());
return response;
}

private void testGoodComposite(ModelNode composite) throws IOException {
ModelNode result = executeOp(composite, "success");
validateInvokePublicStep(result, 3, false);
}

private ModelNode testBadOp(ModelNode badOp) throws IOException {
ModelNode response = executeOp(badOp, "failed");
String msg = response.toString();
assertTrue(msg, response.has("failure-description"));
ModelNode failure = response.get("failure-description");
assertTrue(msg, failure.asString().contains("WFLYCTL0030"));
return response;
}

private static ModelNode buildComposite(ModelNode... steps) {
ModelNode result = Util.createEmptyOperation("composite", PathAddress.EMPTY_ADDRESS);
ModelNode stepsParam = result.get("steps");
for (ModelNode step : steps) {
stepsParam.add(step);
}
return result;
}

private static void validateInvokePublicStep(ModelNode response, int step, boolean expectRollback) {
String msg = response.toString();
assertTrue(msg, response.has("result"));
ModelNode result = response.get("result");
assertTrue(msg, result.isDefined());
String stepKey = "step-"+step;
assertEquals(msg, expectRollback ? "failed" : "success", result.get(stepKey, "outcome").asString());
assertTrue(msg, result.has(stepKey, "result"));
assertEquals(msg, TestHostCapableExtension.MODULE_NAME, result.get(stepKey, "result").asString());
if (expectRollback) {
assertTrue(msg, result.has(stepKey, "rolled-back"));
assertTrue(msg, result.get(stepKey, "rolled-back").asBoolean());
} else {
assertFalse(msg, result.has(stepKey, "rolled-back"));
}
}

private enum Target {
DOMAIN,
MASTER,
@@ -396,6 +396,7 @@ public void testSlaveHCStageRollback() throws Exception {
}

private void errorTest(String host, String server, ErrorExtension.ErrorPoint errorPoint, boolean addRolloutPlan, boolean expectFailure) throws Exception {
boolean multiphase = true;
ModelNode op = ERROR_OP.clone();
op.get(TARGET_HOST.getName()).set(host);
if (server != null) {
@@ -410,6 +411,7 @@ private void errorTest(String host, String server, ErrorExtension.ErrorPoint err
// retarget the op to the HC subsystem
PathAddress addr = host.equals("master") ? MASTER_ADDRESS : SLAVE_ADDRESS;
op.get(OP_ADDR).set(addr.append(SUBSYSTEM_ELEMENT).toModelNode());
multiphase = false;
}

ModelNode response;
@@ -419,11 +421,12 @@ private void errorTest(String host, String server, ErrorExtension.ErrorPoint err
response = executeForResponse(op, masterClient);
}
if (errorPoint != ErrorExtension.ErrorPoint.SERVICE_STOP) {
validateResponseDetails(response, host, server, errorPoint);
validateResponseDetails(response, host, server, errorPoint, multiphase);
} // else it's a success and I'm too lazy to write the code to verify the response
}

private void validateResponseDetails(ModelNode response, String host, String server, ErrorExtension.ErrorPoint errorPoint) {
private void validateResponseDetails(ModelNode response, String host, String server,
ErrorExtension.ErrorPoint errorPoint, boolean multiphase) {
ModelNode unmodified = response.clone();
ModelNode fd;
if (server != null) {
@@ -439,6 +442,8 @@ private void validateResponseDetails(ModelNode response, String host, String ser
// simple structure and want this test to enforce that. OTOH it shouldn't be changed lightly
// as it would be easy to mess up.
fd = response.get(FAILURE_DESCRIPTION);
} else if (!multiphase) {
fd = response.get(FAILURE_DESCRIPTION);
} else if (RUNTIME_POINTS.contains(errorPoint)) {
fd = response.get(FAILURE_DESCRIPTION, HOST_FAILURE_DESCRIPTIONS, host);
} else {
@@ -450,7 +455,7 @@ private void validateResponseDetails(ModelNode response, String host, String ser
assertFalse(unmodified.toString(), response.hasDefined(FAILURE_DESCRIPTION));
return;
}
fd = response.get(FAILURE_DESCRIPTION, HOST_FAILURE_DESCRIPTIONS, host);
fd = multiphase ? response.get(FAILURE_DESCRIPTION, HOST_FAILURE_DESCRIPTIONS, host) : response.get(FAILURE_DESCRIPTION);
}
ModelType errorType = errorPoint == ErrorExtension.ErrorPoint.SERVICE_START ? ModelType.OBJECT : ModelType.STRING;
assertEquals(unmodified.toString(), errorType, fd.getType());
@@ -51,14 +51,28 @@ public void cleanup() throws Exception {

@Test
public void testInvalidHostContextMapValue() {
boolean success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"\\\\?.invalid.example.com\"=exampleSslContext})", true);
boolean success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"\\\\?.invalid.com\"=exampleSslContext})", true);
Assert.assertFalse(success);
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("not a valid hostname"));
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("Invalid value of host context map"));
success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"invalid\\\\.\\\\.example.com\"=exampleSslContext})", true);
Assert.assertFalse(success);
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("Invalid value of host context map"));
success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"*\\.invalid.com\"=exampleSslContext})", true);
Assert.assertFalse(success);
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("Invalid value of host context map"));
success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"invalid.com-\"=exampleSslContext})", true);
Assert.assertFalse(success);
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("Invalid value of host context map"));
success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"invalid.com\\\\.\"=exampleSslContext})", true);
Assert.assertFalse(success);
Assert.assertThat("Wrong error message", cli.readOutput(), containsString("Invalid value of host context map"));
}

@Test
public void testValidHostContextMapValue() {
boolean success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"valid.example.com\"=exampleSslContext})", true);
boolean success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"..valid\\\\.example\\\\.com\"=exampleSslContext})", true);
Assert.assertTrue(success);
success = cli.sendLine("/subsystem=elytron/server-ssl-sni-context=exampleSslSniContext:write-attribute(name=host-context-map,value={\"valid\\\\.example\\\\.com\"=exampleSslContext})", true);
Assert.assertTrue(success);
}

@@ -138,7 +138,7 @@ private OperationTypesSubsystemResourceDefinition(ProcessType processType) {
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);

resourceRegistration.registerOperationHandler(PUBLIC, NoopOperationStepHandler.WITH_RESULT);
resourceRegistration.registerOperationHandler(PUBLIC, ((context, operation) -> context.getResult().set(true)));
resourceRegistration.registerOperationHandler(HIDDEN, NoopOperationStepHandler.WITH_RESULT);
resourceRegistration.registerOperationHandler(PRIVATE, NoopOperationStepHandler.WITH_RESULT);
resourceRegistration.registerOperationHandler(RUNTIME_ONLY, RuntimeOnlyHandler.INSTANCE);
@@ -0,0 +1,170 @@
/*
Copyright 2019 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.wildfly.core.test.standalone.mgmt;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.integration.management.extension.EmptySubsystemParser;
import org.jboss.as.test.integration.management.extension.ExtensionUtils;
import org.jboss.as.test.integration.management.extension.optypes.OpTypesExtension;
import org.jboss.dmr.ModelNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ManagementClient;
import org.wildfly.core.testrunner.WildflyTestRunner;

import javax.inject.Inject;
import java.io.IOException;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* Tests adding and removing extensions and subsystems in a composite op.
*
* @author Brian Stansberry
*/
@RunWith(WildflyTestRunner.class)
public class ExtensionSubsystemCompositeTestCase {

private static final PathAddress EXT = PathAddress.pathAddress("extension", OpTypesExtension.EXTENSION_NAME);
private static final PathAddress SUBSYSTEM = PathAddress.pathAddress("subsystem", OpTypesExtension.SUBSYSTEM_NAME);

@Inject
private static ManagementClient managementClient;

@Before
public void installExtensionModule() throws IOException {
// We use OpTypesExtension for this test because it's convenient. Doesn't do anything crazy
// and exposes an op ('public') that we can call to check the subsystem is added and functions
ExtensionUtils.createExtensionModule(OpTypesExtension.EXTENSION_NAME, OpTypesExtension.class,
EmptySubsystemParser.class.getPackage());
}

@After
public void removeExtensionModule() {

try {
executeOp(Util.createRemoveOperation(SUBSYSTEM), SUCCESS);
} catch (Throwable ignored) {
// assume subsystem wasn't there
} finally {
try {
executeOp(Util.createRemoveOperation(EXT), SUCCESS);
} catch (Throwable t) {
// assume extension wasn't there
} finally {
ExtensionUtils.deleteExtensionModule(OpTypesExtension.EXTENSION_NAME);
}
}
}

@Test
public void test() throws IOException {

// 1) Sanity check -- subsystem not there
ModelNode invokePublic = Util.createEmptyOperation("public", SUBSYSTEM);
testBadOp(invokePublic);

// 2) sanity check -- subsystem add w/o extension -- fail
ModelNode subAdd = Util.createAddOperation(SUBSYSTEM);
testBadOp(subAdd);

// 3) ext add + sub add + sub other in composite
ModelNode extAdd = Util.createAddOperation(EXT);
ModelNode goodAdd = buildComposite(extAdd, subAdd, invokePublic);
testGoodComposite(goodAdd);

// 4) Sanity check -- try invokePublic again outside the composite
ModelNode response = executeOp(invokePublic, "success");
assertTrue(response.toString(), response.has("result"));
assertTrue(response.toString(), response.get("result").asBoolean());

// 5) sub remove + ext remove + sub add in composite -- fail
ModelNode subRemove = Util.createRemoveOperation(SUBSYSTEM);
ModelNode extRemove = Util.createRemoveOperation(EXT);
ModelNode badRemove = buildComposite(invokePublic, subRemove, extRemove, subAdd);
response = testBadOp(badRemove);
// But the 'public' op should have worked
validateInvokePublicStep(response, 1, true);

// 6) sub remove + ext remove in composite
ModelNode goodRemove = buildComposite(invokePublic, subRemove, extRemove);
response = executeOp(goodRemove, "success");
validateInvokePublicStep(response, 1, false);

// 7) confirm ext add + sub add + sub other still works
testGoodComposite(goodAdd);

// 8) Sanity check -- try invokePublic again outside the composite
response = executeOp(invokePublic, "success");
assertTrue(response.toString(), response.has("result"));
assertTrue(response.toString(), response.get("result").asBoolean());
}

private ModelNode executeOp(ModelNode op, String outcome) throws IOException {
ModelNode response = managementClient.getControllerClient().execute(op);
assertTrue(response.toString(), response.hasDefined(OUTCOME));
assertEquals(response.toString(), outcome, response.get(OUTCOME).asString());
return response;
}

private void testGoodComposite(ModelNode composite) throws IOException {
ModelNode result = executeOp(composite, "success");
validateInvokePublicStep(result, 3, false);
}

private ModelNode testBadOp(ModelNode badOp) throws IOException {
ModelNode response = executeOp(badOp, "failed");
String msg = response.toString();
assertTrue(msg, response.has("failure-description"));
ModelNode failure = response.get("failure-description");
assertTrue(msg, failure.asString().contains("WFLYCTL0030"));
return response;
}

private static ModelNode buildComposite(ModelNode... steps) {
ModelNode result = Util.createEmptyOperation("composite", PathAddress.EMPTY_ADDRESS);
ModelNode stepsParam = result.get("steps");
for (ModelNode step : steps) {
stepsParam.add(step);
}
return result;
}

private static void validateInvokePublicStep(ModelNode response, int step, boolean expectRollback) {
String msg = response.toString();
assertTrue(msg, response.has("result"));
ModelNode result = response.get("result");
assertTrue(msg, result.isDefined());
String stepKey = "step-"+step;
assertEquals(msg, expectRollback ? "failed" : "success", result.get(stepKey, "outcome").asString());
assertTrue(msg, result.has(stepKey, "result"));
assertTrue(msg, result.get(stepKey, "result").asBoolean());
if (expectRollback) {
assertTrue(msg, result.has(stepKey, "rolled-back"));
assertTrue(msg, result.get(stepKey, "rolled-back").asBoolean());
} else {
assertFalse(msg, result.has(stepKey, "rolled-back"));
}
}

}