@@ -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();
@@ -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());
@@ -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"));
}
}

}